diff --git a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php index bf042c0a12e1b..49c5c2291f9e4 100755 --- a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php +++ b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsCallback.php @@ -21,7 +21,16 @@ use Magento\Backend\Model\View\Result\Redirect; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; +/** + * Class ImsCallback is responsible to get the Access Token, User Profile, + * check if the assigned organization is valid, And Check if user exists and then do the login + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ImsCallback extends Auth implements HttpGetActionInterface { public const ACTION_NAME = 'imscallback'; @@ -75,6 +84,28 @@ public function __construct( $this->logger = $logger; } + /** + * Validate IMS state is valid + * + * @param RequestInterface $request + * @return ResponseInterface + */ + public function dispatch(RequestInterface $request) + { + $request->setParam('form_key', $request->getParam('state', null)); + if (!$this->_formKeyValidator->validate($request)) { + $this->logger->critical(__('Invalid state returned in callback from IMS.')); + $this->imsErrorMessage( + 'Error signing in', + 'Something went wrong and we could not sign you in. ' . + 'Please try again or contact your administrator.' + ); + $this->_actionFlag->set('', ActionInterface::FLAG_NO_DISPATCH, true); + return $this->_redirect($this->_helper->getHomePageUrl()); + } + return parent::dispatch($request); + } + /** * Execute AdobeIMS callback * diff --git a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php index 846b0332f705c..b4af5b48af612 100755 --- a/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php +++ b/app/code/Magento/AdminAdobeIms/Controller/Adminhtml/OAuth/ImsReauthCallback.php @@ -21,6 +21,8 @@ use Magento\Framework\Controller\Result\Raw; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Exception\NotFoundException; class ImsReauthCallback extends Auth implements HttpGetActionInterface { @@ -111,6 +113,7 @@ public function execute(): ResultInterface } try { + $this->validateStateKey($this->getRequest()); $code = $this->getRequest()->getParam('code'); if ($code === null) { @@ -149,4 +152,19 @@ public function execute(): ResultInterface return $resultRaw; } + + /** + * Validate IMS state is valid + * + * @param RequestInterface $request + * @return void + * @throws NotFoundException + */ + private function validateStateKey(RequestInterface $request): void + { + $request->setParam('form_key', $request->getParam('state', null)); + if (!$this->_formKeyValidator->validate($request)) { + throw new NotFoundException(__('Invalid state returned from IMS')); + } + } } diff --git a/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php b/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php index 5b95893b23430..feb2323ab6147 100644 --- a/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php +++ b/app/code/Magento/AdminAdobeIms/Model/ImsConnection.php @@ -167,8 +167,12 @@ public function validateToken(?string $token, string $tokenType = 'access_token' $curl->addHeader('cache-control', 'no-cache'); $curl->post( - $this->adminImsConfig->getValidateTokenUrl($token, $tokenType), - [] + $this->adminImsConfig->getValidateTokenUrl(), + [ + 'token' => $token, + 'type' => $tokenType, + 'client_id' => $this->adminImsConfig->getApiKey() + ] ); if ($curl->getBody() === '') { diff --git a/app/code/Magento/AdminAdobeIms/Model/LogOut.php b/app/code/Magento/AdminAdobeIms/Model/LogOut.php index 001d3a0794dd2..188bdce29c55a 100644 --- a/app/code/Magento/AdminAdobeIms/Model/LogOut.php +++ b/app/code/Magento/AdminAdobeIms/Model/LogOut.php @@ -112,8 +112,12 @@ private function externalLogOut(string $accessToken): void $curl->addHeader('cache-control', 'no-cache'); $curl->post( - $this->adminImsConfig->getBackendLogoutUrl($accessToken), - [] + $this->adminImsConfig->getBackendLogoutUrl(), + [ + 'access_token' => $accessToken, + 'client_secret' => $this->adminImsConfig->getPrivateKey(), + 'client_id' => $this->adminImsConfig->getApiKey() + ] ); if ($curl->getStatus() !== self::HTTP_OK || ($this->checkUserProfile($accessToken))) { diff --git a/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php b/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php index 400602395d419..dda1e09dc6569 100644 --- a/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php +++ b/app/code/Magento/AdminAdobeIms/Service/ImsConfig.php @@ -18,6 +18,7 @@ use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\UrlInterface; +use Magento\Framework\Data\Form\FormKey; class ImsConfig extends Config { @@ -58,25 +59,33 @@ class ImsConfig extends Config */ private BackendUrlInterface $backendUrl; + /** + * @var FormKey + */ + private FormKey $formKey; + /** * @param ScopeConfigInterface $scopeConfig * @param UrlInterface $url * @param WriterInterface $writer * @param EncryptorInterface $encryptor * @param BackendUrlInterface $backendUrl + * @param FormKey $formKey */ public function __construct( ScopeConfigInterface $scopeConfig, UrlInterface $url, WriterInterface $writer, EncryptorInterface $encryptor, - BackendUrlInterface $backendUrl + BackendUrlInterface $backendUrl, + FormKey $formKey ) { parent::__construct($scopeConfig, $url); $this->writer = $writer; $this->encryptor = $encryptor; $this->scopeConfig = $scopeConfig; $this->backendUrl = $backendUrl; + $this->formKey = $formKey; } /** @@ -180,17 +189,11 @@ public function getProfileUrl(): string /** * Get Token validation url * - * @param string $code - * @param string $tokenType * @return string */ - public function getValidateTokenUrl(string $code, string $tokenType): string + public function getValidateTokenUrl(): string { - return str_replace( - ['#{token}', '#{client_id}', '#{token_type}'], - [$code, $this->getApiKey(), $tokenType], - $this->scopeConfig->getValue(self::XML_PATH_VALIDATE_TOKEN_URL) - ); + return $this->scopeConfig->getValue(self::XML_PATH_VALIDATE_TOKEN_URL); } /** @@ -253,11 +256,12 @@ public function getAdminAdobeImsAuthUrl(?string $clientId): string } return str_replace( - ['#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'], + ['#{client_id}', '#{redirect_uri}', '#{scope}', '#{state}', '#{locale}'], [ $clientId, $this->getAdminAdobeImsCallBackUrl(), $this->getScopes(), + $this->formKey->getFormKey(), $this->getLocale() ], $this->scopeConfig->getValue(self::XML_PATH_ADMIN_AUTH_URL_PATTERN) @@ -272,11 +276,12 @@ public function getAdminAdobeImsAuthUrl(?string $clientId): string public function getAdminAdobeImsReAuthUrl(): string { return str_replace( - ['#{client_id}', '#{redirect_uri}', '#{scope}', '#{locale}'], + ['#{client_id}', '#{redirect_uri}', '#{scope}', '#{state}', '#{locale}'], [ $this->getApiKey(), $this->getAdminAdobeImsReAuthCallBackUrl(), $this->getScopes(), + $this->formKey->getFormKey(), $this->getLocale() ], $this->scopeConfig->getValue(self::XML_PATH_ADMIN_REAUTH_URL_PATTERN) @@ -345,16 +350,11 @@ private function getLocale(): string /** * Get BackendLogout URL * - * @param string $accessToken * @return string */ - public function getBackendLogoutUrl(string $accessToken) : string + public function getBackendLogoutUrl() : string { - return str_replace( - ['#{access_token}', '#{client_secret}', '#{client_id}'], - [$accessToken, $this->getPrivateKey(), $this->getApiKey()], - $this->scopeConfig->getValue(self::XML_PATH_ADMIN_LOGOUT_URL) - ); + return $this->scopeConfig->getValue(self::XML_PATH_ADMIN_LOGOUT_URL); } /** diff --git a/app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php b/app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php index c562ce8a482a6..cf15cd4618cee 100644 --- a/app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php +++ b/app/code/Magento/AdminAdobeIms/Service/ImsOrganizationService.php @@ -75,7 +75,6 @@ public function checkOrganizationMembership(string $access_token): void __('User is not a member of configured Adobe Organization.') ); } - } catch (\Exception $exception) { throw new AdobeImsOrganizationAuthorizationException( __('Organization Membership check can\'t be performed') diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Controller/Adminhtml/OAuth/ImsCallbackTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Controller/Adminhtml/OAuth/ImsCallbackTest.php new file mode 100644 index 0000000000000..dd8a994c96570 --- /dev/null +++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Controller/Adminhtml/OAuth/ImsCallbackTest.php @@ -0,0 +1,252 @@ +objectManagerMock = $this->getMockBuilder(ObjectManager::class) + ->disableOriginalConstructor() + ->onlyMethods(['get', 'create']) + ->getMock(); + $this->requestMock = $this->getMockBuilder(Http::class) + ->disableOriginalConstructor() + ->onlyMethods(['getParam', 'setParam']) + ->getMock(); + $responseMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http::class) + ->disableOriginalConstructor() + ->addMethods([]) + ->getMock(); + $this->validatorMock = $this->getMockBuilder(Validator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->loggerMock = $this->getMockBuilder(AdminAdobeImsLogger::class) + ->disableOriginalConstructor() + ->getMock(); + $this->messagesMock = $this->getMockBuilder(Manager::class) + ->disableOriginalConstructor() + ->onlyMethods(['addComplexErrorMessage']) + ->getMockForAbstractClass(); + $this->authSessionMock = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->addMethods(['setIsUrlNotice', 'getLocale']) + ->getMock(); + $this->authMock = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->getMock(); + $this->actionFlagMock = $this->getMockBuilder(ActionFlag::class) + ->disableOriginalConstructor() + ->getMock(); + $this->imsConnectionMock = $this->getMockBuilder(ImsConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->localeMock = $this->getMockBuilder(Locale::class) + ->disableOriginalConstructor() + ->onlyMethods(['isValid']) + ->getMock(); + $this->managerMock = $this->getMockBuilder(\Magento\Backend\Model\Locale\Manager::class) + ->disableOriginalConstructor() + ->onlyMethods(['switchBackendInterfaceLocale']) + ->getMock(); + $this->helperMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->onlyMethods(['getHomePageUrl', 'getUrl']) + ->getMock(); + $imsConfigMock = $this->getMockBuilder(ImsConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $imsOrganizationServiceMock = $this->getMockBuilder(ImsOrganizationService::class) + ->disableOriginalConstructor() + ->getMock(); + $loginProcessServiceMock = $this->createMock(AdminLoginProcessService::class); + $contextMock = $this->getMockBuilder(Context::class) + ->addMethods(['getFrontController', 'getTranslator']) + ->onlyMethods([ + 'getRequest', + 'getFormKeyValidator', + 'getMessageManager', + 'getHelper', + 'getActionFlag', + 'getResponse', + 'getSession', + 'getAuth', + 'getObjectManager' + ]) + ->disableOriginalConstructor() + ->getMock(); + $contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); + $contextMock->expects($this->once())->method('getResponse')->willReturn($responseMock); + $contextMock->expects($this->once())->method('getAuth')->willReturn($this->authMock); + $contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); + $contextMock->expects($this->once())->method('getFormKeyValidator')->willReturn($this->validatorMock); + $contextMock->expects($this->once())->method('getActionFlag')->willReturn($this->actionFlagMock); + $contextMock->expects($this->once())->method('getSession')->willReturn($this->authSessionMock); + $contextMock->expects($this->once())->method('getHelper')->willReturn($this->helperMock); + $contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messagesMock); + + $this->controller = new ImsCallback( + $contextMock, + $this->imsConnectionMock, + $imsConfigMock, + $imsOrganizationServiceMock, + $loginProcessServiceMock, + $this->loggerMock, + ); + } + + /** + * Validate if state exists in ims callback url. + * @return void + */ + public function testStateExistsInImsCallback(): void + { + $this->addMockData(); + $this->validatorMock->expects($this->once())->method('validate') + ->with($this->requestMock) + ->willReturn(true); + $response = $this->controller->dispatch($this->requestMock); + $this->assertEquals(200, $response->getHttpResponseCode()); + } + + /** + * Validate if state not exists in ims callback url. + * @return void + */ + public function testStateNotExistsInImsCallback(): void + { + $this->addMockData(); + $this->validatorMock->expects($this->once())->method('validate') + ->with($this->requestMock) + ->willReturn(false); + $response = $this->controller->dispatch($this->requestMock); + $this->assertEquals(302, $response->getHttpResponseCode()); + } + + /** + * Add mock data for tests + * @return void + */ + private function addMockData(): void + { + $this->requestMock->expects($this->any())->method('setParam') + ->with('form_key') + ->willReturnSelf(); + $this->requestMock->expects($this->any())->method('getParam') + ->withConsecutive(['state'], ['locale']) + ->willReturnOnConsecutiveCalls('abc', 'en'); + $this->authSessionMock->expects($this->any())->method('setIsUrlNotice') + ->willReturnSelf(); + $this->authSessionMock->expects($this->any())->method('getLocale') + ->willReturn('en'); + $this->actionFlagMock->expects($this->any())->method('get') + ->with('', 'check_url_settings') + ->willReturn(true); + $this->helperMock->expects($this->any())->method('getHomePageUrl') + ->willReturn('https://magento.test/admin'); + $this->helperMock->expects($this->any())->method('getUrl') + ->willReturn('https://magento.test/admin'); + $this->authMock->expects($this->any())->method('isLoggedIn')->willReturn(false); + $this->objectManagerMock + ->method('get') + ->withConsecutive([Locale::class], [\Magento\Backend\Model\Locale\Manager::class]) + ->willReturnOnConsecutiveCalls($this->localeMock, $this->managerMock); + } +} diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Controller/Adminhtml/OAuth/ImsReauthCallbackTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Controller/Adminhtml/OAuth/ImsReauthCallbackTest.php new file mode 100644 index 0000000000000..5306ddcf692c8 --- /dev/null +++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Controller/Adminhtml/OAuth/ImsReauthCallbackTest.php @@ -0,0 +1,157 @@ +request = $this->getMockBuilder(Http::class) + ->disableOriginalConstructor() + ->onlyMethods(['getParam', 'setParam']) + ->getMock(); + $this->resultRaw = $this->createMock(Raw::class); + $this->resultFactory = $this->createMock(ResultFactory::class); + $this->context = $this->createMock(Context::class); + $this->imsConfigMock = $this->createMock(ImsConfig::class); + $this->loggerMock = $this->createMock(AdminAdobeImsLogger::class); + $this->messagesMock = $this->getMockBuilder(Manager::class) + ->disableOriginalConstructor() + ->onlyMethods(['addErrorMessage']) + ->getMockForAbstractClass(); + } + + /** + * Validate state key not exists in callback + * @return void + */ + public function testExecuteStateKeyNotExistsInCallBack(): void + { + $this->setMockData(); + $content = 'auth[code=error;message=Invalid state returned from IMS]'; + $this->resultRaw->expects($this->once())->method('setContents')->with($content)->willReturnSelf(); + $this->validatorMock->expects($this->once())->method('validate') + ->with($this->request) + ->willReturn(false); + + $this->assertSame($this->resultRaw, $this->controller->execute()); + } + + /** + * Set mock objects data + * @return void + */ + private function setMockData(): void + { + $this->request->expects($this->any())->method('setParam') + ->with('form_key') + ->willReturnSelf(); + $this->request->expects($this->any())->method('getParam') + ->withConsecutive(['state'], ['code']) + ->willReturnOnConsecutiveCalls(null, 'asdasdasdad'); + $this->resultFactory->expects($this->once()) + ->method('create') + ->with(ResultFactory::TYPE_RAW) + ->willReturn($this->resultRaw); + $this->validatorMock = $this->getMockBuilder(Validator::class) + ->disableOriginalConstructor() + ->getMock(); + $imsConnectionMock = $this->getMockBuilder(ImsConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $imsOrganizationServiceMock = $this->getMockBuilder(ImsOrganizationService::class) + ->disableOriginalConstructor() + ->getMock(); + $loginProcessServiceMock = $this->createMock(AdminReauthProcessService::class); + + $this->context->expects($this->once())->method('getRequest')->willReturn($this->request); + $this->context->expects($this->once())->method('getResultFactory')->willReturn($this->resultFactory); + $this->context->expects($this->any())->method('getMessageManager')->willReturn($this->messagesMock); + $this->context->expects($this->any())->method('getFormKeyValidator')->willReturn($this->validatorMock); + + $this->imsConfigMock->expects($this->once())->method('enabled') + ->willReturn(true); + $this->controller = new ImsReauthCallback( + $this->context, + $imsConnectionMock, + $this->imsConfigMock, + $imsOrganizationServiceMock, + $loginProcessServiceMock, + $this->loggerMock + ); + } +} diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php index 5596b3a3d6c0d..641915024b5ec 100644 --- a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php +++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/ImsConnectionTest.php @@ -38,25 +38,35 @@ class ImsConnectionTest extends TestCase */ private $adminImsConnection; + /** + * @var Json|mixed|\PHPUnit\Framework\MockObject\MockObject + */ + private $json; + + /** + * @var ImsConfig|mixed|\PHPUnit\Framework\MockObject\MockObject + */ + private $adminImsConfigMock; + protected function setUp(): void { $objectManagerHelper = new ObjectManagerHelper($this); - $adminImsConfigMock = $this->createMock(ImsConfig::class); - $adminImsConfigMock + $this->adminImsConfigMock = $this->createMock(ImsConfig::class); + $this->adminImsConfigMock ->method('getAuthUrl') ->willReturn(self::AUTH_URL); $this->curlFactory = $this->createMock(CurlFactory::class); - $json = $this->createMock(Json::class); + $this->json = $this->createMock(Json::class); $this->adminImsConnection = $objectManagerHelper->getObject( ImsConnection::class, [ 'curlFactory' => $this->curlFactory, - 'adminImsConfig' => $adminImsConfigMock, - 'json' => $json, + 'adminImsConfig' => $this->adminImsConfigMock, + 'json' => $this->json, ] ); } @@ -92,4 +102,31 @@ public function testAuthThrowsExceptionWhenResponseContainsError(): void $this->expectExceptionMessage('Could not connect to Adobe IMS Service: invalid_scope.'); $this->adminImsConnection->auth(); } + + /** + * Token validate test + * + * @return void + */ + public function testValidateToken(): void + { + $this->adminImsConfigMock->method('getValidateTokenUrl') + ->willReturn('https://ims-na1-stg1.adobelogin.com/ims/validate_token/v1'); + $this->adminImsConfigMock->method('getApiKey') + ->willReturn('api_key'); + $curlMock = $this->createMock(Curl::class); + $curlMock->expects($this->once()) + ->method('post') + ->willReturn(null); + $curlMock->method('getBody') + ->willReturn('{"valid":1}'); + $curlMock->method('getStatus') + ->willReturn(302); + $this->json->method('unserialize') + ->with('{"valid":1}') + ->willReturn(['valid' => true]); + $this->curlFactory->method('create') + ->willReturn($curlMock); + $this->assertTrue($this->adminImsConnection->validateToken('valid_token')); + } } diff --git a/app/code/Magento/AdminAdobeIms/Test/Unit/Model/LogOutTest.php b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/LogOutTest.php new file mode 100644 index 0000000000000..cd7160ed51bdc --- /dev/null +++ b/app/code/Magento/AdminAdobeIms/Test/Unit/Model/LogOutTest.php @@ -0,0 +1,155 @@ +curlFactoryMock = $this->createMock(CurlFactory::class); + $this->config = $this->createMock(ImsConfig::class); + $this->loggerInterfaceMock = $this->createMock(LoggerInterface::class); + $this->imsConnection = $this->createMock(ImsConnection::class); + $this->auth = $this->createMock(Auth::class); + $this->session = $this->getMockBuilder(StorageInterface::class) + ->addMethods(['getAdobeAccessToken']) + ->getMockForAbstractClass(); + $this->model = new LogOut( + $this->loggerInterfaceMock, + $this->config, + $this->curlFactoryMock, + $this->imsConnection, + $this->auth + ); + } + + /** + * Test LogOut. + * @return void + */ + public function testExecute(): void + { + $this->session->expects($this->any()) + ->method('getAdobeAccessToken') + ->willReturn('access_token'); + $this->auth->expects($this->any()) + ->method('getAuthStorage') + ->willReturn($this->session); + $this->imsConnection->expects($this->exactly(2)) + ->method('getProfile') + ->willReturnOnConsecutiveCalls(['email' => 'test@email.com'], []); + + $curl = $this->createMock(Curl::class); + + $this->curlFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($curl); + $curl->expects($this->exactly(2)) + ->method('addHeader') + ->willReturn(null); + $curl->expects($this->once()) + ->method('post') + ->willReturn(null); + $curl->expects($this->any()) + ->method('getStatus') + ->willReturn(self::HTTP_FOUND); + + $this->assertEquals(true, $this->model->execute('access_token')); + } + + /** + * Test LogOut with Error. + * @return void + */ + public function testExecuteWithError(): void + { + $this->session->expects($this->any()) + ->method('getAdobeAccessToken') + ->willReturn('access_token'); + $this->auth->expects($this->any()) + ->method('getAuthStorage') + ->willReturn($this->session); + $this->imsConnection->expects($this->exactly(1)) + ->method('getProfile') + ->willReturnOnConsecutiveCalls(['email' => 'test@email.com'], []); + + $curl = $this->createMock(Curl::class); + + $this->curlFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($curl); + $curl->expects($this->exactly(2)) + ->method('addHeader') + ->willReturn(null); + $curl->expects($this->once()) + ->method('post') + ->willReturn(null); + $curl->expects($this->any()) + ->method('getStatus') + ->willReturn(self::HTTP_ERROR); + + $this->assertEquals(false, $this->model->execute('access_token')); + } +} diff --git a/app/code/Magento/AdminAdobeIms/composer.json b/app/code/Magento/AdminAdobeIms/composer.json index 0da1aa2549305..ad1227088c0f4 100644 --- a/app/code/Magento/AdminAdobeIms/composer.json +++ b/app/code/Magento/AdminAdobeIms/composer.json @@ -1,33 +1,34 @@ { "name": "magento/module-admin-adobe-ims", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.0-p2", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-adobe-ims": "*", - "magento/module-adobe-ims-api": "*", - "magento/module-config": "*", - "magento/module-backend": "*", - "magento/module-user": "*", - "magento/module-captcha": "*", - "magento/module-authorization": "*", - "magento/module-store": "*", - "magento/module-email": "*", - "magento/module-integration": "*", - "magento/module-jwt-user-token": "*", - "magento/module-security": "*" + "magento/framework": "103.0.*", + "magento/module-adobe-ims": "2.1.*", + "magento/module-adobe-ims-api": "2.1.*", + "magento/module-config": "101.2.*", + "magento/module-backend": "102.0.*", + "magento/module-user": "101.2.*", + "magento/module-captcha": "100.4.*", + "magento/module-authorization": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-email": "101.1.*", + "magento/module-integration": "100.4.*", + "magento/module-jwt-user-token": "100.4.*", + "magento/module-security": "100.4.*" }, "suggest": { - "magento/module-theme": "*" + "magento/module-theme": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -37,3 +38,4 @@ } } } + diff --git a/app/code/Magento/AdminAdobeIms/etc/config.xml b/app/code/Magento/AdminAdobeIms/etc/config.xml index 07a1bfed83687..7419e3f8ab696 100644 --- a/app/code/Magento/AdminAdobeIms/etc/config.xml +++ b/app/code/Magento/AdminAdobeIms/etc/config.xml @@ -11,8 +11,8 @@ 0 - - + + AdobeID openid @@ -27,9 +27,9 @@ https://ims-na1.adobelogin.com/ims/token - + - + admin_adobe_ims_email_header_template diff --git a/app/code/Magento/AdminAdobeIms/etc/di.xml b/app/code/Magento/AdminAdobeIms/etc/di.xml index 6f3b05f6f2bb6..ff6ab6998d2fa 100644 --- a/app/code/Magento/AdminAdobeIms/etc/di.xml +++ b/app/code/Magento/AdminAdobeIms/etc/di.xml @@ -67,7 +67,6 @@ - @@ -77,4 +76,10 @@ + + + + Magento\Framework\Data\Form\FormKey\Proxy + + diff --git a/app/code/Magento/AdminAdobeIms/i18n/en_US.csv b/app/code/Magento/AdminAdobeIms/i18n/en_US.csv new file mode 100644 index 0000000000000..5291ce03afce2 --- /dev/null +++ b/app/code/Magento/AdminAdobeIms/i18n/en_US.csv @@ -0,0 +1,66 @@ +"Admin Adobe IMS integration is disabled","Admin Adobe IMS integration is disabled" +"Admin Adobe IMS integration is enabled","Admin Adobe IMS integration is enabled" +"The Client ID, Client Secret, Organization ID and 2FA are required when enabling the Admin Adobe IMS Module","The Client ID, Client Secret, Organization ID and 2FA are required when enabling the Admin Adobe IMS Module" +"Module is disabled","Module is disabled" +"Admin Adobe IMS integration is %1","Admin Adobe IMS integration is %1" +"Invalid state returned in callback from IMS.","Invalid state returned in callback from IMS." +"An authentication error occurred. Verify and try again.","An authentication error occurred. Verify and try again." +"Adobe Sign-In is disabled.","Adobe Sign-In is disabled." +"Authorization was successful","Authorization was successful" +"Invalid state returned from IMS","Invalid state returned from IMS" +"Could not connect to Adobe IMS Service: %1.","Could not connect to Adobe IMS Service: %1." +"Could not get a valid response from Adobe IMS Service.","Could not get a valid response from Adobe IMS Service." +"Could not verify the access_token","Could not verify the access_token" +"Profile body is empty","Profile body is empty" +"Could not save ims token.","Could not save ims token." +"Could not find ims token id: %id.","Could not find ims token id: %id." +"Could not delete ims tokens for admin user id %1.","Could not delete ims tokens for admin user id %1." +"An error occurred during logout operation.","An error occurred during logout operation." +"Failed to get JWK","Failed to get JWK" +"Failed to read JWT token","Failed to read JWT token" +"JWT does not contain claims","JWT does not contain claims" +"created_at not provided by the received JWT","created_at not provided by the received JWT" +"expires_in not provided by the received JWT","expires_in not provided by the received JWT" +"Token has expired","Token has expired" +"The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later.","The account sign-in was incorrect or your account is disabled temporarily. Please wait and try again later." +"More permissions are needed to access this.","More permissions are needed to access this." +"Please sign in with Adobe ID","Please sign in with Adobe ID" +"Admin token generation is disabled. Please use Adobe IMS ACCESS_TOKEN.","Admin token generation is disabled. Please use Adobe IMS ACCESS_TOKEN." +"Identity Verification","Identity Verification" +"Verify Identity with Adobe IMS","Verify Identity with Adobe IMS" +"Confirm Identity","Confirm Identity" +"To apply changes you need to verify your Adobe identity.","To apply changes you need to verify your Adobe identity." +"Identity Verified with Adobe IMS","Identity Verified with Adobe IMS" +"Please perform the AdobeIms reAuth and try again.","Please perform the AdobeIms reAuth and try again." +"Use the same email user has in Adobe IMS organization.","Use the same email user has in Adobe IMS organization." +"The tokens couldn't be revoked.","The tokens couldn't be revoked." +"No matching admin user found for Adobe ID.","No matching admin user found for Adobe ID." +"This field is required to enable the Admin Adobe IMS Module","This field is required to enable the Admin Adobe IMS Module" +"No valid Organization ID provided","No valid Organization ID provided" +"No valid Client ID provided","No valid Client ID provided" +"No valid Client Secret provided","No valid Client Secret provided" +"2FA is required when enabling the Admin Adobe IMS Module","2FA is required when enabling the Admin Adobe IMS Module" +"Can't check user membership in organization.","Can't check user membership in organization." +"Could not check Organization Membership. Response is empty.","Could not check Organization Membership. Response is empty." +"User is not a member of configured Adobe Organization.","User is not a member of configured Adobe Organization." +"Organization Membership check can't be performed","Organization Membership check can't be performed" +"The ims token wasn't found.","The ims token wasn't found." +"Sign in to access the Adobe Commerce for your organization.","Sign in to access the Adobe Commerce for your organization." +"Sign In","Sign In" +"This Commerce instance is managed by an organization. Contact your organization administrator to request access.","This Commerce instance is managed by an organization. Contact your organization administrator to request access." +"Sign in with Adobe ID","Sign in with Adobe ID" +Footer,Footer +"User Guides","User Guides" +"Customer Support","Customer Support" +Forums,Forums +Header,Header +"%user_name, you now have access to Adobe Commerce","%user_name, you now have access to Adobe Commerce" +"Your administrator at %store_name has given you access to Adobe Commerce","Your administrator at %store_name has given you access to Adobe Commerce" +"Get started","Get started" +"Here are a few links to help you get up and running:","Here are a few links to help you get up and running:" +Documentation,Documentation +"Release notes","Release notes" +"If you have any questions about access to Adobe Commerce, contact your administrator or your Adobe account team for more information.","If you have any questions about access to Adobe Commerce, contact your administrator or your Adobe account team for more information." +"Enable Logging for Admin Adobe IMS Module","Enable Logging for Admin Adobe IMS Module" +"Adobe Commerce","Adobe Commerce" + diff --git a/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php b/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php index 15d4afef086cd..1d8421e74376a 100644 --- a/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php +++ b/app/code/Magento/AdminAnalytics/ViewModel/Metadata.php @@ -9,7 +9,9 @@ namespace Magento\AdminAnalytics\ViewModel; use Magento\Config\Model\Config\Backend\Admin\Custom; +use Magento\Csp\Helper\CspNonceProvider; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ProductMetadataInterface; use Magento\Backend\Model\Auth\Session; use Magento\Framework\App\State; @@ -21,6 +23,11 @@ */ class Metadata implements ArgumentInterface { + /** + * @var string + */ + private $nonce; + /** * @var State */ @@ -41,22 +48,33 @@ class Metadata implements ArgumentInterface */ private $config; + /** + * @var CspNonceProvider + */ + private $nonceProvider; + /** * @param ProductMetadataInterface $productMetadata * @param Session $authSession * @param State $appState * @param ScopeConfigInterface $config + * @param CspNonceProvider|null $nonceProvider */ public function __construct( ProductMetadataInterface $productMetadata, Session $authSession, State $appState, - ScopeConfigInterface $config + ScopeConfigInterface $config, + CspNonceProvider $nonceProvider = null ) { $this->productMetadata = $productMetadata; $this->authSession = $authSession; $this->appState = $appState; $this->config = $config; + + $this->nonceProvider = $nonceProvider ?: ObjectManager::getInstance()->get(CspNonceProvider::class); + + $this->nonce = $this->nonceProvider->generateNonce(); } /** @@ -156,4 +174,14 @@ public function getCurrentUserRoleName(): string { return $this->authSession->getUser()->getRole()->getRoleName(); } + + /** + * Get a random nonce for each request. + * + * @return string + */ + public function getNonce(): string + { + return $this->nonce; + } } diff --git a/app/code/Magento/AdminAnalytics/composer.json b/app/code/Magento/AdminAnalytics/composer.json index ef3829fd149c6..f786def43437b 100644 --- a/app/code/Magento/AdminAnalytics/composer.json +++ b/app/code/Magento/AdminAnalytics/composer.json @@ -1,23 +1,25 @@ { "name": "magento/module-admin-analytics", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4-p8", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-config": "*", - "magento/module-store": "*", - "magento/module-ui": "*", - "magento/module-release-notification": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-config": "101.2.*", + "magento/module-store": "101.1.*", + "magento/module-ui": "101.2.*", + "magento/module-release-notification": "100.4.*", + "magento/module-csp": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -27,3 +29,4 @@ } } } + diff --git a/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml b/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml index 4b155e1a5ae5b..0ccd4d1b26082 100644 --- a/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml +++ b/app/code/Magento/AdminAnalytics/view/adminhtml/templates/tracking.phtml @@ -6,6 +6,7 @@ /** * @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer + * @var \Magento\Framework\Escaper $escaper */ ?> @@ -22,18 +23,25 @@ getMetadata(); +$nonce = $escaper->escapeJs($metadata->getNonce()); $scriptString = ' var adminAnalyticsMetadata = { - "secure_base_url": "' . $block->escapeJs($metadata->getSecureBaseUrlForScope()) . '", - "version": "' . $block->escapeJs($metadata->getMagentoVersion()) . '", - "product_edition": "' . $block->escapeJs($metadata->getProductEdition()) . '", - "user": "' . $block->escapeJs($metadata->getCurrentUser()) . '", - "mode": "' . $block->escapeJs($metadata->getMode()) . '", - "store_name_default": "' . $block->escapeJs($metadata->getStoreNameForScope()) . '", - "admin_user_created": "' . $block->escapeJs($metadata->getCurrentUserCreatedDate()) . '", - "admin_user_logdate": "' . $block->escapeJs($metadata->getCurrentUserLogDate()) . '", - "admin_user_role_name": "' . $block->escapeJs($metadata->getCurrentUserRoleName()) . '" + "secure_base_url": "' . $escaper->escapeJs($metadata->getSecureBaseUrlForScope()) . '", + "version": "' . $escaper->escapeJs($metadata->getMagentoVersion()) . '", + "product_edition": "' . $escaper->escapeJs($metadata->getProductEdition()) . '", + "user": "' . $escaper->escapeJs($metadata->getCurrentUser()) . '", + "mode": "' . $escaper->escapeJs($metadata->getMode()) . '", + "store_name_default": "' . $escaper->escapeJs($metadata->getStoreNameForScope()) . '", + "admin_user_created": "' . $escaper->escapeJs($metadata->getCurrentUserCreatedDate()) . '", + "admin_user_logdate": "' . $escaper->escapeJs($metadata->getCurrentUserLogDate()) . '", + "admin_user_role_name": "' . $escaper->escapeJs($metadata->getCurrentUserRoleName()) . '" }; + + var digitalData = { + "nonce": "' . $nonce . '" + }; + + var cspNonce = "' . $nonce . '"; '; ?> renderTag('script', [], $scriptString, false); ?> diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index 28ca1f626a2cd..54c910c5d3459 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -1,24 +1,25 @@ { "name": "magento/module-admin-notification", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4", "require": { "php": "~7.4.0||~8.1.0", "lib-libxml": "*", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-media-storage": "*", - "magento/module-store": "*", - "magento/module-ui": "*", - "magento/module-config": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-media-storage": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-ui": "101.2.*", + "magento/module-config": "101.2.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -28,3 +29,4 @@ } } } + diff --git a/app/code/Magento/AdobeIms/Model/Config.php b/app/code/Magento/AdobeIms/Model/Config.php index ee94a5a18d2fc..b4f7816b30982 100644 --- a/app/code/Magento/AdobeIms/Model/Config.php +++ b/app/code/Magento/AdobeIms/Model/Config.php @@ -106,15 +106,15 @@ private function getLocale(): string /** * @inheritdoc */ - public function getLogoutUrl(string $accessToken, string $redirectUrl = '') : string + public function getLogoutUrl(string $redirectUrl = '') : string { // there is no success response with empty redirect url if ($redirectUrl === '') { $redirectUrl = 'self'; } return str_replace( - ['#{access_token}', '#{redirect_uri}'], - [$accessToken, $redirectUrl], + '#{redirect_uri}', + $redirectUrl, $this->scopeConfig->getValue(self::XML_PATH_LOGOUT_URL_PATTERN) ?? '' ); } diff --git a/app/code/Magento/AdobeIms/Model/LogOut.php b/app/code/Magento/AdobeIms/Model/LogOut.php index db90ccf88f0e8..dc795c2e53a19 100644 --- a/app/code/Magento/AdobeIms/Model/LogOut.php +++ b/app/code/Magento/AdobeIms/Model/LogOut.php @@ -103,7 +103,14 @@ private function externalLogOut(string $accessToken): void $curl = $this->curlFactory->create(); $curl->addHeader('Content-Type', 'application/x-www-form-urlencoded'); $curl->addHeader('cache-control', 'no-cache'); - $curl->get($this->config->getLogoutUrl($accessToken)); + $curl->post( + $this->config->getLogoutUrl(), + [ + 'access_token' => $accessToken, + 'client_secret' => $this->config->getPrivateKey(), + 'client_id' => $this->config->getApiKey() + ] + ); if ($curl->getStatus() !== self::HTTP_FOUND) { throw new LocalizedException( diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php index 821b77c57246c..dae336641312a 100644 --- a/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php +++ b/app/code/Magento/AdobeIms/Test/Unit/Model/ConfigTest.php @@ -56,9 +56,8 @@ class ConfigTest extends TestCase */ private const XML_PATH_LOGOUT_URL_PATTERN = 'adobe_ims/integration/logout_url'; private const LOGOUT_URL_PATTERN = 'https://logout-url.com/pattern' . - '?access_token=#{access_token}&redirect_uri=#{redirect_uri}'; + '?redirect_uri=#{redirect_uri}'; private const REDIRECT_URI = 'REDIRECT_URI'; - private const ACCCESS_TOKEN = 'ACCCESS_TOKEN'; /** * Profile image URL constants @@ -187,9 +186,8 @@ public function testGetLogoutUrl(): void ->willReturn(self::LOGOUT_URL_PATTERN); $this->assertEquals( - 'https://logout-url.com/pattern?access_token=' . self::ACCCESS_TOKEN . - '&redirect_uri=' . self::REDIRECT_URI, - $this->config->getLogoutUrl(self::ACCCESS_TOKEN, self::REDIRECT_URI) + 'https://logout-url.com/pattern?redirect_uri=' . self::REDIRECT_URI, + $this->config->getLogoutUrl(self::REDIRECT_URI) ); } diff --git a/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php b/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php index fa6c7c1bcb9f1..00de9f95b8629 100644 --- a/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php +++ b/app/code/Magento/AdobeIms/Test/Unit/Model/LogOutTest.php @@ -96,7 +96,7 @@ public function testExecute(): void ->method('addHeader') ->willReturn(null); $curl->expects($this->once()) - ->method('get') + ->method('post') ->willReturnSelf(); $curl->expects($this->once()) ->method('getStatus') @@ -125,7 +125,7 @@ public function testExecuteWithError(): void ->method('addHeader') ->willReturn(null); $curl->expects($this->once()) - ->method('get') + ->method('post') ->willReturnSelf(); $curl->expects($this->once()) ->method('getStatus') @@ -156,7 +156,7 @@ public function testExecuteWithException(): void ->method('addHeader') ->willReturn(null); $curl->expects($this->once()) - ->method('get') + ->method('post') ->willReturnSelf(); $curl->expects($this->once()) ->method('getStatus') diff --git a/app/code/Magento/AdobeIms/composer.json b/app/code/Magento/AdobeIms/composer.json index 872c29ffc97b4..30e70a7534e1c 100644 --- a/app/code/Magento/AdobeIms/composer.json +++ b/app/code/Magento/AdobeIms/composer.json @@ -1,20 +1,21 @@ { "name": "magento/module-adobe-ims", "description": "Magento module responsible for authentication to Adobe services", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-adobe-ims-api": "*", - "magento/module-authorization": "*", - "magento/module-backend": "*", - "magento/module-config": "*", - "magento/module-user": "*" - }, "type": "magento2-module", "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "2.1.4-p2", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/framework": "103.0.*", + "magento/module-adobe-ims-api": "2.1.*", + "magento/module-authorization": "100.4.*", + "magento/module-backend": "102.0.*", + "magento/module-config": "101.2.*", + "magento/module-user": "101.2.*" + }, "autoload": { "files": [ "registration.php" @@ -24,3 +25,4 @@ } } } + diff --git a/app/code/Magento/AdobeIms/etc/config.xml b/app/code/Magento/AdobeIms/etc/config.xml index b6cbbb24e351a..4d5e056e60eb9 100644 --- a/app/code/Magento/AdobeIms/etc/config.xml +++ b/app/code/Magento/AdobeIms/etc/config.xml @@ -12,7 +12,7 @@ https://ims-na1.adobelogin.com/ims/token - + diff --git a/app/code/Magento/AdobeImsApi/Api/ConfigInterface.php b/app/code/Magento/AdobeImsApi/Api/ConfigInterface.php index 93284fee8e066..38be023348feb 100644 --- a/app/code/Magento/AdobeImsApi/Api/ConfigInterface.php +++ b/app/code/Magento/AdobeImsApi/Api/ConfigInterface.php @@ -53,11 +53,10 @@ public function getCallBackUrl(): string; /** * Return logout url for AdobeSdk. * - * @param string $accessToken * @param string $redirectUrl * @return string */ - public function getLogoutUrl(string $accessToken, string $redirectUrl = ''): string; + public function getLogoutUrl(string $redirectUrl = ''): string; /** * Return image url for AdobeSdk. diff --git a/app/code/Magento/AdobeImsApi/composer.json b/app/code/Magento/AdobeImsApi/composer.json index 231f1ddfa1513..99c831a774e30 100644 --- a/app/code/Magento/AdobeImsApi/composer.json +++ b/app/code/Magento/AdobeImsApi/composer.json @@ -1,15 +1,16 @@ { "name": "magento/module-adobe-ims-api", "description": "Implementation of Magento module responsible for authentication to Adobe services", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/framework": "*" - }, "type": "magento2-module", "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "2.1.2-p2", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/framework": "103.0.*" + }, "autoload": { "files": [ "registration.php" @@ -19,3 +20,4 @@ } } } + diff --git a/app/code/Magento/AdvancedPricingImportExport/composer.json b/app/code/Magento/AdvancedPricingImportExport/composer.json index 59ea74cf4ddcb..2164413314854 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -1,26 +1,27 @@ { "name": "magento/module-advanced-pricing-import-export", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-catalog-import-export": "*", - "magento/module-catalog-inventory": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-import-export": "*", - "magento/module-store": "*", - "magento/module-directory": "*" + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-import-export": "101.1.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-import-export": "101.0.*", + "magento/module-store": "101.1.*", + "magento/module-directory": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -30,3 +31,4 @@ } } } + diff --git a/app/code/Magento/AdvancedSearch/composer.json b/app/code/Magento/AdvancedSearch/composer.json index 30205c5255cdd..7367a01eeba54 100644 --- a/app/code/Magento/AdvancedSearch/composer.json +++ b/app/code/Magento/AdvancedSearch/composer.json @@ -1,25 +1,26 @@ { "name": "magento/module-advanced-search", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.3", "require": { - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-catalog-search": "*", - "magento/module-config": "*", - "magento/module-customer": "*", - "magento/module-search": "*", - "magento/module-store": "*", + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-search": "102.0.*", + "magento/module-config": "101.2.*", + "magento/module-customer": "103.0.*", + "magento/module-search": "101.1.*", + "magento/module-store": "101.1.*", "php": "~7.4.0||~8.1.0" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -29,3 +30,4 @@ } } } + diff --git a/app/code/Magento/Amqp/composer.json b/app/code/Magento/Amqp/composer.json index c7d8d49fb0003..6f2b360634d3e 100644 --- a/app/code/Magento/Amqp/composer.json +++ b/app/code/Magento/Amqp/composer.json @@ -1,20 +1,21 @@ { "name": "magento/module-amqp", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.2", "require": { - "magento/framework": "*", - "magento/framework-amqp": "*", - "magento/framework-message-queue": "*", + "magento/framework": "103.0.*", + "magento/framework-amqp": "100.4.*", + "magento/framework-message-queue": "100.4.*", "php": "~7.4.0||~8.1.0" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -24,3 +25,4 @@ } } } + diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml index ac4fca843a36b..3f0bedd344d24 100644 --- a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml +++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml @@ -15,6 +15,6 @@ - + diff --git a/app/code/Magento/Analytics/composer.json b/app/code/Magento/Analytics/composer.json index 9bf08b4b068ca..9e68a12671d2f 100644 --- a/app/code/Magento/Analytics/composer.json +++ b/app/code/Magento/Analytics/composer.json @@ -1,19 +1,20 @@ { "name": "magento/module-analytics", "description": "N/A", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/module-backend": "*", - "magento/module-config": "*", - "magento/module-integration": "*", - "magento/module-store": "*", - "magento/framework": "*" - }, "type": "magento2-module", "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.5-p3", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/module-backend": "102.0.*", + "magento/module-config": "101.2.*", + "magento/module-integration": "100.4.*", + "magento/module-store": "101.1.*", + "magento/framework": "103.0.*" + }, "autoload": { "files": [ "registration.php" @@ -23,3 +24,4 @@ } } } + diff --git a/app/code/Magento/AsynchronousOperations/composer.json b/app/code/Magento/AsynchronousOperations/composer.json index b09ca94052e87..5d39b99918ecb 100644 --- a/app/code/Magento/AsynchronousOperations/composer.json +++ b/app/code/Magento/AsynchronousOperations/composer.json @@ -1,27 +1,28 @@ { "name": "magento/module-asynchronous-operations", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { - "magento/framework": "*", - "magento/framework-message-queue": "*", - "magento/framework-bulk": "*", - "magento/module-authorization": "*", - "magento/module-backend": "*", - "magento/module-ui": "*", + "magento/framework": "103.0.*", + "magento/framework-message-queue": "100.4.*", + "magento/framework-bulk": "101.0.*", + "magento/module-authorization": "100.4.*", + "magento/module-backend": "102.0.*", + "magento/module-ui": "101.2.*", "php": "~7.4.0||~8.1.0" }, "suggest": { - "magento/module-admin-notification": "*", + "magento/module-admin-notification": "100.4.*", "magento/module-logging": "*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -31,3 +32,4 @@ } } } + diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index d122e8b29b46e..34392ac507373 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -1,19 +1,20 @@ { "name": "magento/module-authorization", "description": "Authorization module provides access to Magento ACL functionality.", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -23,3 +24,4 @@ } } } + diff --git a/app/code/Magento/AwsS3/composer.json b/app/code/Magento/AwsS3/composer.json index 19078b9ee7b77..c74db49a09dfe 100644 --- a/app/code/Magento/AwsS3/composer.json +++ b/app/code/Magento/AwsS3/composer.json @@ -1,18 +1,19 @@ { "name": "magento/module-aws-s3", "description": "N/A", + "type": "magento2-module", + "license": [ + "proprietary" + ], "config": { "sort-packages": true }, + "version": "100.4.3", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-remote-storage": "*" + "magento/framework": "103.0.*", + "magento/module-remote-storage": "100.4.*" }, - "type": "magento2-module", - "license": [ - "proprietary" - ], "autoload": { "files": [ "registration.php" @@ -22,3 +23,4 @@ } } } + diff --git a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php index 3d7154eb20f92..11cca3717ba20 100644 --- a/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php +++ b/app/code/Magento/Backend/Block/System/Store/Grid/Render/Group.php @@ -5,6 +5,8 @@ */ namespace Magento\Backend\Block\System\Store\Grid\Render; +use Magento\Framework\DataObject; + /** * Store render group * @@ -13,9 +15,9 @@ class Group extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** - * {@inheritdoc} + * @inheritDoc */ - public function render(\Magento\Framework\DataObject $row) + public function render(DataObject $row) { if (!$row->getData($this->getColumn()->getIndex())) { return null; @@ -28,6 +30,6 @@ public function render(\Magento\Framework\DataObject $row) '">' . $this->escapeHtml($row->getData($this->getColumn()->getIndex())) . '
' - . '(' . __('Code') . ': ' . $row->getGroupCode() . ')'; + . '(' . __('Code') . ': ' . $this->escapeHtml($row->getGroupCode()) . ')'; } } diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index 65aa05fe71e56..ce49fdd516324 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -1,38 +1,39 @@ { "name": "magento/module-backend", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "102.0.5-p8", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backup": "*", - "magento/module-catalog": "*", - "magento/module-config": "*", - "magento/module-cms": "*", - "magento/module-customer": "*", - "magento/module-developer": "*", - "magento/module-directory": "*", - "magento/module-eav": "*", - "magento/module-quote": "*", - "magento/module-reports": "*", - "magento/module-require-js": "*", - "magento/module-sales": "*", - "magento/module-security": "*", - "magento/module-store": "*", - "magento/module-translation": "*", - "magento/module-ui": "*", - "magento/module-user": "*" + "magento/framework": "103.0.*", + "magento/module-backup": "100.4.*", + "magento/module-catalog": "104.0.*", + "magento/module-config": "101.2.*", + "magento/module-cms": "104.0.*", + "magento/module-customer": "103.0.*", + "magento/module-developer": "100.4.*", + "magento/module-directory": "100.4.*", + "magento/module-eav": "102.1.*", + "magento/module-quote": "101.2.*", + "magento/module-reports": "100.4.*", + "magento/module-require-js": "100.4.*", + "magento/module-sales": "103.0.*", + "magento/module-security": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-translation": "100.4.*", + "magento/module-ui": "101.2.*", + "magento/module-user": "101.2.*" }, "suggest": { - "magento/module-theme": "*" + "magento/module-theme": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php", @@ -43,3 +44,4 @@ } } } + diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 57cc36da95cfe..04a52530e52c8 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -10,6 +10,9 @@ + + + diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index e7437a3077aa7..0a7053ffa6a0c 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -1,21 +1,22 @@ { "name": "magento/module-backup", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-cron": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-cron": "100.4.*", + "magento/module-store": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -25,3 +26,4 @@ } } } + diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml index 7038ad90b81b9..45e26cf327dc9 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml @@ -17,11 +17,15 @@ + + + + diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index 47be75a42c254..c950f30d20f21 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -1,39 +1,40 @@ { "name": "magento/module-bundle", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "101.0.5-p6", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-catalog-inventory": "*", - "magento/module-catalog-rule": "*", - "magento/module-checkout": "*", - "magento/module-config": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-gift-message": "*", - "magento/module-media-storage": "*", - "magento/module-quote": "*", - "magento/module-sales": "*", - "magento/module-store": "*", - "magento/module-tax": "*", - "magento/module-ui": "*", - "magento/module-directory": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-catalog-rule": "101.2.*", + "magento/module-checkout": "100.4.*", + "magento/module-config": "101.2.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-gift-message": "100.4.*", + "magento/module-media-storage": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-sales": "103.0.*", + "magento/module-store": "101.1.*", + "magento/module-tax": "100.4.*", + "magento/module-ui": "101.2.*", + "magento/module-directory": "100.4.*" }, "suggest": { - "magento/module-webapi": "*", - "magento/module-bundle-sample-data": "*", - "magento/module-sales-rule": "*" + "magento/module-webapi": "100.4.*", + "magento/module-bundle-sample-data": "Sample Data version: 100.4.*", + "magento/module-sales-rule": "101.2.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -43,3 +44,4 @@ } } } + diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml index 706b28049470e..5f3e219866ba6 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ use Magento\Bundle\ViewModel\ValidateQuantity; + +// phpcs:disable Generic.Files.LineLength.TooLong ?> getOption(); ?> @@ -20,42 +22,45 @@ $viewModel = $block->getData('validateQuantityViewModel');
- showSingle()) : ?> + showSingle()): ?> getSelectionTitlePrice($_selections[0]) ?> getTierPriceRenderer()->renderTierPrice($_selections[0]) ?> - - getRequired()) : ?> + + getRequired()): ?>
isSalable())?'':' checked="checked" ' ?> + isSalable())?'':' checked="checked" ' ?> value=""/>
- +
getRequired()) { echo 'data-validate="{\'validate-one-required-by-name\':true}"'; }?> + getRequired()) { + echo 'data-validate="{\'validate-one-required-by-name\':true}"'; + } ?> name="bundle_option[escapeHtmlAttr($_option->getId()) ?>]" data-selector="bundle_option[escapeHtmlAttr($_option->getId()) ?>]" - isSelected($_selection)) { echo ' checked="checked"'; } ?> - isSaleable()) { echo ' disabled="disabled"'; } ?> - value="escapeHtmlAttr($_selection->getSelectionId()) ?>"/> + isSelected($_selection)) { echo ' checked="checked"'; } ?> + isSaleable()) { echo ' disabled="disabled"'; } ?> + value="escapeHtmlAttr($_selection->getSelectionId()) ?>" + data-errors-message-box="#validation-message-box-radio"/>
+
- id="bundle-option-escapeHtmlAttr($_option->getId()) ?>-qty-input" - class="input-text qty" - type="number" - min="0" - data-validate="escapeHtmlAttr($viewModel->getQuantityValidators()) ?>" - name="bundle_option_qty[escapeHtmlAttr($_option->getId()) ?>]" - data-selector="bundle_option_qty[escapeHtmlAttr($_option->getId()) ?>]" - value="escapeHtmlAttr($_defaultQty) ?>"/> + id="bundle-option-escapeHtmlAttr($_option->getId()) ?>-qty-input" + class="input-text qty" + type="number" + min="0" + data-validate="escapeHtmlAttr($viewModel->getQuantityValidators()) ?>" + name="bundle_option_qty[escapeHtmlAttr($_option->getId()) ?>]" + data-selector="bundle_option_qty[escapeHtmlAttr($_option->getId()) ?>]" + value="escapeHtmlAttr($_defaultQty) ?>"/>
diff --git a/app/code/Magento/BundleGraphQl/composer.json b/app/code/Magento/BundleGraphQl/composer.json index 70a619cbf6837..06dc5f4a96ba0 100644 --- a/app/code/Magento/BundleGraphQl/composer.json +++ b/app/code/Magento/BundleGraphQl/composer.json @@ -2,23 +2,24 @@ "name": "magento/module-bundle-graph-ql", "description": "N/A", "type": "magento2-module", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/module-catalog": "*", - "magento/module-bundle": "*", - "magento/module-graph-ql": "*", - "magento/module-catalog-graph-ql": "*", - "magento/module-quote": "*", - "magento/module-quote-graph-ql": "*", - "magento/module-store": "*", - "magento/module-sales": "*", - "magento/module-sales-graph-ql": "*", - "magento/framework": "*" - }, "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.5", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/module-catalog": "104.0.*", + "magento/module-bundle": "101.0.*", + "magento/module-graph-ql": "100.4.*", + "magento/module-catalog-graph-ql": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-quote-graph-ql": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-sales": "103.0.*", + "magento/module-sales-graph-ql": "100.4.*", + "magento/framework": "103.0.*" + }, "autoload": { "files": [ "registration.php" @@ -28,3 +29,4 @@ } } } + diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index ff7d0acc7c48d..dc9786e65003d 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -1,24 +1,25 @@ { "name": "magento/module-bundle-import-export", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-bundle": "*", - "magento/module-store": "*", - "magento/module-catalog": "*", - "magento/module-catalog-import-export": "*", - "magento/module-eav": "*", - "magento/module-import-export": "*" + "magento/framework": "103.0.*", + "magento/module-bundle": "101.0.*", + "magento/module-store": "101.1.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-import-export": "101.1.*", + "magento/module-eav": "102.1.*", + "magento/module-import-export": "101.0.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -28,3 +29,4 @@ } } } + diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index c756a5fe602e9..bc179e2710bf3 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -1,19 +1,20 @@ { "name": "magento/module-cache-invalidate", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.3", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-page-cache": "*" + "magento/framework": "103.0.*", + "magento/module-page-cache": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -23,3 +24,4 @@ } } } + diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnOnepageCheckoutPyamentTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnOnepageCheckoutPyamentTest.xml index 912e637dc534e..4ab4ec7f055f9 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnOnepageCheckoutPyamentTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnOnepageCheckoutPyamentTest.xml @@ -21,6 +21,7 @@ + 20 @@ -62,6 +63,7 @@ + diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index d4b94dbb586c2..6ed2c6a4a8ba0 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -1,26 +1,27 @@ { "name": "magento/module-captcha", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5-p3", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-checkout": "*", - "magento/module-customer": "*", - "magento/module-sales": "*", - "magento/module-store": "*", - "magento/module-authorization": "*", + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-checkout": "100.4.*", + "magento/module-customer": "103.0.*", + "magento/module-sales": "103.0.*", + "magento/module-store": "101.1.*", + "magento/module-authorization": "100.4.*", "laminas/laminas-captcha": "^2.12", "laminas/laminas-db": "^2.13.4" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -30,3 +31,4 @@ } } } + diff --git a/app/code/Magento/CardinalCommerce/composer.json b/app/code/Magento/CardinalCommerce/composer.json index 4c49c92cec1ea..d531c7f4634f1 100644 --- a/app/code/Magento/CardinalCommerce/composer.json +++ b/app/code/Magento/CardinalCommerce/composer.json @@ -1,21 +1,22 @@ { "name": "magento/module-cardinal-commerce", "description": "Provides a possibility to enable 3-D Secure 2.0 support for payment methods.", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.3", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-checkout": "*", - "magento/module-payment": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-checkout": "100.4.*", + "magento/module-payment": "100.4.*", + "magento/module-store": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -25,3 +26,4 @@ } } } + diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php index 0b1ef98c386c4..ea14dbc1ce627 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php @@ -4,18 +4,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Controller\Adminhtml\Product; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; -use Magento\Backend\App\Action; use Magento\Catalog\Controller\Adminhtml\Product; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\RegexValidator; class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface { /** * @var Initialization\StockDataFilter * @deprecated 101.0.0 + * @see Initialization\StockDataFilter */ protected $stockFilter; @@ -30,23 +33,32 @@ class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product implements protected $resultForwardFactory; /** - * @param Action\Context $context + * @var RegexValidator + */ + private RegexValidator $regexValidator; + + /** + * @param Context $context * @param Builder $productBuilder * @param Initialization\StockDataFilter $stockFilter * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + * @param RegexValidator|null $regexValidator */ public function __construct( \Magento\Backend\App\Action\Context $context, Product\Builder $productBuilder, Initialization\StockDataFilter $stockFilter, \Magento\Framework\View\Result\PageFactory $resultPageFactory, - \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory, + RegexValidator $regexValidator = null ) { $this->stockFilter = $stockFilter; parent::__construct($context, $productBuilder); $this->resultPageFactory = $resultPageFactory; $this->resultForwardFactory = $resultForwardFactory; + $this->regexValidator = $regexValidator + ?: ObjectManager::getInstance()->get(RegexValidator::class); } /** @@ -56,6 +68,11 @@ public function __construct( */ public function execute() { + $typeId = $this->getRequest()->getParam('type'); + if (!$this->regexValidator->validateParamRegex($typeId)) { + return $this->resultForwardFactory->create()->forward('noroute'); + } + if (!$this->getRequest()->getParam('set')) { return $this->resultForwardFactory->create()->forward('noroute'); } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php index 4c4daccb90174..8afbdf7425ca2 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php @@ -49,6 +49,7 @@ class ValidatorInfo extends Validator * @var IoFile */ private $ioFile; + /** * @var NotProtectedExtension */ @@ -147,12 +148,14 @@ private function validatePath(array $optionValuePath): bool { foreach ([$optionValuePath['quote_path'], $optionValuePath['order_path']] as $path) { $pathInfo = $this->ioFile->getPathInfo($path); - if (isset($pathInfo['extension'])) { - if (!$this->fileValidator->isValid($pathInfo['extension'])) { - return false; - } + + if (isset($pathInfo['extension']) + && (empty($pathInfo['extension']) || !$this->fileValidator->isValid($pathInfo['extension'])) + ) { + return false; } } + return true; } diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductWithTextOptionCharLimitActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductWithTextOptionCharLimitActionGroup.xml index f27a08eb3e0b2..f1b20569700bf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductWithTextOptionCharLimitActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateSimpleProductWithTextOptionCharLimitActionGroup.xml @@ -29,6 +29,7 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml index 447fb186d3a7c..b398300b8d29b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml @@ -30,5 +30,7 @@ + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml index f3a0919f6c728..cf419c8f27c00 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml @@ -40,5 +40,6 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml index 97db161e6f137..4b40f04f098e0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml @@ -32,7 +32,7 @@ - + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml index 3d3c7f198d808..0dec58de66c3c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRestrictedUserAddCategoryFromProductPageTest.xml @@ -97,6 +97,7 @@ + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml index f32ba620732fc..9d983776553f0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml @@ -17,6 +17,7 @@ + @@ -34,7 +35,9 @@ - + + + @@ -44,7 +47,9 @@ - + + + @@ -65,33 +70,37 @@ - - + + + + + + - + - - + + @@ -100,6 +109,7 @@ + diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php old mode 100644 new mode 100755 index 974c85b2b5c98..cad43f39f0261 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/NewActionTest.php @@ -16,6 +16,9 @@ use Magento\Catalog\Controller\Adminhtml\Product\NewAction; use Magento\Catalog\Model\Product; use Magento\Catalog\Test\Unit\Controller\Adminhtml\ProductTest; +use Magento\Framework\RegexValidator; +use Magento\Framework\Validator\Regex; +use Magento\Framework\Validator\RegexFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Result\PageFactory; use PHPUnit\Framework\MockObject\MockObject; @@ -42,6 +45,26 @@ class NewActionTest extends ProductTest */ protected $initializationHelper; + /** + * @var RegexValidator|MockObject + */ + private $regexValidator; + + /** + * @var RegexFactory + */ + private $regexValidatorFactoryMock; + + /** + * @var Regex|MockObject + */ + private $regexValidatorMock; + + /** + * @var ForwardFactory&MockObject|MockObject + */ + private $resultForwardFactory; + protected function setUp(): void { $this->productBuilder = $this->createPartialMock( @@ -63,37 +86,78 @@ protected function setUp(): void ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $resultPageFactory->expects($this->atLeastOnce()) - ->method('create') - ->willReturn($this->resultPage); $this->resultForward = $this->getMockBuilder(Forward::class) ->disableOriginalConstructor() ->getMock(); - $resultForwardFactory = $this->getMockBuilder(ForwardFactory::class) + $this->resultForwardFactory = $this->getMockBuilder(ForwardFactory::class) + ->disableOriginalConstructor() + ->onlyMethods(['create']) + ->getMock(); + + $this->regexValidatorFactoryMock = $this->getMockBuilder(RegexFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $resultForwardFactory->expects($this->any()) - ->method('create') - ->willReturn($this->resultForward); + $this->regexValidatorMock = $this->createMock(Regex::class); + $this->regexValidatorFactoryMock->method('create') + ->willReturn($this->regexValidatorMock); + $this->regexValidator = new regexValidator($this->regexValidatorFactoryMock); $this->action = (new ObjectManager($this))->getObject( NewAction::class, [ 'context' => $this->initContext(), 'productBuilder' => $this->productBuilder, 'resultPageFactory' => $resultPageFactory, - 'resultForwardFactory' => $resultForwardFactory, + 'resultForwardFactory' => $this->resultForwardFactory, + 'regexValidator' => $this->regexValidator, ] ); } - public function testExecute() + /** + * Test execute method input validation. + * + * @param string $value + * @param bool $exceptionThrown + * @dataProvider validationCases + */ + public function testExecute(string $value, bool $exceptionThrown): void + { + if ($exceptionThrown) { + $this->action->getRequest()->expects($this->any()) + ->method('getParam') + ->willReturn($value); + $this->resultForwardFactory->expects($this->any()) + ->method('create') + ->willReturn($this->resultForward); + $this->resultForward->expects($this->once()) + ->method('forward') + ->with('noroute') + ->willReturn(true); + $this->assertTrue($this->action->execute()); + } else { + $this->action->getRequest()->expects($this->any())->method('getParam')->willReturn($value); + $this->regexValidatorMock->expects($this->any()) + ->method('isValid') + ->with($value) + ->willReturn(true); + + $this->assertEquals(true, $this->regexValidator->validateParamRegex($value)); + } + } + + /** + * Validation cases. + * + * @return array + */ + public function validationCases(): array { - $this->action->getRequest()->expects($this->any())->method('getParam')->willReturn(true); - $this->action->getRequest()->expects($this->any())->method('getFullActionName') - ->willReturn('catalog_product_new'); - $this->action->execute(); + return [ + 'execute-with-exception' => ['simple\' and true()]|*[self%3a%3ahandle%20or%20self%3a%3alayout',true], + 'execute-without-exception' => ['catalog_product_new',false] + ]; } } diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index f3831e50ef3d9..a97c75d32da15 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -15,8 +15,11 @@ use Magento\Catalog\Pricing\Render\FinalPriceBox; use Magento\Framework\App\Cache\StateInterface; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\State; +use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\Event\Test\Unit\ManagerStub; +use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Pricing\Amount\AmountInterface; use Magento\Framework\Pricing\Price\PriceInterface; use Magento\Framework\Pricing\PriceInfoInterface; @@ -96,11 +99,27 @@ class FinalPriceBoxTest extends TestCase */ private $minimalPriceCalculator; + /** + * @var DeploymentConfig|MockObject + */ + private $deploymentConfig; + + /** + * @var ObjectManagerInterface|MockObject + */ + private $objectManagerMock; + /** * @inheritDoc + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function setUp(): void { + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['get']) + ->getMockForAbstractClass(); + \Magento\Framework\App\ObjectManager::setInstance($this->objectManagerMock); $this->product = $this->getMockBuilder(Product::class) ->addMethods(['getCanShowPrice']) ->onlyMethods(['getPriceInfo', 'isSalable', 'getId']) @@ -183,6 +202,11 @@ protected function setUp(): void ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->deploymentConfig = $this->createPartialMock( + DeploymentConfig::class, + ['get'] + ); + $this->minimalPriceCalculator = $this->getMockForAbstractClass(MinimalPriceCalculatorInterface::class); $this->object = $objectManager->getObject( FinalPriceBox::class, @@ -455,6 +479,15 @@ public function testHidePrice(): void */ public function testGetCacheKey(): void { + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->with(DeploymentConfig::class) + ->willReturn($this->deploymentConfig); + + $this->deploymentConfig->expects($this->any()) + ->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY) + ->willReturn('448198e08af35844a42d3c93c1ef4e03'); $result = $this->object->getCacheKey(); $this->assertStringEndsWith('list-category-page', $result); } diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 6597e88e9d995..b428b5d7ec27c 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -1,48 +1,49 @@ { "name": "magento/module-catalog", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "104.0.5-p9", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-authorization": "*", - "magento/module-asynchronous-operations": "*", - "magento/module-backend": "*", - "magento/module-catalog-inventory": "*", - "magento/module-catalog-rule": "*", - "magento/module-catalog-url-rewrite": "*", - "magento/module-checkout": "*", - "magento/module-cms": "*", - "magento/module-config": "*", - "magento/module-customer": "*", - "magento/module-directory": "*", - "magento/module-eav": "*", - "magento/module-indexer": "*", - "magento/module-media-storage": "*", - "magento/module-msrp": "*", - "magento/module-page-cache": "*", - "magento/module-product-alert": "*", - "magento/module-quote": "*", - "magento/module-store": "*", - "magento/module-tax": "*", - "magento/module-theme": "*", - "magento/module-ui": "*", - "magento/module-url-rewrite": "*", - "magento/module-widget": "*", - "magento/module-wishlist": "*" + "magento/framework": "103.0.*", + "magento/module-authorization": "100.4.*", + "magento/module-asynchronous-operations": "100.4.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-catalog-rule": "101.2.*", + "magento/module-catalog-url-rewrite": "100.4.*", + "magento/module-checkout": "100.4.*", + "magento/module-cms": "104.0.*", + "magento/module-config": "101.2.*", + "magento/module-customer": "103.0.*", + "magento/module-directory": "100.4.*", + "magento/module-eav": "102.1.*", + "magento/module-indexer": "100.4.*", + "magento/module-media-storage": "100.4.*", + "magento/module-msrp": "100.4.*", + "magento/module-page-cache": "100.4.*", + "magento/module-product-alert": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-store": "101.1.*", + "magento/module-tax": "100.4.*", + "magento/module-theme": "101.1.*", + "magento/module-ui": "101.2.*", + "magento/module-url-rewrite": "102.0.*", + "magento/module-widget": "101.2.*", + "magento/module-wishlist": "101.2.*" }, "suggest": { - "magento/module-cookie": "*", - "magento/module-sales": "*", - "magento/module-catalog-sample-data": "*" + "magento/module-cookie": "100.4.*", + "magento/module-sales": "103.0.*", + "magento/module-catalog-sample-data": "Sample Data version: 100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -52,3 +53,4 @@ } } } + diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv index d5ba3b75493eb..d09e8a4aa62a3 100644 --- a/app/code/Magento/Catalog/i18n/en_US.csv +++ b/app/code/Magento/Catalog/i18n/en_US.csv @@ -816,4 +816,5 @@ Details,Details "Are you sure you want to delete this category?","Are you sure you want to delete this category?" "Attribute Set Information","Attribute Set Information" "Failed to retrieve product links for ""%1""","Failed to retrieve product links for ""%1""" +"The url has invalid characters. Please correct and try again.","The url has invalid characters. Please correct and try again." diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js index d292bd126593c..b4d4ed12d20ba 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-tree.js @@ -5,10 +5,9 @@ define([ 'jquery', - 'mageUtils', 'jquery/ui', 'jquery/jstree/jquery.jstree' -], function ($, utils) { +], function ($) { 'use strict'; $.widget('mage.categoryTree', { @@ -87,7 +86,7 @@ define([ // jscs:disable requireCamelCaseOrUpperCaseIdentifiers result = { id: node.id, - text: utils.unescape(node.name) + ' (' + node.product_count + ')', + text: node.name + ' (' + node.product_count + ')', li_attr: { class: node.cls + (!!node.disabled ? ' disabled' : '') //eslint-disable-line no-extra-boolean-cast }, diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json index a41a47fa4764b..2fb9f4fb021cf 100644 --- a/app/code/Magento/CatalogAnalytics/composer.json +++ b/app/code/Magento/CatalogAnalytics/composer.json @@ -1,17 +1,18 @@ { "name": "magento/module-catalog-analytics", "description": "N/A", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-analytics": "*" - }, "type": "magento2-module", "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.2", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-analytics": "100.4.*" + }, "autoload": { "files": [ "registration.php" @@ -21,3 +22,4 @@ } } } + diff --git a/app/code/Magento/CatalogCmsGraphQl/composer.json b/app/code/Magento/CatalogCmsGraphQl/composer.json index cf9e76f3b2ea2..b32b4635745c3 100644 --- a/app/code/Magento/CatalogCmsGraphQl/composer.json +++ b/app/code/Magento/CatalogCmsGraphQl/composer.json @@ -2,21 +2,22 @@ "name": "magento/module-catalog-cms-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.1", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-cms-graph-ql": "*" + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-cms-graph-ql": "100.4.*" }, "suggest": { - "magento/module-graph-ql": "*", - "magento/module-cms": "*", - "magento/module-catalog-graph-ql": "*" + "magento/module-graph-ql": "100.4.*", + "magento/module-cms": "104.0.*", + "magento/module-catalog-graph-ql": "100.4.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/CatalogCustomerGraphQl/composer.json b/app/code/Magento/CatalogCustomerGraphQl/composer.json index b1743ae964966..c7062a2d03e6a 100644 --- a/app/code/Magento/CatalogCustomerGraphQl/composer.json +++ b/app/code/Magento/CatalogCustomerGraphQl/composer.json @@ -2,17 +2,18 @@ "name": "magento/module-catalog-customer-graph-ql", "description": "N/A", "type": "magento2-module", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-customer": "*", - "magento/module-catalog-graph-ql": "*" - }, "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.4", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-customer": "103.0.*", + "magento/module-catalog-graph-ql": "100.4.*" + }, "autoload": { "files": [ "registration.php" @@ -22,3 +23,4 @@ } } } + diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json index c289f84a359ba..d7f4f0c44f901 100644 --- a/app/code/Magento/CatalogGraphQl/composer.json +++ b/app/code/Magento/CatalogGraphQl/composer.json @@ -2,28 +2,29 @@ "name": "magento/module-catalog-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/module-eav": "*", - "magento/module-catalog": "*", - "magento/module-catalog-inventory": "*", - "magento/module-directory": "*", - "magento/module-search": "*", - "magento/module-store": "*", - "magento/module-eav-graph-ql": "*", - "magento/module-catalog-search": "*", - "magento/framework": "*", - "magento/module-graph-ql": "*", - "magento/module-advanced-search": "*" + "magento/module-eav": "102.1.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-directory": "100.4.*", + "magento/module-search": "101.1.*", + "magento/module-store": "101.1.*", + "magento/module-eav-graph-ql": "100.4.*", + "magento/module-catalog-search": "102.0.*", + "magento/framework": "103.0.*", + "magento/module-graph-ql": "100.4.*", + "magento/module-advanced-search": "100.4.*" }, "suggest": { - "magento/module-graph-ql-cache": "*", - "magento/module-store-graph-ql": "*" + "magento/module-graph-ql-cache": "100.4.*", + "magento/module-store-graph-ql": "100.4.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -33,3 +34,4 @@ } } } + diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index ac18211b44bd4..6d66d58af55d1 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -47,6 +47,7 @@ */ class Product extends AbstractEntity { + private const COL_NAME_FORMAT = '/[\x00-\x1F\x7F]/'; private const DEFAULT_GLOBAL_MULTIPLE_VALUE_SEPARATOR = ','; public const CONFIG_KEY_PRODUCT_TYPES = 'global/importexport/import_product_types'; private const HASH_ALGORITHM = 'sha256'; @@ -227,6 +228,7 @@ class Product extends AbstractEntity * Links attribute name-to-link type ID. * * @deprecated 101.1.0 use DI for LinkProcessor class if you want to add additional types + * @see LinkProcessor::fetchProductLinks * * @var array */ @@ -548,6 +550,7 @@ class Product extends AbstractEntity /** * @var \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory * @deprecated 101.0.0 this variable isn't used anymore. + * @see 101.0.0 */ protected $_stockResItemFac; @@ -612,6 +615,7 @@ class Product extends AbstractEntity /** * @var array * @deprecated 100.0.3 + * @see 100.0.3 * @since 100.0.3 */ protected $productUrlKeys = []; @@ -1280,6 +1284,7 @@ protected function _prepareRowForDb(array $rowData) * Must be called after ALL products saving done. * * @deprecated 101.1.0 use linkProcessor Directly + * @see LinkProcessor::saveLinks * * @return $this */ @@ -1489,6 +1494,7 @@ private function getNewSkuFieldsForSelect() * @return void * @since 100.0.4 * @deprecated 100.2.3 + * @see 100.2.3 */ protected function initMediaGalleryResources() { @@ -1611,6 +1617,11 @@ protected function _saveProducts() $bunch[$rowNum][self::URL_KEY] = $rowData[self::URL_KEY] = $urlKey; } + if (!empty($rowData[self::COL_NAME])) { + // remove null byte character + $rowData[self::COL_NAME] = preg_replace(self::COL_NAME_FORMAT, '', $rowData[self::COL_NAME]); + } + $rowSku = $rowData[self::COL_SKU]; $rowSkuNormalized = mb_strtolower($rowSku); diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json index dac8624086df0..4bc36f87d251d 100644 --- a/app/code/Magento/CatalogImportExport/composer.json +++ b/app/code/Magento/CatalogImportExport/composer.json @@ -1,29 +1,30 @@ { "name": "magento/module-catalog-import-export", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "101.1.5-p3", "require": { "php": "~7.4.0||~8.1.0", "ext-ctype": "*", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-catalog-inventory": "*", - "magento/module-catalog-url-rewrite": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-import-export": "*", - "magento/module-media-storage": "*", - "magento/module-store": "*", - "magento/module-tax": "*", - "magento/module-authorization": "*" + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-catalog-url-rewrite": "100.4.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-import-export": "101.0.*", + "magento/module-media-storage": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-tax": "100.4.*", + "magento/module-authorization": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -33,3 +34,4 @@ } } } + diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index 893de329628fa..778e1e61142c1 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -1,25 +1,26 @@ { "name": "magento/module-catalog-inventory", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-config": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-quote": "*", - "magento/module-store": "*", - "magento/module-ui": "*" + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-config": "101.2.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-quote": "101.2.*", + "magento/module-store": "101.1.*", + "magento/module-ui": "101.2.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -30,3 +31,4 @@ }, "abandoned": "magento/inventory-metapackage" } + diff --git a/app/code/Magento/CatalogInventoryGraphQl/composer.json b/app/code/Magento/CatalogInventoryGraphQl/composer.json index 38685524d5346..b585dba99e69e 100644 --- a/app/code/Magento/CatalogInventoryGraphQl/composer.json +++ b/app/code/Magento/CatalogInventoryGraphQl/composer.json @@ -2,18 +2,19 @@ "name": "magento/module-catalog-inventory-graph-ql", "description": "N/A", "type": "magento2-module", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-catalog": "*", - "magento/module-catalog-inventory": "*", - "magento/module-graph-ql": "*" - }, "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.2", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/framework": "103.0.*", + "magento/module-store": "101.1.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-graph-ql": "100.4.*" + }, "autoload": { "files": [ "registration.php" @@ -23,3 +24,4 @@ } } } + diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml index c6452612f82a4..baef5aaf9e494 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml @@ -27,6 +27,8 @@ + + diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index 531a12ac017ed..7ff6a232b7c44 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -1,29 +1,30 @@ { "name": "magento/module-catalog-rule", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "101.2.5-p5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-rule": "*", - "magento/module-store": "*", - "magento/module-ui": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-rule": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-ui": "101.2.*" }, "suggest": { - "magento/module-import-export": "*", - "magento/module-catalog-rule-sample-data": "*" + "magento/module-import-export": "101.0.*", + "magento/module-catalog-rule-sample-data": "Sample Data version: 100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -33,3 +34,4 @@ } } } + diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json index 68da972ae94f9..782229dd6daa3 100644 --- a/app/code/Magento/CatalogRuleConfigurable/composer.json +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -1,25 +1,26 @@ { "name": "magento/module-catalog-rule-configurable", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", + "magento/framework": "103.0.*", "magento/magento-composer-installer": "*", - "magento/module-catalog": "*", - "magento/module-catalog-rule": "*", - "magento/module-configurable-product": "*" + "magento/module-catalog": "104.0.*", + "magento/module-catalog-rule": "101.2.*", + "magento/module-configurable-product": "100.4.*" }, "suggest": { - "magento/module-catalog-rule": "*" + "magento/module-catalog-rule": "101.2.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -29,3 +30,4 @@ } } } + diff --git a/app/code/Magento/CatalogRuleGraphQl/composer.json b/app/code/Magento/CatalogRuleGraphQl/composer.json index 2c8c3ef20c96a..6bd7ef191d895 100644 --- a/app/code/Magento/CatalogRuleGraphQl/composer.json +++ b/app/code/Magento/CatalogRuleGraphQl/composer.json @@ -2,17 +2,18 @@ "name": "magento/module-catalog-rule-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.2", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*" + "magento/framework": "103.0.*" }, "suggest": { - "magento/module-catalog-rule": "*" + "magento/module-catalog-rule": "101.2.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -22,3 +23,4 @@ } } } + diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index 465d7daeebe18..1e9d7381a08e2 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -1,32 +1,33 @@ { "name": "magento/module-catalog-search", "description": "Catalog search", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "102.0.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-indexer": "*", - "magento/module-catalog-inventory": "*", - "magento/module-customer": "*", - "magento/module-directory": "*", - "magento/module-eav": "*", - "magento/module-search": "*", - "magento/module-store": "*", - "magento/module-theme": "*", - "magento/module-ui": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-indexer": "100.4.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-customer": "103.0.*", + "magento/module-directory": "100.4.*", + "magento/module-eav": "102.1.*", + "magento/module-search": "101.1.*", + "magento/module-store": "101.1.*", + "magento/module-theme": "101.1.*", + "magento/module-ui": "101.2.*" }, "suggest": { - "magento/module-config": "*" + "magento/module-config": "101.2.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -36,3 +37,4 @@ } } } + diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index ce409e2186faa..0d7de0896c3d8 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -1,29 +1,30 @@ { "name": "magento/module-catalog-url-rewrite", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-catalog-import-export": "*", - "magento/module-eav": "*", - "magento/module-import-export": "*", - "magento/module-store": "*", - "magento/module-ui": "*", - "magento/module-url-rewrite": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-import-export": "101.1.*", + "magento/module-eav": "102.1.*", + "magento/module-import-export": "101.0.*", + "magento/module-store": "101.1.*", + "magento/module-ui": "101.2.*", + "magento/module-url-rewrite": "102.0.*" }, "suggest": { - "magento/module-webapi": "*" + "magento/module-webapi": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -33,3 +34,4 @@ } } } + diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json index 025234af6f865..d79c2ab17f611 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json @@ -2,23 +2,24 @@ "name": "magento/module-catalog-url-rewrite-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.3", "require": { "php": "~7.4.0||~8.1.0", - "magento/module-store": "*", - "magento/module-catalog": "*", - "magento/module-catalog-graph-ql": "*", - "magento/module-url-rewrite-graph-ql": "*", - "magento/framework": "*" + "magento/module-store": "101.1.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-graph-ql": "100.4.*", + "magento/module-url-rewrite-graph-ql": "100.4.*", + "magento/framework": "103.0.*" }, "suggest": { - "magento/module-catalog-url-rewrite": "*", - "magento/module-catalog-graph-ql": "*", - "magento/module-url-rewrite-graph-ql": "*" + "magento/module-catalog-url-rewrite": "100.4.*", + "magento/module-catalog-graph-ql": "100.4.*", + "magento/module-url-rewrite-graph-ql": "100.4.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -28,3 +29,4 @@ } } } + diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml index 5590aa1cdcefa..e833c8e72f47a 100644 --- a/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml +++ b/app/code/Magento/CatalogWidget/Test/Mftf/Test/StorefrontProductGridUIUpdatesOnDesktopTest.xml @@ -61,6 +61,7 @@ + diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index 33c5e3b3ba3ee..12cc7ed087a1e 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -1,27 +1,28 @@ { "name": "magento/module-catalog-widget", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5-p1", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-rule": "*", - "magento/module-store": "*", - "magento/module-widget": "*", - "magento/module-wishlist": "*", - "magento/module-theme": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-rule": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-widget": "101.2.*", + "magento/module-wishlist": "101.2.*", + "magento/module-theme": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -31,3 +32,4 @@ } } } + diff --git a/app/code/Magento/Checkout/Model/ShippingInformationManagement.php b/app/code/Magento/Checkout/Model/ShippingInformationManagement.php index f397a8ddc9cf1..f08c48c55efa1 100644 --- a/app/code/Magento/Checkout/Model/ShippingInformationManagement.php +++ b/app/code/Magento/Checkout/Model/ShippingInformationManagement.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Checkout\Model; @@ -39,60 +40,62 @@ class ShippingInformationManagement implements ShippingInformationManagementInte /** * @var PaymentMethodManagementInterface */ - protected $paymentMethodManagement; + protected PaymentMethodManagementInterface $paymentMethodManagement; /** * @var PaymentDetailsFactory */ - protected $paymentDetailsFactory; + protected PaymentDetailsFactory $paymentDetailsFactory; /** * @var CartTotalRepositoryInterface */ - protected $cartTotalsRepository; + protected CartTotalRepositoryInterface $cartTotalsRepository; /** * @var CartRepositoryInterface */ - protected $quoteRepository; - + protected CartRepositoryInterface $quoteRepository; /** * @var Logger */ - protected $logger; + protected Logger $logger; /** * @var QuoteAddressValidator */ - protected $addressValidator; + protected QuoteAddressValidator $addressValidator; /** * @var AddressRepositoryInterface * @deprecated 100.2.0 + * @see AddressRepositoryInterface */ - protected $addressRepository; + protected AddressRepositoryInterface $addressRepository; /** * @var ScopeConfigInterface * @deprecated 100.2.0 + * @see ScopeConfigInterface */ - protected $scopeConfig; + protected ScopeConfigInterface $scopeConfig; /** * @var TotalsCollector * @deprecated 100.2.0 + * @see TotalsCollector */ - protected $totalsCollector; + protected TotalsCollector $totalsCollector; /** * @var CartExtensionFactory */ - private $cartExtensionFactory; + private CartExtensionFactory $cartExtensionFactory; /** * @var ShippingAssignmentFactory */ - protected $shippingAssignmentFactory; + protected ShippingAssignmentFactory $shippingAssignmentFactory; /** * @var ShippingFactory @@ -262,8 +265,11 @@ protected function validateQuote(Quote $quote): void * @param string $method * @return CartInterface */ - private function prepareShippingAssignment(CartInterface $quote, AddressInterface $address, $method): CartInterface - { + private function prepareShippingAssignment( + CartInterface $quote, + AddressInterface $address, + string $method + ): CartInterface { $cartExtension = $quote->getExtensionAttributes(); if ($cartExtension === null) { $cartExtension = $this->cartExtensionFactory->create(); diff --git a/app/code/Magento/Checkout/Observer/CspPolicyObserver.php b/app/code/Magento/Checkout/Observer/CspPolicyObserver.php new file mode 100644 index 0000000000000..10c6e7aefac12 --- /dev/null +++ b/app/code/Magento/Checkout/Observer/CspPolicyObserver.php @@ -0,0 +1,69 @@ +inlineTranslate = $inlineTranslate; + $this->dynamicCollector = $dynamicCollector; + } + + /** + * Override CSP policy for checkout page wit inline translation + * + * @param Observer $observer + * @return void + * + * @throws \Exception + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function execute(Observer $observer): void + { + if ($this->inlineTranslate->isAllowed()) { + $policy = new FetchPolicy( + 'script-src', + false, + [], + [], + true, + true, + false, + [], + [] + ); + + $this->dynamicCollector->add($policy); + } + } +} diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml index 649421a53040b..2119e5c43f7bb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniCartEmptyActionGroup.xml @@ -13,8 +13,9 @@ Validates that the provided Product Count appears in the Storefront Header next to the Shopping Cart icon. Clicks on the Mini Shopping Cart icon. Validates that the 'No Items' message is present and correct in the Storefront Mini Shopping Cart. + - + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml index 64f392d39edcb..f770e7f42caec 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingDefaultAddressTest.xml @@ -20,11 +20,11 @@ + 560 - @@ -40,6 +40,7 @@ + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml index f6db22cbccaa8..68205dd0a369d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -20,6 +20,7 @@ + 560 @@ -40,6 +41,7 @@ + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml index 1a85bb0bee1ee..07d1378d00529 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml @@ -18,6 +18,7 @@ + @@ -105,6 +106,7 @@ + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml index eb76748a81c97..ec4593cf133b0 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerLoginDuringCheckoutTest.xml @@ -19,6 +19,7 @@ + @@ -39,9 +40,9 @@ - + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml index 05dff1ae58773..efcbca4de9adf 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml @@ -66,12 +66,12 @@ - + + - - + diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml index 901c5c3598dbf..1ea13399dcaaf 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml @@ -45,8 +45,10 @@ - + + + diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index f277184d8986b..ae1c98d9812bf 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -1,41 +1,43 @@ { "name": "magento/module-checkout", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5-p8", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-captcha": "*", - "magento/module-catalog": "*", - "magento/module-catalog-inventory": "*", - "magento/module-config": "*", - "magento/module-customer": "*", - "magento/module-directory": "*", - "magento/module-eav": "*", - "magento/module-msrp": "*", - "magento/module-page-cache": "*", - "magento/module-payment": "*", - "magento/module-quote": "*", - "magento/module-sales": "*", - "magento/module-sales-rule": "*", - "magento/module-security": "*", - "magento/module-shipping": "*", - "magento/module-store": "*", - "magento/module-tax": "*", - "magento/module-theme": "*", - "magento/module-ui": "*", - "magento/module-authorization": "*" + "magento/framework": "103.0.*", + "magento/module-captcha": "100.4.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-config": "101.2.*", + "magento/module-customer": "103.0.*", + "magento/module-directory": "100.4.*", + "magento/module-eav": "102.1.*", + "magento/module-msrp": "100.4.*", + "magento/module-page-cache": "100.4.*", + "magento/module-payment": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-sales": "103.0.*", + "magento/module-sales-rule": "101.2.*", + "magento/module-security": "100.4.*", + "magento/module-shipping": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-tax": "100.4.*", + "magento/module-theme": "101.1.*", + "magento/module-ui": "101.2.*", + "magento/module-authorization": "100.4.*", + "magento/module-csp": "100.4.*" }, "suggest": { - "magento/module-cookie": "*" + "magento/module-cookie": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -45,3 +47,4 @@ } } } + diff --git a/app/code/Magento/Checkout/etc/adminhtml/system.xml b/app/code/Magento/Checkout/etc/adminhtml/system.xml index b56566a043c3e..944914b34e1ea 100644 --- a/app/code/Magento/Checkout/etc/adminhtml/system.xml +++ b/app/code/Magento/Checkout/etc/adminhtml/system.xml @@ -13,6 +13,11 @@ Magento_Checkout::checkout + + + Magento\Config\Model\Config\Source\Yesno + Enabling this setting will allow unauthenticated users to query if an e-mail address is already associated with a customer account. This can be used to enhance the checkout workflow for guests that do not realize they already have an account but comes at the cost of exposing information to unauthenticated users. + Magento\Config\Model\Config\Source\Yesno @@ -23,7 +28,7 @@ - \Magento\Checkout\Model\Adminhtml\BillingAddressDisplayOptions + Magento\Checkout\Model\Adminhtml\BillingAddressDisplayOptions @@ -101,5 +106,17 @@ +
+ + + + + + If empty, Default Report URI for storefront will be used. + validate-url + + + +
diff --git a/app/code/Magento/Checkout/etc/config.xml b/app/code/Magento/Checkout/etc/config.xml index eac0bd849da35..ef4afdf8d4b27 100644 --- a/app/code/Magento/Checkout/etc/config.xml +++ b/app/code/Magento/Checkout/etc/config.xml @@ -9,6 +9,7 @@ + 0 1 1 0 @@ -55,5 +56,20 @@ + + + + 0 + + + + + + 0 + 1 + + + + diff --git a/app/code/Magento/Checkout/etc/frontend/events.xml b/app/code/Magento/Checkout/etc/frontend/events.xml index 901b652201db1..90e2236ee9d63 100644 --- a/app/code/Magento/Checkout/etc/frontend/events.xml +++ b/app/code/Magento/Checkout/etc/frontend/events.xml @@ -12,4 +12,8 @@ + + + diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/AdminOpenEditPageTermsConditionsByNameActionGroup.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/AdminOpenEditPageTermsConditionsByNameActionGroup.xml new file mode 100644 index 0000000000000..3cddd2ebb5389 --- /dev/null +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/ActionGroup/AdminOpenEditPageTermsConditionsByNameActionGroup.xml @@ -0,0 +1,26 @@ + + + + + + + Opens Edit Page of Terms and Conditions By Provided Name + + + + + + + + + + + + + diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml index 326f9dcce4320..61173e040c700 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Section/AdminTermGridSection.xml @@ -11,6 +11,7 @@ + diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminUpdateDisabledHtmlTermEntityTest.xml b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminUpdateDisabledHtmlTermEntityTest.xml index f9d60796d0424..50ba29126a0fd 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminUpdateDisabledHtmlTermEntityTest.xml +++ b/app/code/Magento/CheckoutAgreements/Test/Mftf/Test/AdminUpdateDisabledHtmlTermEntityTest.xml @@ -32,7 +32,7 @@ - + diff --git a/app/code/Magento/CheckoutAgreements/composer.json b/app/code/Magento/CheckoutAgreements/composer.json index 753bef25e3e64..2121da4fcee10 100644 --- a/app/code/Magento/CheckoutAgreements/composer.json +++ b/app/code/Magento/CheckoutAgreements/composer.json @@ -1,22 +1,23 @@ { "name": "magento/module-checkout-agreements", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4-p5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-checkout": "*", - "magento/module-quote": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-checkout": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-store": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json index de6bc855e7847..f75215e0e3e1f 100644 --- a/app/code/Magento/CheckoutAgreementsGraphQl/composer.json +++ b/app/code/Magento/CheckoutAgreementsGraphQl/composer.json @@ -2,19 +2,20 @@ "name": "magento/module-checkout-agreements-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.1", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-store": "*", - "magento/module-checkout-agreements": "*" + "magento/framework": "103.0.*", + "magento/module-store": "101.1.*", + "magento/module-checkout-agreements": "100.4.*" }, "suggest": { - "magento/module-graph-ql": "*" + "magento/module-graph-ql": "100.4.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -24,3 +25,4 @@ } } } + diff --git a/app/code/Magento/Cms/Controller/Noroute/Index.php b/app/code/Magento/Cms/Controller/Noroute/Index.php index b30beae73dce1..5475e7960dd26 100644 --- a/app/code/Magento/Cms/Controller/Noroute/Index.php +++ b/app/code/Magento/Cms/Controller/Noroute/Index.php @@ -6,15 +6,17 @@ */ namespace Magento\Cms\Controller\Noroute; +use Magento\Framework\Controller\Result\ForwardFactory; + /** * @SuppressWarnings(PHPMD.AllPurposeAction) */ class Index extends \Magento\Framework\App\Action\Action { /** - * @var \Magento\Framework\Controller\Result\ForwardFactory + * @var ForwardFactory */ - protected $resultForwardFactory; + protected ForwardFactory $resultForwardFactory; /** * @param \Magento\Framework\App\Action\Context $context @@ -48,6 +50,7 @@ public function execute() if ($resultPage) { $resultPage->setStatusHeader(404, '1.1', 'Not Found'); $resultPage->setHeader('Status', '404 File not found'); + $resultPage->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0', true); return $resultPage; } else { /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Noroute/IndexTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Noroute/IndexTest.php index 665b79fdf48be..6d4dcfc0711df 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Noroute/IndexTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Noroute/IndexTest.php @@ -29,17 +29,17 @@ class IndexTest extends TestCase /** * @var Index */ - protected $_controller; + protected Index $_controller; /** * @var MockObject */ - protected $_cmsHelperMock; + protected MockObject $_cmsHelperMock; /** * @var MockObject */ - protected $_requestMock; + protected MockObject $_requestMock; /** * @var ForwardFactory|MockObject @@ -121,8 +121,10 @@ public function testExecuteResultPage(): void ->willReturn($this->resultPageMock); $this->resultPageMock ->method('setHeader') - ->with('Status', '404 File not found') - ->willReturn($this->resultPageMock); + ->withConsecutive( + ['Status', '404 File not found'], + ['Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'] + )->willReturn($this->resultPageMock); $this->_cmsHelperMock->expects( $this->once() )->method( diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index b3b2ba31db37b..43140a669b59f 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -1,30 +1,31 @@ { "name": "magento/module-cms", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "104.0.5-p1", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-email": "*", - "magento/module-media-storage": "*", - "magento/module-store": "*", - "magento/module-theme": "*", - "magento/module-ui": "*", - "magento/module-variable": "*", - "magento/module-widget": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-email": "101.1.*", + "magento/module-media-storage": "100.4.*", + "magento/module-store": "101.1.*", + "magento/module-theme": "101.1.*", + "magento/module-ui": "101.2.*", + "magento/module-variable": "100.4.*", + "magento/module-widget": "101.2.*" }, "suggest": { - "magento/module-cms-sample-data": "*" + "magento/module-cms-sample-data": "Sample Data version: 100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -34,3 +35,4 @@ } } } + diff --git a/app/code/Magento/CmsGraphQl/composer.json b/app/code/Magento/CmsGraphQl/composer.json index b2550344299fa..31cdf1b9242e7 100644 --- a/app/code/Magento/CmsGraphQl/composer.json +++ b/app/code/Magento/CmsGraphQl/composer.json @@ -2,22 +2,23 @@ "name": "magento/module-cms-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.2", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-cms": "*", - "magento/module-widget": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-cms": "104.0.*", + "magento/module-widget": "101.2.*", + "magento/module-store": "101.1.*" }, "suggest": { - "magento/module-graph-ql": "*", - "magento/module-graph-ql-cache": "*", - "magento/module-store-graph-ql": "*" + "magento/module-graph-ql": "100.4.*", + "magento/module-graph-ql-cache": "100.4.*", + "magento/module-store-graph-ql": "100.4.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -27,3 +28,4 @@ } } } + diff --git a/app/code/Magento/CmsUrlRewrite/composer.json b/app/code/Magento/CmsUrlRewrite/composer.json index 8fb9bbfff22e2..635ee06491d27 100644 --- a/app/code/Magento/CmsUrlRewrite/composer.json +++ b/app/code/Magento/CmsUrlRewrite/composer.json @@ -1,21 +1,22 @@ { "name": "magento/module-cms-url-rewrite", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-cms": "*", - "magento/module-store": "*", - "magento/module-url-rewrite": "*" + "magento/framework": "103.0.*", + "magento/module-cms": "104.0.*", + "magento/module-store": "101.1.*", + "magento/module-url-rewrite": "102.0.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -25,3 +26,4 @@ } } } + diff --git a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json index 70a598d26d574..a9d224eeb6da3 100644 --- a/app/code/Magento/CmsUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CmsUrlRewriteGraphQl/composer.json @@ -2,22 +2,23 @@ "name": "magento/module-cms-url-rewrite-graph-ql", "description": "N/A", "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "version": "100.4.3", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-cms": "*", - "magento/module-store": "*", - "magento/module-url-rewrite-graph-ql": "*", - "magento/module-cms-graph-ql": "*" + "magento/framework": "103.0.*", + "magento/module-cms": "104.0.*", + "magento/module-store": "101.1.*", + "magento/module-url-rewrite-graph-ql": "100.4.*", + "magento/module-cms-graph-ql": "100.4.*" }, "suggest": { - "magento/module-cms-url-rewrite": "*", - "magento/module-catalog-graph-ql": "*" + "magento/module-cms-url-rewrite": "100.4.*", + "magento/module-catalog-graph-ql": "100.4.*" }, - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -27,3 +28,4 @@ } } } + diff --git a/app/code/Magento/CompareListGraphQl/composer.json b/app/code/Magento/CompareListGraphQl/composer.json index e8fb5d588852e..bae7c7a6e3769 100644 --- a/app/code/Magento/CompareListGraphQl/composer.json +++ b/app/code/Magento/CompareListGraphQl/composer.json @@ -2,16 +2,17 @@ "name": "magento/module-compare-list-graph-ql", "description": "N/A", "type": "magento2-module", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-customer": "*" - }, "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.1", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-customer": "103.0.*" + }, "autoload": { "files": [ "registration.php" @@ -21,3 +22,4 @@ } } } + diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field/File.php b/app/code/Magento/Config/Block/System/Config/Form/Field/File.php old mode 100644 new mode 100755 index 6ebd85af7a5da..edc3d3e977072 --- a/app/code/Magento/Config/Block/System/Config/Form/Field/File.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field/File.php @@ -9,6 +9,8 @@ * * @author Magento Core Team */ +declare(strict_types=1); + namespace Magento\Config\Block\System\Config\Form\Field; class File extends \Magento\Framework\Data\Form\Element\File @@ -35,7 +37,7 @@ protected function _getDeleteCheckbox() $html = ''; if ((string)$this->getValue()) { $label = __('Delete File'); - $html .= '
' . $this->getValue() . ' '; + $html .= '
' . $this->_escaper->escapeHtml($this->getValue()) . ' '; $html .= 'assertStringContainsString($this->testData['before_element_html'], $html); diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index 61100e6336c27..fe995eec0f4ee 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -1,25 +1,26 @@ { "name": "magento/module-config", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "101.2.5-p9", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-cron": "*", - "magento/module-deploy": "*", - "magento/module-directory": "*", - "magento/module-email": "*", - "magento/module-media-storage": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-cron": "100.4.*", + "magento/module-deploy": "100.4.*", + "magento/module-directory": "100.4.*", + "magento/module-email": "101.1.*", + "magento/module-media-storage": "100.4.*", + "magento/module-store": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -29,3 +30,4 @@ } } } + diff --git a/app/code/Magento/Config/i18n/en_US.csv b/app/code/Magento/Config/i18n/en_US.csv index ceb1efdc8b77d..b7e1e4c7611bc 100644 --- a/app/code/Magento/Config/i18n/en_US.csv +++ b/app/code/Magento/Config/i18n/en_US.csv @@ -119,3 +119,4 @@ Dashboard,Dashboard "Store Email Addresses Section","Store Email Addresses Section" "Email to a Friend","Email to a Friend" "Taiwan","Taiwan, Province of China" +"Invalid file name", "Invalid file name" diff --git a/app/code/Magento/ConfigurableImportExport/composer.json b/app/code/Magento/ConfigurableImportExport/composer.json index 98205def6a799..687fcea6d2c26 100644 --- a/app/code/Magento/ConfigurableImportExport/composer.json +++ b/app/code/Magento/ConfigurableImportExport/composer.json @@ -1,24 +1,25 @@ { "name": "magento/module-configurable-import-export", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.3", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-catalog-import-export": "*", - "magento/module-configurable-product": "*", - "magento/module-eav": "*", - "magento/module-import-export": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-import-export": "101.1.*", + "magento/module-configurable-product": "100.4.*", + "magento/module-eav": "102.1.*", + "magento/module-import-export": "101.0.*", + "magento/module-store": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -28,3 +29,4 @@ } } } + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml index 9925aba09fb82..0f0ea705971ec 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/CreateNewAttributeActionGroup.xml @@ -51,6 +51,7 @@ + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml index 0018f5996c9bc..ec8fc99b46bb3 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml @@ -12,7 +12,7 @@ Shoes 60 100 - design + design123 red red123 blue diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml index fb71d6cbda2a8..df3fffb6f716c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/CatalogProductsSection.xml @@ -14,7 +14,7 @@ - + diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml index 8099f30941f7d..437411d7c041a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection/ConfigurableProductSection.xml @@ -12,7 +12,7 @@ - + diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 67b1ad2b2ed33..aa3c5b8e86cd9 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -1,38 +1,39 @@ { "name": "magento/module-configurable-product", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5-p1", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-backend": "*", - "magento/module-catalog": "*", - "magento/module-catalog-inventory": "*", - "magento/module-checkout": "*", - "magento/module-customer": "*", - "magento/module-eav": "*", - "magento/module-media-storage": "*", - "magento/module-quote": "*", - "magento/module-store": "*", - "magento/module-ui": "*" + "magento/framework": "103.0.*", + "magento/module-backend": "102.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/module-checkout": "100.4.*", + "magento/module-customer": "103.0.*", + "magento/module-eav": "102.1.*", + "magento/module-media-storage": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-store": "101.1.*", + "magento/module-ui": "101.2.*" }, "suggest": { - "magento/module-msrp": "*", - "magento/module-webapi": "*", - "magento/module-sales": "*", - "magento/module-sales-rule": "*", - "magento/module-product-video": "*", - "magento/module-configurable-sample-data": "*", - "magento/module-product-links-sample-data": "*", - "magento/module-tax": "*" + "magento/module-msrp": "100.4.*", + "magento/module-webapi": "100.4.*", + "magento/module-sales": "103.0.*", + "magento/module-sales-rule": "101.2.*", + "magento/module-product-video": "100.4.*", + "magento/module-configurable-sample-data": "Sample Data version: 100.4.*", + "magento/module-product-links-sample-data": "Sample Data version: 100.4.*", + "magento/module-tax": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -42,3 +43,4 @@ } } } + diff --git a/app/code/Magento/ConfigurableProductGraphQl/composer.json b/app/code/Magento/ConfigurableProductGraphQl/composer.json index b839227511d88..0c742439aa66d 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/composer.json +++ b/app/code/Magento/ConfigurableProductGraphQl/composer.json @@ -2,21 +2,22 @@ "name": "magento/module-configurable-product-graph-ql", "description": "N/A", "type": "magento2-module", - "require": { - "php": "~7.4.0||~8.1.0", - "magento/module-catalog": "*", - "magento/module-configurable-product": "*", - "magento/module-graph-ql": "*", - "magento/module-catalog-graph-ql": "*", - "magento/module-quote": "*", - "magento/module-quote-graph-ql": "*", - "magento/module-catalog-inventory": "*", - "magento/framework": "*" - }, "license": [ "OSL-3.0", "AFL-3.0" ], + "version": "100.4.5", + "require": { + "php": "~7.4.0||~8.1.0", + "magento/module-catalog": "104.0.*", + "magento/module-configurable-product": "100.4.*", + "magento/module-graph-ql": "100.4.*", + "magento/module-catalog-graph-ql": "100.4.*", + "magento/module-quote": "101.2.*", + "magento/module-quote-graph-ql": "100.4.*", + "magento/module-catalog-inventory": "100.4.*", + "magento/framework": "103.0.*" + }, "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/ConfigurableProductSales/composer.json b/app/code/Magento/ConfigurableProductSales/composer.json index 55b2e78bd24d2..4256442b87ac1 100644 --- a/app/code/Magento/ConfigurableProductSales/composer.json +++ b/app/code/Magento/ConfigurableProductSales/composer.json @@ -1,22 +1,23 @@ { "name": "magento/module-configurable-product-sales", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.2", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-catalog": "*", - "magento/module-sales": "*", - "magento/module-store": "*", - "magento/module-configurable-product": "*" + "magento/framework": "103.0.*", + "magento/module-catalog": "104.0.*", + "magento/module-sales": "103.0.*", + "magento/module-store": "101.1.*", + "magento/module-configurable-product": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index 00ea8f865928d..55e8dae73c64d 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -1,22 +1,23 @@ { "name": "magento/module-contact", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-cms": "*", - "magento/module-config": "*", - "magento/module-customer": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-cms": "104.0.*", + "magento/module-config": "101.2.*", + "magento/module-customer": "103.0.*", + "magento/module-store": "101.1.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/Cookie/composer.json b/app/code/Magento/Cookie/composer.json index 6a5752792f7fb..6e0e1fc8ef258 100644 --- a/app/code/Magento/Cookie/composer.json +++ b/app/code/Magento/Cookie/composer.json @@ -1,22 +1,23 @@ { "name": "magento/module-cookie", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-store": "101.1.*" }, "suggest": { - "magento/module-backend": "*" + "magento/module-backend": "102.0.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index 0468a95b457c0..f6237ab319adf 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -1,22 +1,23 @@ { "name": "magento/module-cron", "description": "N/A", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.5", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-store": "101.1.*" }, "suggest": { - "magento/module-config": "*" + "magento/module-config": "101.2.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -26,3 +27,4 @@ } } } + diff --git a/app/code/Magento/Csp/Block/Sri/Hashes.php b/app/code/Magento/Csp/Block/Sri/Hashes.php new file mode 100644 index 0000000000000..7b67ec4ca534f --- /dev/null +++ b/app/code/Magento/Csp/Block/Sri/Hashes.php @@ -0,0 +1,94 @@ +integrityRepositoryPool = $integrityRepositoryPool ?: ObjectManager::getInstance() + ->get(SubresourceIntegrityRepositoryPool::class); + + $this->serializer = $serializer ?: ObjectManager::getInstance() + ->get(SerializerInterface::class); + } + + /** + * Retrieves integrity hashes in serialized format. + * + * @throws LocalizedException + * + * @return string + */ + public function getSerialized(): string + { + $result = []; + + $baseUrl = $this->_urlBuilder->getBaseUrl( + ["_type" => UrlInterface::URL_TYPE_STATIC] + ); + + $integrityRepository = $this->integrityRepositoryPool->get( + Package::BASE_AREA + ); + + foreach ($integrityRepository->getAll() as $integrity) { + $url = $baseUrl . $integrity->getPath(); + + $result[$url] = $integrity->getHash(); + } + + $integrityRepository = $this->integrityRepositoryPool->get( + $this->_appState->getAreaCode() + ); + + foreach ($integrityRepository->getAll() as $integrity) { + $url = $baseUrl . $integrity->getPath(); + + $result[$url] = $integrity->getHash(); + } + + return $this->serializer->serialize($result); + } +} diff --git a/app/code/Magento/Csp/Helper/CspNonceProvider.php b/app/code/Magento/Csp/Helper/CspNonceProvider.php new file mode 100644 index 0000000000000..14cfc859b321b --- /dev/null +++ b/app/code/Magento/Csp/Helper/CspNonceProvider.php @@ -0,0 +1,86 @@ +random = $random; + $this->dynamicCollector = $dynamicCollector; + } + + /** + * Generate nonce and add it to the CSP header + * + * @return string + * @throws LocalizedException + */ + public function generateNonce(): string + { + if (empty($this->nonce)) { + $this->nonce = $this->random->getRandomString( + self::NONCE_LENGTH, + Random::CHARS_DIGITS . Random::CHARS_LOWERS + ); + + $policy = new FetchPolicy( + 'script-src', + false, + [], + [], + false, + false, + false, + [$this->nonce], + [] + ); + + $this->dynamicCollector->add($policy); + } + + return base64_encode($this->nonce); + } +} diff --git a/app/code/Magento/Csp/Helper/InlineUtil.php b/app/code/Magento/Csp/Helper/InlineUtil.php index f2a70fe465de3..be6ac32485fad 100644 --- a/app/code/Magento/Csp/Helper/InlineUtil.php +++ b/app/code/Magento/Csp/Helper/InlineUtil.php @@ -45,6 +45,14 @@ class InlineUtil implements InlineUtilInterface, SecurityProcessorInterface */ private $configCollector; + /** + * @var CspNonceProvider + */ + private CspNonceProvider $nonceProvider; + + /** + * @var array[] + */ private static $tagMeta = [ 'script' => ['id' => 'script-src', 'remote' => ['src'], 'hash' => true], 'style' => ['id' => 'style-src', 'remote' => [], 'hash' => true], @@ -67,17 +75,20 @@ class InlineUtil implements InlineUtilInterface, SecurityProcessorInterface * @param bool $useUnsafeHashes Use 'unsafe-hashes' policy (not supported by CSP v2). * @param HtmlRenderer|null $htmlRenderer * @param ConfigCollector|null $configCollector + * @param CspNonceProvider|null $nonceProvider */ public function __construct( DynamicCollector $dynamicCollector, bool $useUnsafeHashes = false, ?HtmlRenderer $htmlRenderer = null, - ?ConfigCollector $configCollector = null + ?ConfigCollector $configCollector = null, + ?CspNonceProvider $nonceProvider = null ) { $this->dynamicCollector = $dynamicCollector; $this->useUnsafeHashes = $useUnsafeHashes; $this->htmlRenderer = $htmlRenderer ?? ObjectManager::getInstance()->get(HtmlRenderer::class); $this->configCollector = $configCollector ?? ObjectManager::getInstance()->get(ConfigCollector::class); + $this->nonceProvider = $nonceProvider ?? ObjectManager::getInstance()->get(CspNonceProvider::class); } /** @@ -200,19 +211,34 @@ public function processTag(TagData $tagData): TagData && !empty(self::$tagMeta[$tagData->getTag()]['hash']) && $this->isInlineDisabled(self::$tagMeta[$tagData->getTag()]['id']) ) { - $this->dynamicCollector->add( - new FetchPolicy( - $policyId, - false, - [], - [], - false, - false, - false, - [], - $this->generateHashValue($tagData->getContent()) - ) - ); + /** create new tagData with a nonce */ + if ($tagData->getTag() === 'script') { + $nonce = $this->nonceProvider->generateNonce(); + $tagAttributes = $tagData->getAttributes(); + $tagAttributes['nonce'] = $nonce; + $newTagData = new TagData( + $tagData->getTag(), + $tagAttributes, + $tagData->getContent(), + $tagData->isTextContent() + ); + + $tagData = $newTagData; + } else { + $this->dynamicCollector->add( + new FetchPolicy( + $policyId, + false, + [], + [], + false, + false, + false, + [], + $this->generateHashValue($tagData->getContent()) + ) + ); + } } } diff --git a/app/code/Magento/Csp/Model/Collector/ConfigCollector.php b/app/code/Magento/Csp/Model/Collector/ConfigCollector.php index 34711fe5d8a22..1ae4badbc9348 100644 --- a/app/code/Magento/Csp/Model/Collector/ConfigCollector.php +++ b/app/code/Magento/Csp/Model/Collector/ConfigCollector.php @@ -11,6 +11,8 @@ use Magento\Csp\Model\Collector\Config\PolicyReaderPool; use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Request\Http; use Magento\Framework\App\State; use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Model\ScopeInterface; @@ -40,22 +42,32 @@ class ConfigCollector implements PolicyCollectorInterface */ private $storeManager; + /** + * @var Http + */ + private Http $request; + /** * @param ScopeConfigInterface $config * @param PolicyReaderPool $readersPool * @param State $state * @param StoreManagerInterface $storeManager + * @param Http|null $request */ public function __construct( ScopeConfigInterface $config, PolicyReaderPool $readersPool, State $state, - StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + ?Http $request = null ) { $this->config = $config; $this->readersPool = $readersPool; $this->state = $state; $this->storeManager = $storeManager; + + $this->request = $request + ?? ObjectManager::getInstance()->get(Http::class); } /** @@ -74,11 +86,26 @@ public function collect(array $defaultPolicies = []): array } if ($configArea) { - $policiesConfig = $this->config->getValue( + $policiesConfigGlobal = $this->config->getValue( 'csp/policies/' . $configArea, ScopeInterface::SCOPE_STORE, $this->storeManager->getStore() ); + + $policiesConfigLocal = $this->config->getValue( + sprintf( + 'csp/policies/%s_%s', + $configArea, + $this->request->getFullActionName() + ), + ScopeInterface::SCOPE_STORE, + $this->storeManager->getStore() + ); + + $policiesConfig = is_array($policiesConfigLocal) ? + array_replace_recursive($policiesConfigGlobal, $policiesConfigLocal) : + $policiesConfigGlobal; + if (is_array($policiesConfig) && $policiesConfig) { foreach ($policiesConfig as $policyConfig) { $collected[] = $this->readersPool->getReader($policyConfig['policy_id']) diff --git a/app/code/Magento/Csp/Model/Deploy/Package/Processor/PostProcessor/Integrity.php b/app/code/Magento/Csp/Model/Deploy/Package/Processor/PostProcessor/Integrity.php new file mode 100644 index 0000000000000..41e81752632fb --- /dev/null +++ b/app/code/Magento/Csp/Model/Deploy/Package/Processor/PostProcessor/Integrity.php @@ -0,0 +1,89 @@ +filesystem = $filesystem; + $this->hashGenerator = $hashGenerator; + $this->integrityFactory = $integrityFactory; + $this->integrityCollector = $integrityCollector; + } + + /** + * @inheritdoc + */ + public function process(Package $package, array $options): bool + { + $staticDir = $this->filesystem->getDirectoryRead( + DirectoryList::ROOT + ); + + foreach ($package->getFiles() as $file) { + if ($file->getExtension() == "js") { + $integrity = $this->integrityFactory->create( + [ + "data" => [ + 'hash' => $this->hashGenerator->generate( + $staticDir->readFile($file->getSourcePath()) + ), + 'path' => $file->getDeployedFilePath() + ] + ] + ); + + $this->integrityCollector->collect($integrity); + } + } + + return true; + } +} diff --git a/app/code/Magento/Csp/Model/Deploy/Package/Processor/PostProcessor/Map.php b/app/code/Magento/Csp/Model/Deploy/Package/Processor/PostProcessor/Map.php new file mode 100644 index 0000000000000..4e97517a165c5 --- /dev/null +++ b/app/code/Magento/Csp/Model/Deploy/Package/Processor/PostProcessor/Map.php @@ -0,0 +1,139 @@ +minification = $minification; + $this->integrityFactory = $integrityFactory; + $this->hashGenerator = $hashGenerator; + $this->driver = $driver; + $this->integrityCollector = $integrityCollector; + $this->filesystem = $filesystem; + parent::__construct($deployStaticFile, $formatter, $packageFileFactory, $minification); + } + + /** + * @inheritdoc + * + * @throws FileSystemException + */ + public function process(Package $package, array $options): bool + { + parent::process($package, $options); + $fileName = $this->minification->addMinifiedSign(RepositoryMap::REQUIRE_JS_MAP_NAME); + $path = $package->getPath(); + $relativePath = $path . DIRECTORY_SEPARATOR . $fileName; + + if ($this->fileExists($relativePath)) { + $dir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); + $absolutePath = $dir->getAbsolutePath($relativePath); + $fileContent = $this->driver->fileGetContents($absolutePath); + + if ($fileContent) { + $integrity = $this->integrityFactory->create( + [ + "data" => [ + 'hash' => $this->hashGenerator->generate($fileContent), + 'path' => $relativePath + ] + ] + ); + $this->integrityCollector->collect($integrity); + } + } + return true; + } + + /** + * Check if file exist + * + * @param string $path + * @return bool + * @throws FileSystemException + */ + private function fileExists(string $path): bool + { + $dir = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); + return $dir->isExist($path); + } +} diff --git a/app/code/Magento/Csp/Model/Mode/ConfigManager.php b/app/code/Magento/Csp/Model/Mode/ConfigManager.php index 9f10154604d5f..bc31a41982549 100644 --- a/app/code/Magento/Csp/Model/Mode/ConfigManager.php +++ b/app/code/Magento/Csp/Model/Mode/ConfigManager.php @@ -7,6 +7,8 @@ namespace Magento\Csp\Model\Mode; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\ObjectManager; use Magento\Csp\Api\Data\ModeConfiguredInterface; use Magento\Csp\Api\ModeConfigManagerInterface; use Magento\Csp\Model\Mode\Data\ModeConfigured; @@ -36,16 +38,29 @@ class ConfigManager implements ModeConfigManagerInterface */ private $state; + /** + * @var Http + */ + private Http $request; + /** * @param ScopeConfigInterface $config * @param Store $store * @param State $state + * @param Http|null $request */ - public function __construct(ScopeConfigInterface $config, Store $store, State $state) - { + public function __construct( + ScopeConfigInterface $config, + Store $store, + State $state, + ?Http $request = null + ) { $this->config = $config; $this->storeModel = $store; $this->state = $state; + + $this->request = $request + ?? ObjectManager::getInstance()->get(Http::class); } /** @@ -54,25 +69,58 @@ public function __construct(ScopeConfigInterface $config, Store $store, State $s public function getConfigured(): ModeConfiguredInterface { $area = $this->state->getAreaCode(); + if ($area === Area::AREA_ADMINHTML) { $configArea = 'admin'; } elseif ($area === Area::AREA_FRONTEND) { $configArea = 'storefront'; } else { - throw new \RuntimeException('CSP can only be configured for storefront or admin area'); + throw new \RuntimeException( + 'CSP can only be configured for storefront or admin area' + ); } - $reportOnly = $this->config->isSetFlag( - 'csp/mode/' . $configArea .'/report_only', + $reportOnly = $this->config->getValue( + sprintf( + 'csp/mode/%s_%s/report_only', + $configArea, + $this->request->getFullActionName() + ), ScopeInterface::SCOPE_STORE, $this->storeModel->getStore() ); + + if ($reportOnly === null) { + // Fallback to default configuration. + $reportOnly = $this->config->getValue( + 'csp/mode/' . $configArea .'/report_only', + ScopeInterface::SCOPE_STORE, + $this->storeModel->getStore() + ); + } + $reportUri = $this->config->getValue( - 'csp/mode/' . $configArea .'/report_uri', + sprintf( + 'csp/mode/%s_%s/report_uri', + $configArea, + $this->request->getFullActionName() + ), ScopeInterface::SCOPE_STORE, $this->storeModel->getStore() ); - return new ModeConfigured($reportOnly, !empty($reportUri) ? $reportUri : null); + if (empty($reportUri)) { + // Fallback to default configuration. + $reportUri = $this->config->getValue( + 'csp/mode/' . $configArea .'/report_uri', + ScopeInterface::SCOPE_STORE, + $this->storeModel->getStore() + ); + } + + return new ModeConfigured( + (bool) $reportOnly, + !empty($reportUri) ? $reportUri : null + ); } } diff --git a/app/code/Magento/Csp/Model/SubresourceIntegrity.php b/app/code/Magento/Csp/Model/SubresourceIntegrity.php new file mode 100644 index 0000000000000..0a2824c93027e --- /dev/null +++ b/app/code/Magento/Csp/Model/SubresourceIntegrity.php @@ -0,0 +1,34 @@ +getData("path"); + } + + /** + * Gets an integrity hash. + * + * @return string|null + */ + public function getHash(): ?string + { + return $this->getData("hash"); + } +} diff --git a/app/code/Magento/Csp/Model/SubresourceIntegrity/HashGenerator.php b/app/code/Magento/Csp/Model/SubresourceIntegrity/HashGenerator.php new file mode 100644 index 0000000000000..be78a1a0c66b8 --- /dev/null +++ b/app/code/Magento/Csp/Model/SubresourceIntegrity/HashGenerator.php @@ -0,0 +1,37 @@ +data = $data; + } + + /** + * Collects given Integrity object. + * + * @param SubresourceIntegrity $integrity + * + * @return void + */ + public function collect(SubresourceIntegrity $integrity): void + { + $this->data[] = $integrity; + } + + /** + * Provides all collected Integrity objects. + * + * @return SubresourceIntegrity[] + */ + public function release(): array + { + return $this->data; + } +} diff --git a/app/code/Magento/Csp/Model/SubresourceIntegrityRepository.php b/app/code/Magento/Csp/Model/SubresourceIntegrityRepository.php new file mode 100644 index 0000000000000..f2ef550a8bda3 --- /dev/null +++ b/app/code/Magento/Csp/Model/SubresourceIntegrityRepository.php @@ -0,0 +1,207 @@ +cache = $cache; + $this->serializer = $serializer; + $this->integrityFactory = $integrityFactory; + $this->context = $context; + } + + /** + * Gets an Integrity object by path. + * + * @param string $path + * + * @return SubresourceIntegrity|null + */ + public function getByPath(string $path): ?SubresourceIntegrity + { + $data = $this->getData(); + + if (isset($data[$path])) { + return $this->integrityFactory->create( + [ + "data" => [ + "path" => $path, + "hash" => $data[$path] + ] + ] + ); + } + + return null; + } + + /** + * Gets all available Integrity objects. + * + * @return SubresourceIntegrity[] + */ + public function getAll(): array + { + $result = []; + + foreach ($this->getData() as $path => $hash) { + $result[] = $this->integrityFactory->create( + [ + "data" => [ + "path" => $path, + "hash" => $hash + ] + ] + ); + } + + return $result; + } + + /** + * Saves Integrity object. + * + * @param SubresourceIntegrity $integrity + * + * @return bool + */ + public function save(SubresourceIntegrity $integrity): bool + { + $data = $this->getData(); + + $data[$integrity->getPath()] = $integrity->getHash(); + + $this->data = $data; + + return $this->cache->save( + $this->serializer->serialize($this->data), + $this->getCacheKey(), + [self::CACHE_PREFIX] + ); + } + + /** + * Saves a bunch of Integrity objects. + * + * @param SubresourceIntegrity[] $bunch + * + * @return bool + */ + public function saveBunch(array $bunch): bool + { + $data = $this->getData(); + + foreach ($bunch as $integrity) { + $data[$integrity->getPath()] = $integrity->getHash(); + } + + $this->data = $data; + + return $this->cache->save( + $this->serializer->serialize($this->data), + $this->getCacheKey(), + [self::CACHE_PREFIX] + ); + } + + /** + * Deletes all Integrity objects. + * + * @return bool + */ + public function deleteAll(): bool + { + $this->data = null; + + return $this->cache->remove( + $this->getCacheKey() + ); + } + + /** + * Loads integrity data from a storage. + * + * @return array + */ + private function getData(): array + { + if ($this->data === null) { + $cache = $this->cache->load($this->getCacheKey()); + + $this->data = $cache ? $this->serializer->unserialize($cache) : []; + } + + return $this->data; + } + + /** + * Gets a cache key based on current context. + * + * @return string + */ + private function getCacheKey(): string + { + $cacheKey = self::CACHE_PREFIX; + + if ($this->context) { + $cacheKey .= "_" . $this->context; + } + + return $cacheKey; + } +} diff --git a/app/code/Magento/Csp/Model/SubresourceIntegrityRepositoryPool.php b/app/code/Magento/Csp/Model/SubresourceIntegrityRepositoryPool.php new file mode 100644 index 0000000000000..5ad9c6c676de8 --- /dev/null +++ b/app/code/Magento/Csp/Model/SubresourceIntegrityRepositoryPool.php @@ -0,0 +1,53 @@ +integrityRepositoryFactory = $integrityRepositoryFactory; + } + + /** + * Gets subresource integrity repository by given context. + * + * @param string $context + * + * @return SubresourceIntegrityRepository + */ + public function get(string $context): SubresourceIntegrityRepository + { + if (!isset($this->repositories[$context])) { + $this->repositories[$context] = $this->integrityRepositoryFactory->create( + [ + "context" => $context + ] + ); + } + + return $this->repositories[$context]; + } +} diff --git a/app/code/Magento/Csp/Plugin/AddDefaultPropertiesToGroupPlugin.php b/app/code/Magento/Csp/Plugin/AddDefaultPropertiesToGroupPlugin.php new file mode 100644 index 0000000000000..159e2180427a5 --- /dev/null +++ b/app/code/Magento/Csp/Plugin/AddDefaultPropertiesToGroupPlugin.php @@ -0,0 +1,81 @@ +state = $state; + $this->integrityRepositoryPool = $integrityRepositoryPool; + } + + /** + * Before Plugin to add Properties to JS assets + * + * @param GroupedCollection $subject + * @param AssetInterface $asset + * @param array $properties + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeGetFilteredProperties( + GroupedCollection $subject, + AssetInterface $asset, + array $properties = [] + ): array { + if ($asset instanceof LocalInterface) { + $integrityRepository = $this->integrityRepositoryPool->get( + Package::BASE_AREA + ); + + $integrity = $integrityRepository->getByPath($asset->getPath()); + + if (!$integrity) { + $integrityRepository = $this->integrityRepositoryPool->get( + $this->state->getAreaCode() + ); + + $integrity = $integrityRepository->getByPath($asset->getPath()); + } + + if ($integrity && $integrity->getHash()) { + $properties['attributes']['integrity'] = $integrity->getHash(); + $properties['attributes']['crossorigin'] = 'anonymous'; + } + } + + return [$asset, $properties]; + } +} diff --git a/app/code/Magento/Csp/Plugin/GenerateAssetIntegrity.php b/app/code/Magento/Csp/Plugin/GenerateAssetIntegrity.php new file mode 100644 index 0000000000000..2b461588fa5ad --- /dev/null +++ b/app/code/Magento/Csp/Plugin/GenerateAssetIntegrity.php @@ -0,0 +1,91 @@ +hashGenerator = $hashGenerator; + $this->integrityFactory = $integrityFactory; + $this->integrityCollector = $integrityCollector; + } + + /** + * Generates integrity for RequireJs config. + * + * @param FileManager $subject + * @param File $result + * + * @return File + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterCreateRequireJsConfigAsset( + FileManager $subject, + File $result + ): File { + if (PHP_SAPI == 'cli') { + if (in_array($result->getContentType(), self::CONTENT_TYPES)) { + $integrity = $this->integrityFactory->create( + [ + "data" => [ + 'hash' => $this->hashGenerator->generate( + $result->getContent() + ), + 'path' => $result->getPath() + ] + ] + ); + + $this->integrityCollector->collect($integrity); + } + } + + return $result; + } +} diff --git a/app/code/Magento/Csp/Plugin/RemoveAllAssetIntegrityHashes.php b/app/code/Magento/Csp/Plugin/RemoveAllAssetIntegrityHashes.php new file mode 100644 index 0000000000000..85b97e9091c54 --- /dev/null +++ b/app/code/Magento/Csp/Plugin/RemoveAllAssetIntegrityHashes.php @@ -0,0 +1,69 @@ +integrityRepositoryPool = $integrityRepositoryPool; + } + + /** + * Removes existing integrity hashes before static content deploy + * + * @param DeployStaticContent $subject + * @param array $options + * + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeDeploy( + DeployStaticContent $subject, + array $options + ): void { + if (PHP_SAPI == 'cli' && !$this->isRefreshContentVersionOnly($options)) { + foreach ([Package::BASE_AREA, Area::AREA_FRONTEND, Area::AREA_ADMINHTML] as $area) { + $this->integrityRepositoryPool->get($area) + ->deleteAll(); + } + } + } + + /** + * Checks if only version refresh is requested. + * + * @param array $options + * + * @return bool + */ + private function isRefreshContentVersionOnly(array $options): bool + { + return isset($options[DeployStaticOptions::REFRESH_CONTENT_VERSION_ONLY]) + && $options[DeployStaticOptions::REFRESH_CONTENT_VERSION_ONLY]; + } +} diff --git a/app/code/Magento/Csp/Plugin/StoreAssetIntegrityHashes.php b/app/code/Magento/Csp/Plugin/StoreAssetIntegrityHashes.php new file mode 100644 index 0000000000000..e5674dd3a78ef --- /dev/null +++ b/app/code/Magento/Csp/Plugin/StoreAssetIntegrityHashes.php @@ -0,0 +1,70 @@ +integrityCollector = $integrityCollector; + $this->integrityRepositoryPool = $integrityRepositoryPool; + } + + /** + * Stores generated integrity hashes after static content deploy + * + * @param DeployStaticContent $subject + * @param mixed $result + * @param array $options + * + * @return void + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDeploy( + DeployStaticContent $subject, + $result, + array $options + ): void { + $bunches = []; + + foreach ($this->integrityCollector->release() as $integrity) { + $area = explode("/", $integrity->getPath())[0]; + + $bunches[$area][] = $integrity; + } + + foreach ($bunches as $area => $bunch) { + $this->integrityRepositoryPool->get($area) + ->saveBunch($bunch); + } + } +} diff --git a/app/code/Magento/Csp/Test/Unit/Model/Mode/ConfigManagerTest.php b/app/code/Magento/Csp/Test/Unit/Model/Mode/ConfigManagerTest.php index 3660e063387ae..9fff6efa407ec 100644 --- a/app/code/Magento/Csp/Test/Unit/Model/Mode/ConfigManagerTest.php +++ b/app/code/Magento/Csp/Test/Unit/Model/Mode/ConfigManagerTest.php @@ -12,8 +12,10 @@ use Magento\Csp\Model\Mode\Data\ModeConfigured; use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Request\Http; use Magento\Framework\App\State; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -44,6 +46,11 @@ class ConfigManagerTest extends TestCase */ private $stateMock; + /** + * @var Http|MockObject + */ + private $requestMock; + /** * Set Up */ @@ -54,13 +61,15 @@ protected function setUp(): void $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); $this->storeMock = $this->createMock(Store::class); $this->stateMock = $this->createMock(State::class); + $this->requestMock = $this->createMock(Http::class); $this->model = $objectManager->getObject( ConfigManager::class, [ 'config' => $this->scopeConfigMock, 'storeModel' => $this->storeMock, - 'state' => $this->stateMock + 'state' => $this->stateMock, + 'request' => $this->requestMock, ] ); } @@ -102,4 +111,73 @@ public function testConfiguredCSPForAdminArea() $this->assertInstanceOf(ModeConfigured::class, $result); } + + /** + * Test storefront checkout page CSP config. + * + * @return void + * + */ + public function testCheckoutPageReportOnly(): void + { + $this->requestMock->expects($this->exactly(2)) + ->method('getFullActionName') + ->willReturn('checkout_index_index'); + + $this->stateMock->expects($this->once()) + ->method('getAreaCode') + ->willReturn(Area::AREA_FRONTEND); + + $matcher = $this->exactly(2); + $this->scopeConfigMock->expects($matcher) + ->method('getValue') + ->willReturnCallback(function () use ($matcher) { + return match ($matcher->getInvocationCount()) { + 1 => ['csp/mode/checkout_index_index/report_only', ScopeInterface::SCOPE_STORE, null], + 2 => ['csp/mode/checkout_index_index/report_uri', ScopeInterface::SCOPE_STORE, null], + }; + }) + ->willReturnOnConsecutiveCalls(true, 'testReportUri'); + + $result = $this->model->getConfigured(); + + $this->assertInstanceOf(ModeConfigured::class, $result); + $this->assertTrue($result->isReportOnly()); + $this->assertEquals($result->getReportUri(), 'testReportUri'); + } + + /** + * Test non checkout page CSP config. + * + * @return void + */ + public function testNonCheckoutPageReportOnly(): void + { + $this->requestMock->expects($this->exactly(2)) + ->method('getFullActionName') + ->willReturn('dashboard_index_index'); + + $this->stateMock->expects($this->once()) + ->method('getAreaCode') + ->willReturn(Area::AREA_ADMINHTML); + + $matcher = $this->exactly(4); + $this->scopeConfigMock->expects($matcher) + ->method('getValue') + ->willReturnCallback(function () use ($matcher) { + return match ($matcher->getInvocationCount()) { + 1 => ['csp/mode/dashboard_index_index/report_only', ScopeInterface::SCOPE_STORE, null], + 2 => ['csp/mode/admin/report_only', ScopeInterface::SCOPE_STORE, null], + 3 => ['csp/mode/dashboard_index_index/report_uri', ScopeInterface::SCOPE_STORE, null], + 4 => ['csp/mode/admin/report_uri', ScopeInterface::SCOPE_STORE, null], + }; + }) + ->willReturnOnConsecutiveCalls(null, true, null, 'testPageReportUri'); + + $result = $this->model->getConfigured(); + + $this->assertInstanceOf(ModeConfigured::class, $result); + $this->assertTrue($result->isReportOnly()); + $this->assertEquals($result->getReportUri(), 'testPageReportUri'); + } } diff --git a/app/code/Magento/Csp/Test/Unit/Model/SubresourceIntegrityRepositoryTest.php b/app/code/Magento/Csp/Test/Unit/Model/SubresourceIntegrityRepositoryTest.php new file mode 100644 index 0000000000000..0b7a55d896f04 --- /dev/null +++ b/app/code/Magento/Csp/Test/Unit/Model/SubresourceIntegrityRepositoryTest.php @@ -0,0 +1,129 @@ +cacheMock = $this->getMockBuilder(CacheInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['save', 'load']) + ->getMockForAbstractClass(); + $this->serializerMock = $this->getMockBuilder(SerializerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['serialize', 'unserialize']) + ->getMockForAbstractClass(); + $this->integrityFactoryMock = $this->getMockBuilder(SubresourceIntegrityFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->subresourceIntegrityRepository = new SubresourceIntegrityRepository( + $this->cacheMock, + $this->serializerMock, + $this->integrityFactoryMock + ); + } + + /** Test save repository + * + * + * @return void + */ + public function testSave(): void + { + $data = new SubresourceIntegrity( + [ + 'hash' => 'testhash', + 'path' => 'js/jquery.js' + ] + ); + + $expected[$data->getPath()] = $data->getHash(); + $serialized = json_encode($expected); + $this->cacheMock->expects($this->once())->method('load')->willReturn(false); + $this->serializerMock->expects($this->once())->method('serialize')->with($expected)->willReturn($serialized); + $this->cacheMock->expects($this->once())->method('save')->willReturn(true); + $this->assertTrue($this->subresourceIntegrityRepository->save($data)); + } + + /** Test that cache saves in bunch + * + * + * @return void + */ + public function testSaveBunch(): void + { + $bunch1 = new SubresourceIntegrity( + [ + 'hash' => 'testhash', + 'path' => 'js/jquery.js' + ] + ); + + $bunch2 = new SubresourceIntegrity( + [ + 'hash' => 'testhash2', + 'path' => 'js/test.js' + ] + ); + + $bunches = [$bunch1, $bunch2]; + + $expected = []; + + foreach ($bunches as $bunch) { + $expected[$bunch->getPath()] = $bunch->getHash(); + } + $serializedBunch = json_encode($expected); + $this->cacheMock->expects($this->once())->method('load')->willReturn(false); + $this->serializerMock->expects($this->once())->method('serialize') + ->with($expected)->willReturn($serializedBunch); + $this->cacheMock->expects($this->once())->method('save')->willReturn(true); + $this->assertTrue($this->subresourceIntegrityRepository->saveBunch($bunches)); + } +} diff --git a/app/code/Magento/Csp/Test/Unit/Plugin/AddDefaultPropertiesToGroupPluginTest.php b/app/code/Magento/Csp/Test/Unit/Plugin/AddDefaultPropertiesToGroupPluginTest.php new file mode 100644 index 0000000000000..3e6b08f17bb7b --- /dev/null +++ b/app/code/Magento/Csp/Test/Unit/Plugin/AddDefaultPropertiesToGroupPluginTest.php @@ -0,0 +1,113 @@ +integrityRepositoryPoolMock = $this->getMockBuilder(SubresourceIntegrityRepositoryPool::class) + ->disableOriginalConstructor() + ->onlyMethods(['get']) + ->getMock(); + $this->assetInterfaceMock = $this->getMockBuilder(File::class) + ->disableOriginalConstructor() + ->onlyMethods(['getPath']) + ->getMockForAbstractClass(); + $this->stateMock = $this->getMockBuilder(State::class) + ->disableOriginalConstructor() + ->onlyMethods(['getAreaCode']) + ->getMock(); + $this->plugin = new AddDefaultPropertiesToGroupPlugin( + $this->stateMock, + $this->integrityRepositoryPoolMock + ); + } + + /** + * Test for plugin with Js assets + * + * @return void + */ + public function testBeforeGetFilteredProperties(): void + { + $integrityRepositoryMock = $this->getMockBuilder(SubresourceIntegrityRepository::class) + ->disableOriginalConstructor() + ->onlyMethods(['getByPath']) + ->getMock(); + $groupedCollectionMock = $this->getMockBuilder(GroupedCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $path = 'jquery.js'; + $area = 'base'; + + $data = new SubresourceIntegrity( + [ + 'hash' => 'testhash', + 'path' => $path + ] + ); + $properties['attributes']['integrity'] = $data->getHash(); + $properties['attributes']['crossorigin'] = 'anonymous'; + $expected = [$this->assetInterfaceMock, $properties]; + $this->integrityRepositoryPoolMock->expects($this->once())->method('get')->with($area) + ->willReturn( + $integrityRepositoryMock + ); + $this->assetInterfaceMock->expects($this->once())->method('getPath')->willReturn($path); + $integrityRepositoryMock->expects($this->once())->method('getByPath')->with($path)->willReturn($data); + $this->assertEquals( + $expected, + $this->plugin->beforeGetFilteredProperties( + $groupedCollectionMock, + $this->assetInterfaceMock + ) + ); + } +} diff --git a/app/code/Magento/Csp/Test/Unit/Plugin/StoreAssetIntegrityHashesTest.php b/app/code/Magento/Csp/Test/Unit/Plugin/StoreAssetIntegrityHashesTest.php new file mode 100644 index 0000000000000..7ac1d462015c7 --- /dev/null +++ b/app/code/Magento/Csp/Test/Unit/Plugin/StoreAssetIntegrityHashesTest.php @@ -0,0 +1,96 @@ +integrityRepositoryPoolMock = $this->getMockBuilder(SubresourceIntegrityRepositoryPool::class) + ->disableOriginalConstructor() + ->onlyMethods(['get']) + ->getMock(); + $this->integrityCollectorMock = $this->getMockBuilder(SubresourceIntegrityCollector::class) + ->disableOriginalConstructor() + ->onlyMethods(['release']) + ->getMock(); + $this->plugin = new StoreAssetIntegrityHashes( + $this->integrityCollectorMock, + $this->integrityRepositoryPoolMock, + ); + } + + /** + * Test After Deploy method of plugin + * + * @return void + * @doesNotPerformAssertions + */ + public function testAfterDeploy(): void + { + $bunch1 = new SubresourceIntegrity( + [ + 'hash' => 'testhash', + 'path' => 'adminhtml/js/jquery.js' + ] + ); + + $bunch2 = new SubresourceIntegrity( + [ + 'hash' => 'testhash2', + 'path' => 'frontend/js/test.js' + ] + ); + + $bunches = [$bunch1, $bunch2]; + $deployStaticContentMock = $this->getMockBuilder(DeployStaticContent::class) + ->disableOriginalConstructor() + ->getMock(); + $subResourceIntegrityMock = $this->getMockBuilder(SubresourceIntegrityRepository::class) + ->disableOriginalConstructor() + ->onlyMethods(['saveBunch']) + ->getMock(); + $this->integrityCollectorMock->expects($this->once())->method('release')->willReturn($bunches); + $this->integrityRepositoryPoolMock->expects($this->any())->method('get')->willReturn($subResourceIntegrityMock); + $subResourceIntegrityMock->expects($this->any())->method('saveBunch')->willReturn(true); + $this->plugin->afterDeploy($deployStaticContentMock, null, []); + } +} diff --git a/app/code/Magento/Csp/composer.json b/app/code/Magento/Csp/composer.json index 2079a30d92068..f53a3656b072a 100644 --- a/app/code/Magento/Csp/composer.json +++ b/app/code/Magento/Csp/composer.json @@ -1,19 +1,22 @@ { "name": "magento/module-csp", "description": "CSP module enables Content Security Policies for Magento", + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], "config": { "sort-packages": true }, + "version": "100.4.4-p8", "require": { "php": "~7.4.0||~8.1.0", - "magento/framework": "*", - "magento/module-store": "*" + "magento/framework": "103.0.*", + "magento/module-store": "101.1.*", + "magento/module-require-js": "100.4.*", + "magento/module-deploy": "100.4.*" }, - "type": "magento2-module", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], "autoload": { "files": [ "registration.php" @@ -23,3 +26,4 @@ } } } + diff --git a/app/code/Magento/Csp/etc/acl.xml b/app/code/Magento/Csp/etc/acl.xml new file mode 100644 index 0000000000000..da62f053ab4a1 --- /dev/null +++ b/app/code/Magento/Csp/etc/acl.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Csp/etc/adminhtml/system.xml b/app/code/Magento/Csp/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..38b2a6834bdb3 --- /dev/null +++ b/app/code/Magento/Csp/etc/adminhtml/system.xml @@ -0,0 +1,35 @@ + + + + +
+ + security + Magento_Csp::config + + + + + + + URI to report CSP violations on storefront. Used for all storefront pages that don't have own URI configured above. + validate-url + + + + + + + URI to report CSP violations in admin area. Used for all admin pages that don't have own URI configured above. + validate-url + + + +
+
+
diff --git a/app/code/Magento/Csp/etc/di.xml b/app/code/Magento/Csp/etc/di.xml index b6a887ab9d943..13c1f225a00f2 100644 --- a/app/code/Magento/Csp/etc/di.xml +++ b/app/code/Magento/Csp/etc/di.xml @@ -111,4 +111,27 @@ Magento\Csp\Model\BlockCache + + + + Magento\Csp\Model\Deploy\Package\Processor\PostProcessor\Integrity + + + + + + + + + + + + + + + + + Magento\Framework\Filesystem\Driver\File + + diff --git a/app/code/Magento/Csp/view/adminhtml/layout/sales_order_create_index.xml b/app/code/Magento/Csp/view/adminhtml/layout/sales_order_create_index.xml new file mode 100644 index 0000000000000..73fe99913466c --- /dev/null +++ b/app/code/Magento/Csp/view/adminhtml/layout/sales_order_create_index.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/app/code/Magento/Csp/view/base/templates/sri/hashes.phtml b/app/code/Magento/Csp/view/base/templates/sri/hashes.phtml new file mode 100644 index 0000000000000..e9d666a50550d --- /dev/null +++ b/app/code/Magento/Csp/view/base/templates/sri/hashes.phtml @@ -0,0 +1,18 @@ + + +getSerialized(); +$scriptString = <<', $content ); - $this->assertStringContainsString("", $content); + $this->assertStringContainsString("\n let myVar = 1;\n", $content); $header = $this->getResponse()->getHeader('Content-Security-Policy'); $this->assertNotEmpty($header); $this->assertStringContainsString('http://my.magento.com', $header->getFieldValue()); - $this->assertStringContainsString('\'sha256-H4RRnauTM2X2Xg/z9zkno1crqhsaY3uKKu97uwmnXXE=\'', $header->getFieldValue()); } } diff --git a/dev/tests/integration/testsuite/Magento/Csp/Helper/CspNonceProviderMock.php b/dev/tests/integration/testsuite/Magento/Csp/Helper/CspNonceProviderMock.php new file mode 100644 index 0000000000000..75ec9abbe1770 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Csp/Helper/CspNonceProviderMock.php @@ -0,0 +1,68 @@ +random = $random; + $this->dynamicCollector = $dynamicCollector; + } + + /** + * Generate nonce and add it to the CSP header + * + * @return string + */ + public function generateNonce(): string + { + $cspNonce = 'nonce-1234567890abcdef'; + + $policy = new FetchPolicy( + 'script-src', + false, + [], + [], + false, + false, + false, + [$cspNonce], + [] + ); + + $this->dynamicCollector->add($policy); + + return $cspNonce; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Csp/Helper/InlineUtilTest.php b/dev/tests/integration/testsuite/Magento/Csp/Helper/InlineUtilTest.php index ab1b702ab1a00..89ac8ab50e68f 100644 --- a/dev/tests/integration/testsuite/Magento/Csp/Helper/InlineUtilTest.php +++ b/dev/tests/integration/testsuite/Magento/Csp/Helper/InlineUtilTest.php @@ -44,7 +44,8 @@ public function setUp(): void { Bootstrap::getObjectManager()->configure([ 'preferences' => [ - DynamicCollector::class => DynamicCollectorMock::class + DynamicCollector::class => DynamicCollectorMock::class, + CspNonceProvider::class => CspNonceProviderMock::class ] ]); $this->util = Bootstrap::getObjectManager()->get(InlineUtil::class); @@ -116,7 +117,7 @@ public function getTags(): array 'script', ['type' => 'text/javascript'], "\n let someVar = 25;\n document.getElementById('test').innerText = someVar;\n", - "", [ new FetchPolicy( @@ -127,8 +128,8 @@ public function getTags(): array false, false, false, - [], - ['U+SKpEef030N2YgyKKdIBIvPy8Fmd42N/JcTZgQV+DA=' => 'sha256'] + ['nonce-1234567890abcdef'], + [] ) ] ], diff --git a/dev/tests/integration/testsuite/Magento/Csp/Model/Collector/ConfigCollectorTest.php b/dev/tests/integration/testsuite/Magento/Csp/Model/Collector/ConfigCollectorTest.php index cf6287ed5b4e1..e8e1365489abb 100644 --- a/dev/tests/integration/testsuite/Magento/Csp/Model/Collector/ConfigCollectorTest.php +++ b/dev/tests/integration/testsuite/Magento/Csp/Model/Collector/ConfigCollectorTest.php @@ -12,6 +12,7 @@ use Magento\Csp\Model\Policy\FlagPolicy; use Magento\Csp\Model\Policy\PluginTypesPolicy; use Magento\Csp\Model\Policy\SandboxPolicy; +use Magento\Framework\App\RequestInterface; use PHPUnit\Framework\TestCase; use Magento\TestFramework\Helper\Bootstrap; @@ -25,12 +26,18 @@ class ConfigCollectorTest extends TestCase */ private $collector; + /** + * @var RequestInterface + */ + private $request; + /** * @inheritDoc */ protected function setUp(): void { $this->collector = Bootstrap::getObjectManager()->get(ConfigCollector::class); + $this->request = Bootstrap::getObjectManager()->get(RequestInterface::class); } /** @@ -68,7 +75,7 @@ private function getExpectedPolicies(): array 'manifest-src' => new FetchPolicy('manifest-src', false, [], [], true), 'media-src' => new FetchPolicy('media-src', false, [], [], true), 'object-src' => new FetchPolicy('object-src', false, [], [], true), - 'script-src' => new FetchPolicy('script-src', false, [], [], true, false, false, [], [], false, true), + 'script-src' => new FetchPolicy('script-src', false, [], [], true, true, true, [], [], false, true), 'style-src' => new FetchPolicy('style-src', false, [], [], true), 'base-uri' => new FetchPolicy('base-uri', false, [], [], true), 'plugin-types' => new PluginTypesPolicy( @@ -172,11 +179,18 @@ private function getExpectedPolicies(): array * @magentoConfigFixture default_store csp/policies/storefront/mixed_content/policy_id block-all-mixed-content * @magentoConfigFixture default_store csp/policies/storefront/base/policy_id base-uri * @magentoConfigFixture default_store csp/policies/storefront/base/inline 0 + * @magentoConfigFixture default_store csp/policies/storefront_checkout_index_index/scripts/policy_id script-src + * @magentoConfigFixture default_store csp/policies/storefront_checkout_index_index/scripts/self 1 + * @magentoConfigFixture default_store csp/policies/storefront_checkout_index_index/scripts/inline 1 + * @magentoConfigFixture default_store csp/policies/storefront_checkout_index_index/scripts/eval 1 * @magentoConfigFixture default_store csp/policies/storefront/upgrade/policy_id upgrade-insecure-requests * @return void */ public function testCollecting(): void { + $this->request->setRouteName('checkout'); + $this->request->setControllerName('index'); + $this->request->setActionName('index'); $policies = $this->collector->collect([new FlagPolicy('upgrade-insecure-requests')]); $expectedPolicies = $this->getExpectedPolicies(); $this->assertNotEmpty($policies); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php index bd2c26e449d72..8b1f393f00ee4 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php @@ -421,6 +421,8 @@ public function testCreateNewCustomerWithPasswordHashWithNotAllowedCountry(): vo $customerData = $this->customerRepository->getById($customerId); $customerData->getAddresses()[1]->setRegion(null)->setCountryId($allowedCountryIdForSecondWebsite) ->setRegionId(null); + $customerData->getAddresses()[1]->setIsDefaultBilling(true); + $customerData->getAddresses()[1]->setIsDefaultShipping(true); $customerData->setStoreId($store->getId())->setWebsiteId($store->getWebsiteId())->setId(null); $password = $this->random->getRandomString(8); $passwordHash = $this->encryptor->getHash($password, true); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index de4a592f46c9e..b7c2dea1b5e02 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -9,6 +9,7 @@ use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\AddressRepositoryInterface; use Magento\Customer\Api\Data\AddressInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\State\ExpiredException; @@ -16,6 +17,7 @@ use Magento\Framework\Session\SessionManagerInterface; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Url as UrlBuilder; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; @@ -331,7 +333,7 @@ public function testValidateResetPasswordLinkTokenExpired() public function testValidateResetPasswordLinkTokenInvalid() { $resetToken = 'lsdj579slkj5987slkj595lkj'; - $invalidToken = 0; + $invalidToken = '0'; $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s'); try { $this->accountManagement->validateResetPasswordLinkToken(1, $invalidToken); @@ -481,7 +483,7 @@ public function testResetPasswordTokenExpired() public function testResetPasswordTokenInvalid() { $resetToken = 'lsdj579slkj5987slkj595lkj'; - $invalidToken = 0; + $invalidToken = '0'; $password = 'new_Password123'; $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s'); @@ -604,7 +606,18 @@ public function testResendConfirmationNotNeeded() */ public function testIsEmailAvailable() { - $this->assertFalse($this->accountManagement->isEmailAvailable('customer@example.com', 1)); + $scopeConfig = $this->objectManager->get(ScopeConfigInterface::class); + $guestLoginConfig = $scopeConfig->getValue( + AccountManagement::GUEST_CHECKOUT_LOGIN_OPTION_SYS_CONFIG, + ScopeInterface::SCOPE_WEBSITE, + 1 + ); + + if (!$guestLoginConfig) { + $this->assertTrue($this->accountManagement->isEmailAvailable('customer@example.com', 1)); + } else { + $this->assertFalse($this->accountManagement->isEmailAvailable('customer@example.com', 1)); + } } /** @@ -612,7 +625,18 @@ public function testIsEmailAvailable() */ public function testIsEmailAvailableNoWebsiteSpecified() { - $this->assertFalse($this->accountManagement->isEmailAvailable('customer@example.com')); + $scopeConfig = $this->objectManager->get(ScopeConfigInterface::class); + $guestLoginConfig = $scopeConfig->getValue( + AccountManagement::GUEST_CHECKOUT_LOGIN_OPTION_SYS_CONFIG, + ScopeInterface::SCOPE_WEBSITE, + 1 + ); + + if (!$guestLoginConfig) { + $this->assertTrue($this->accountManagement->isEmailAvailable('customer@example.com')); + } else { + $this->assertFalse($this->accountManagement->isEmailAvailable('customer@example.com')); + } } /** diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php index f584b8f7cfcd3..d886a9189502b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/Element/AbstractBlockTest.php @@ -26,6 +26,9 @@ class AbstractBlockTest extends \PHPUnit\Framework\TestCase */ protected $_layout = null; + /** + * @var array + */ protected static $_mocks = []; /** @@ -573,7 +576,7 @@ public function testGetCacheKey() $this->assertNotEquals($name, $key); $block->setCacheKey('key'); - $this->assertEquals(AbstractBlock::CACHE_KEY_PREFIX . 'key', $block->getCacheKey()); + $this->assertEquals(AbstractBlock::CUSTOM_CACHE_KEY_PREFIX . 'key', $block->getCacheKey()); } /** diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php index 63670e9cb458d..f0c5ce25911f4 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php @@ -8,12 +8,15 @@ namespace Magento\Newsletter\Controller\Subscriber; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\AccountManagement; use Magento\Customer\Model\Session; use Magento\Customer\Model\Url; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Newsletter\Model\ResourceModel\Subscriber as SubscriberResource; use Magento\Newsletter\Model\ResourceModel\Subscriber\CollectionFactory; use Magento\Newsletter\Model\ResourceModel\Subscriber\Grid\Collection as GridCollection; +use Magento\Store\Model\ScopeInterface; use Magento\TestFramework\TestCase\AbstractController; use Laminas\Stdlib\Parameters; @@ -222,8 +225,18 @@ public function testWithEmailAssignedToAnotherCustomer(): void $this->session->loginById(1); $this->prepareRequest('customer2@search.example.com'); $this->dispatch('newsletter/subscriber/new'); + $scopeConfig = $this->_objectManager->get(ScopeConfigInterface::class); + $guestLoginConfig = $scopeConfig->getValue( + AccountManagement::GUEST_CHECKOUT_LOGIN_OPTION_SYS_CONFIG, + ScopeInterface::SCOPE_WEBSITE, + 1 + ); - $this->performAsserts('This email address is already assigned to another user.'); + if ($guestLoginConfig) { + $this->performAsserts('This email address is already assigned to another user.'); + } else { + $this->performAsserts('This email address is already subscribed.'); + } } /** diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php index 41300fbd81757..1ea4e7a2ddf70 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/Quote/AddressTest.php @@ -172,7 +172,7 @@ public function testSameAsBillingWhenCustomerHasNoDefaultShippingAddress($unsetI /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager() ->create(AddressRepositoryInterface::class); - $this->_customer->setDefaultShipping(-1) + $this->_customer->setDefaultShipping(1) ->setAddresses( [ $addressRepository->getById($this->_address->getId()), @@ -211,7 +211,7 @@ public function testSameAsBillingWhenCustomerHasDefaultShippingAddress() /** @var AddressRepositoryInterface $addressRepository */ $addressRepository = Bootstrap::getObjectManager() ->create(AddressRepositoryInterface::class); - $this->_customer->setDefaultShipping(2) + $this->_customer->setDefaultShipping(1) ->setAddresses([$addressRepository->getById($this->_address->getId())]); $this->_customer = $this->customerRepository->save($this->_customer); // we should save the customer data in order to be able to use it diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php index 2d3ee063ab5a3..8163ec06174a9 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php @@ -33,7 +33,7 @@ * Tests for quote model. * * @see \Magento\Quote\Model\Quote - * + * @magentoAppIsolation enabled * @magentoDbIsolation enabled * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php index bbd6d27d688df..d0b4038521398 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php @@ -21,6 +21,11 @@ */ class SaveTest extends AbstractCreditmemoControllerTest { + /** + * @var string + */ + protected $resource = 'Magento_Sales::creditmemo'; + /** * @var string */ diff --git a/dev/tests/integration/testsuite/Magento/Sales/Helper/AdminTest.php b/dev/tests/integration/testsuite/Magento/Sales/Helper/AdminTest.php index 7b41f9890d74a..17f402faf2c5a 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Helper/AdminTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Helper/AdminTest.php @@ -76,7 +76,7 @@ public function escapeHtmlWithLinksDataProvider(): array ], [ 'Foo', - 'Foo', + 'Foo', 'allowedTags' => ['a'], ], [ @@ -86,7 +86,7 @@ public function escapeHtmlWithLinksDataProvider(): array ], [ "Foo", - 'Foo', + 'Foo', 'allowedTags' => ['a'], ], [ diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php index 661987ad26627..c129f047057b3 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Rule/Action/Discount/CartFixedTest.php @@ -47,6 +47,11 @@ */ class CartFixedTest extends TestCase { + /** + * @var float + */ + private const EPSILON = 0.0000000001; + /** * @var GuestCartManagementInterface */ @@ -521,11 +526,11 @@ public function testDiscountsWhenByPercentRuleAppliedFirstAndCartFixedRuleSecond $item = array_shift($items); $this->assertEquals('simple1', $item->getSku()); $this->assertEquals(5.99, $item->getPrice()); - $this->assertEquals($expectedDiscounts[$item->getSku()], $item->getDiscountAmount()); + $this->assertEqualsWithDelta($expectedDiscounts[$item->getSku()], $item->getDiscountAmount(), self::EPSILON); $item = array_shift($items); $this->assertEquals('simple2', $item->getSku()); $this->assertEquals(15.99, $item->getPrice()); - $this->assertEquals($expectedDiscounts[$item->getSku()], $item->getDiscountAmount()); + $this->assertEqualsWithDelta($expectedDiscounts[$item->getSku()], $item->getDiscountAmount(), self::EPSILON); } public function discountByPercentDataProvider() diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php index e3374064aa3cb..043ba86dce3ec 100644 --- a/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php @@ -18,6 +18,11 @@ */ class SaveTest extends AbstractShipmentControllerTest { + /** + * @var string + */ + protected $resource = 'Magento_Sales::ship'; + /** * @var string */ @@ -105,8 +110,7 @@ private function prepareRequest(array $params = []) ] ); - $data = $params ?? []; - $this->getRequest()->setPostValue($data); + $this->getRequest()->setPostValue($params); return $order; } diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php index a119b6259b5f6..c75a6c0819b58 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/Sales/Total/Quote/TaxTest.php @@ -23,6 +23,11 @@ */ class TaxTest extends \Magento\TestFramework\Indexer\TestCase { + /** + * @var float + */ + private const EPSILON = 0.0000000001; + /** * Utility object for setting up tax rates, tax classes and tax rules * @@ -176,7 +181,12 @@ public function testFullDiscountWithDeltaRoundingFix() protected function verifyItem($item, $expectedItemData) { foreach ($expectedItemData as $key => $value) { - $this->assertEquals($value, $item->getData($key), 'item ' . $key . ' is incorrect'); + $this->assertEqualsWithDelta( + $value, + $item->getData($key), + self::EPSILON, + 'item ' . $key . ' is incorrect' + ); } return $this; @@ -247,7 +257,12 @@ protected function verifyQuoteAddress($quoteAddress, $expectedAddressData) if ($key == 'applied_taxes') { $this->verifyAppliedTaxes($quoteAddress->getAppliedTaxes(), $value); } else { - $this->assertEquals($value, $quoteAddress->getData($key), 'Quote address ' . $key . ' is incorrect'); + $this->assertEqualsWithDelta( + $value, + $quoteAddress->getData($key), + self::EPSILON, + 'Quote address ' . $key . ' is incorrect' + ); } } diff --git a/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php b/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php index 604a444883a26..fde67cc7c2094 100644 --- a/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php +++ b/dev/tests/integration/testsuite/Magento/Tax/Model/TaxCalculationTest.php @@ -16,15 +16,16 @@ class TaxCalculationTest extends \PHPUnit\Framework\TestCase { /** - * Object Manager - * + * @var float + */ + private const EPSILON = 0.0000000001; + + /** * @var \Magento\Framework\ObjectManagerInterface */ private $objectManager; /** - * Tax calculation service - * * @var \Magento\Tax\Api\TaxCalculationInterface */ private $taxCalculationService; @@ -108,7 +109,7 @@ public function testCalculateTaxUnitBased($quoteDetailsData, $expected) ); $taxDetails = $this->taxCalculationService->calculateTax($quoteDetails, 1); - $this->assertEquals($expected, $this->convertObjectToArray($taxDetails)); + $this->assertEqualsWithDelta($expected, $this->convertObjectToArray($taxDetails), self::EPSILON); } /** @@ -1286,7 +1287,7 @@ public function testCalculateTaxRowBased($quoteDetailsData, $expectedTaxDetails) $taxDetails = $this->taxCalculationService->calculateTax($quoteDetails); - $this->assertEquals($expectedTaxDetails, $this->convertObjectToArray($taxDetails)); + $this->assertEqualsWithDelta($expectedTaxDetails, $this->convertObjectToArray($taxDetails), self::EPSILON); } /** @@ -2387,7 +2388,11 @@ public function testMultiRulesRowBased($quoteDetailsData, $expectedTaxDetails) $taxDetails = $this->taxCalculationService->calculateTax($quoteDetails); - $this->assertEquals($expectedTaxDetails, $this->convertObjectToArray($taxDetails)); + $this->assertEqualsWithDelta( + $expectedTaxDetails, + $this->convertObjectToArray($taxDetails), + self::EPSILON + ); } /** @@ -2424,7 +2429,7 @@ public function testMultiRulesTotalBased($quoteDetailsData, $expectedTaxDetails) $taxDetails = $this->taxCalculationService->calculateTax($quoteDetails); - $this->assertEquals($expectedTaxDetails, $this->convertObjectToArray($taxDetails)); + $this->assertEqualsWithDelta($expectedTaxDetails, $this->convertObjectToArray($taxDetails), self::EPSILON); } /** @@ -2471,7 +2476,7 @@ public function testMultiRulesUnitBased($quoteDetailsData, $expectedTaxDetails) $taxDetails = $this->taxCalculationService->calculateTax($quoteDetails); - $this->assertEquals($expectedTaxDetails, $this->convertObjectToArray($taxDetails)); + $this->assertEqualsWithDelta($expectedTaxDetails, $this->convertObjectToArray($taxDetails), self::EPSILON); } /** diff --git a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php index ce271e5102099..0e20b6723b6e0 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php @@ -14,13 +14,13 @@ use Magento\Framework\HTTP\AsyncClient\Response; use Magento\Framework\HTTP\AsyncClientInterface; use Magento\Quote\Model\Quote\Address\RateRequest; +use Magento\Quote\Model\Quote\Address\RateRequestFactory; use Magento\Quote\Model\Quote\Address\RateResult\Error; +use Magento\Shipping\Model\Shipment\Request; use Magento\TestFramework\Helper\Bootstrap; -use Magento\Quote\Model\Quote\Address\RateRequestFactory; use Magento\TestFramework\HTTP\AsyncClientInterfaceMock; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; -use Magento\Shipping\Model\Shipment\Request; +use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; /** @@ -113,29 +113,54 @@ public function testGetShipConfirmUrlLive() } /** - * Collect free rates. + * Collect rates for UPS Ground method. * * @magentoConfigFixture current_store carriers/ups/active 1 - * @magentoConfigFixture current_store carriers/ups/type UPS - * @magentoConfigFixture current_store carriers/ups/allowed_methods 1DA,GND - * @magentoConfigFixture current_store carriers/ups/free_method GND + * @magentoConfigFixture current_store carriers/ups/type UPS_XML + * @magentoConfigFixture current_store carriers/ups/allowed_methods 03 + * @magentoConfigFixture current_store carriers/ups/free_method 03 + * @magentoConfigFixture default_store carriers/ups/shipper_number 12345 + * @magentoConfigFixture default_store carriers/ups/origin_shipment Shipments Originating in the United States + * @magentoConfigFixture default_store carriers/ups/username user + * @magentoConfigFixture default_store carriers/ups/password pass + * @magentoConfigFixture default_store carriers/ups/access_license_number acn + * @magentoConfigFixture default_store carriers/ups/debug 1 + * @magentoConfigFixture default_store currency/options/allow USD,EUR + * @magentoConfigFixture default_store currency/options/base USD */ public function testCollectFreeRates() { - $rateRequest = Bootstrap::getObjectManager()->get(RateRequestFactory::class)->create(); - $rateRequest->setDestCountryId('US'); - $rateRequest->setDestRegionId('CA'); - $rateRequest->setDestPostcode('90001'); - $rateRequest->setPackageQty(1); - $rateRequest->setPackageWeight(1); - $rateRequest->setFreeMethodWeight(0); - $rateRequest->setLimitCarrier($this->carrier::CODE); - $rateRequest->setFreeShipping(true); - $rateResult = $this->carrier->collectRates($rateRequest); - $result = $rateResult->asArray(); - $methods = $result[$this->carrier::CODE]['methods']; - $this->assertEquals(0, $methods['GND']['price']); - $this->assertNotEquals(0, $methods['1DA']['price']); + $request = Bootstrap::getObjectManager()->create( + RateRequest::class, + [ + 'data' => [ + 'dest_country' => 'US', + 'dest_postal' => '90001', + 'package_weight' => '1', + 'package_qty' => '1', + 'free_method_weight' => '5', + 'product' => '11', + 'action' => 'Rate', + 'unit_measure' => 'KGS', + 'free_shipping' => '1', + 'base_currency' => new DataObject(['code' => 'USD']) + ] + ] + ); + //phpcs:disable Magento2.Functions.DiscouragedFunction + $this->httpClient->nextResponses( + [ + new Response( + 200, + [], + file_get_contents(__DIR__ . "/../_files/ups_rates_response_option9.xml") + ) + ] + ); + + $rates = $this->carrier->collectRates($request)->getAllRates(); + $this->assertEquals('19.19', $rates[0]->getPrice()); + $this->assertEquals('03', $rates[0]->getMethod()); } /** @@ -181,7 +206,7 @@ public function testCollectRates(int $negotiable, int $tax, int $responseId, str new Response( 200, [], - file_get_contents(__DIR__ ."/../_files/ups_rates_response_option$responseId.xml") + file_get_contents(__DIR__ . "/../_files/ups_rates_response_option$responseId.xml") ) ] ); @@ -283,9 +308,9 @@ public function collectRatesDataProvider() public function testRequestToShipment(): void { //phpcs:disable Magento2.Functions.DiscouragedFunction - $expectedShipmentRequest = file_get_contents(__DIR__ .'/../_files/ShipmentConfirmRequest.xml'); - $shipmentResponse = file_get_contents(__DIR__ .'/../_files/ShipmentConfirmResponse.xml'); - $acceptResponse = file_get_contents(__DIR__ .'/../_files/ShipmentAcceptResponse.xml'); + $expectedShipmentRequest = file_get_contents(__DIR__ . '/../_files/ShipmentConfirmRequest.xml'); + $shipmentResponse = file_get_contents(__DIR__ . '/../_files/ShipmentConfirmResponse.xml'); + $acceptResponse = file_get_contents(__DIR__ . '/../_files/ShipmentAcceptResponse.xml'); //phpcs:enable Magento2.Functions.DiscouragedFunction $this->httpClient->nextResponses( [ diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/ups_rates_response_option9.xml b/dev/tests/integration/testsuite/Magento/Ups/_files/ups_rates_response_option9.xml new file mode 100644 index 0000000000000..2e5c4bc0ddbfd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Ups/_files/ups_rates_response_option9.xml @@ -0,0 +1,299 @@ + + + + + + Rating and Service + 1.0 + + 1 + Success + + + + 12 + + Your invoice may vary from the displayed reference rates + + + LBS + + 20.0 + + + USD + 43.19 + + + USD + 0.00 + + + USD + 43.19 + + 3 + + + + USD + 43.19 + + + USD + 0.00 + + + USD + 43.19 + + 20.0 + + + LBS + + 20.0 + + + + + + 14 + + Your invoice may vary from the displayed reference rates + + + LBS + + 20.0 + + + USD + 122.34 + + + USD + 0.00 + + + USD + 122.34 + + 1 + 8:00 A.M. + + + USD + 122.34 + + + USD + 0.00 + + + USD + 122.34 + + 20.0 + + + LBS + + 20.0 + + + + + + 03 + + Your invoice may vary from the displayed reference rates + + + LBS + + 20.0 + + + USD + 19.19 + + + USD + 0.00 + + + USD + 19.19 + + + + + + USD + 19.19 + + + USD + 0.00 + + + USD + 19.19 + + 20.0 + + + LBS + + 20.0 + + + + + + 13 + + Your invoice may vary from the displayed reference rates + + + LBS + + 20.0 + + + USD + 82.24 + + + USD + 0.00 + + + USD + 82.24 + + 1 + + + + USD + 82.24 + + + USD + 0.00 + + + USD + 82.24 + + 20.0 + + + LBS + + 20.0 + + + + + + 01 + + Your invoice may vary from the displayed reference rates + + + LBS + + 20.0 + + + USD + 88.14 + + + USD + 0.00 + + + USD + 88.14 + + 1 + 10:30 A.M. + + + USD + 88.14 + + + USD + 0.00 + + + USD + 88.14 + + 20.0 + + + LBS + + 20.0 + + + + + + 02 + + Your invoice may vary from the displayed reference rates + + + LBS + + 20.0 + + + USD + 57.11 + + + USD + 0.00 + + + USD + 57.11 + + 2 + + + + USD + 57.11 + + + USD + 0.00 + + + USD + 57.11 + + 20.0 + + + LBS + + 20.0 + + + + diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.mariadb10427.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.mariadb10427.php new file mode 100644 index 0000000000000..f00c9a1eb08c4 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.mariadb10427.php @@ -0,0 +1,59 @@ + 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int(10) unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint(5) unsigned DEFAULT 0, + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'reference_table' => 'CREATE TABLE `reference_table` ( + `tinyint_ref` tinyint(4) NOT NULL AUTO_INCREMENT, + `tinyint_without_padding` tinyint(4) NOT NULL DEFAULT 0, + `bigint_without_padding` bigint(20) NOT NULL DEFAULT 0, + `smallint_without_padding` smallint(6) NOT NULL DEFAULT 0, + `integer_without_padding` int(11) NOT NULL DEFAULT 0, + `smallint_with_big_padding` smallint(6) NOT NULL DEFAULT 0, + `smallint_without_default` smallint(6) DEFAULT NULL, + `int_without_unsigned` int(11) DEFAULT NULL, + `int_unsigned` int(10) unsigned DEFAULT NULL, + `bigint_default_nullable` bigint(20) unsigned DEFAULT 1, + `bigint_not_default_not_nullable` bigint(20) unsigned NOT NULL, + `smallint_ref` smallint(6) NOT NULL DEFAULT 0, + PRIMARY KEY (`tinyint_ref`,`smallint_ref`), + UNIQUE KEY `REFERENCE_TABLE_SMALLINT_REF` (`smallint_ref`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'test_table' => 'CREATE TABLE `test_table` ( + `smallint` smallint(6) DEFAULT NULL, + `tinyint` tinyint(4) DEFAULT NULL, + `bigint` bigint(20) DEFAULT 0, + `float` float(12,10) DEFAULT 0.0000000000, + `double` double(245,10) DEFAULT 11111111.1111110000, + `decimal` decimal(15,4) DEFAULT 0.0000, + `date` date DEFAULT NULL, + `timestamp` timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), + `datetime` datetime DEFAULT \'0000-00-00 00:00:00\', + `longtext` longtext DEFAULT NULL, + `mediumtext` mediumtext DEFAULT NULL, + `varchar` varchar(254) DEFAULT NULL, + `char` char(255) DEFAULT NULL, + `mediumblob` mediumblob DEFAULT NULL, + `blob` blob DEFAULT NULL, + `boolean` tinyint(1) DEFAULT NULL, + `integer_main` int(10) unsigned DEFAULT NULL, + `smallint_main` smallint(6) NOT NULL DEFAULT 0, + UNIQUE KEY `TEST_TABLE_SMALLINT_FLOAT` (`smallint`,`float`), + UNIQUE KEY `TEST_TABLE_DOUBLE` (`double`), + KEY `TEST_TABLE_TINYINT_BIGINT` (`tinyint`,`bigint`), + KEY `TEST_TABLE_SMALLINT_MAIN_REFERENCE_TABLE_SMALLINT_REF` (`smallint_main`), + KEY `FK_FB77604C299EB8612D01E4AF8D9931F2` (`integer_main`), + CONSTRAINT `FK_FB77604C299EB8612D01E4AF8D9931F2` FOREIGN KEY (`integer_main`) REFERENCES `auto_increment_test` (`int_auto_increment_with_nullable`) ON DELETE CASCADE, + CONSTRAINT `TEST_TABLE_SMALLINT_MAIN_REFERENCE_TABLE_SMALLINT_REF` FOREIGN KEY (`smallint_main`) REFERENCES `reference_table` (`smallint_ref`) ON DELETE CASCADE, + CONSTRAINT `TEST_TABLE_TINYINT_REFERENCE_TABLE_TINYINT_REF` FOREIGN KEY (`tinyint`) REFERENCES `reference_table` (`tinyint_ref`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.mysql829.php new file mode 100644 index 0000000000000..65a70da8d660c --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.mysql829.php @@ -0,0 +1,59 @@ + 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint unsigned DEFAULT \'0\', + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'reference_table' => 'CREATE TABLE `reference_table` ( + `tinyint_ref` tinyint NOT NULL AUTO_INCREMENT, + `tinyint_without_padding` tinyint NOT NULL DEFAULT \'0\', + `bigint_without_padding` bigint NOT NULL DEFAULT \'0\', + `smallint_without_padding` smallint NOT NULL DEFAULT \'0\', + `integer_without_padding` int NOT NULL DEFAULT \'0\', + `smallint_with_big_padding` smallint NOT NULL DEFAULT \'0\', + `smallint_without_default` smallint DEFAULT NULL, + `int_without_unsigned` int DEFAULT NULL, + `int_unsigned` int unsigned DEFAULT NULL, + `bigint_default_nullable` bigint unsigned DEFAULT \'1\', + `bigint_not_default_not_nullable` bigint unsigned NOT NULL, + `smallint_ref` smallint NOT NULL DEFAULT \'0\', + PRIMARY KEY (`tinyint_ref`,`smallint_ref`), + UNIQUE KEY `REFERENCE_TABLE_SMALLINT_REF` (`smallint_ref`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'test_table' => 'CREATE TABLE `test_table` ( + `smallint` smallint DEFAULT NULL, + `tinyint` tinyint DEFAULT NULL, + `bigint` bigint DEFAULT \'0\', + `float` float(12,10) DEFAULT \'0.0000000000\', + `double` double(245,10) DEFAULT \'11111111.1111110000\', + `decimal` decimal(15,4) DEFAULT \'0.0000\', + `date` date DEFAULT NULL, + `timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `datetime` datetime DEFAULT \'0000-00-00 00:00:00\', + `longtext` longtext, + `mediumtext` mediumtext, + `varchar` varchar(254) DEFAULT NULL, + `char` char(255) DEFAULT NULL, + `mediumblob` mediumblob, + `blob` blob, + `boolean` tinyint(1) DEFAULT NULL, + `integer_main` int unsigned DEFAULT NULL, + `smallint_main` smallint NOT NULL DEFAULT \'0\', + UNIQUE KEY `TEST_TABLE_SMALLINT_FLOAT` (`smallint`,`float`), + UNIQUE KEY `TEST_TABLE_DOUBLE` (`double`), + KEY `TEST_TABLE_TINYINT_BIGINT` (`tinyint`,`bigint`), + KEY `TEST_TABLE_SMALLINT_MAIN_REFERENCE_TABLE_SMALLINT_REF` (`smallint_main`), + KEY `FK_FB77604C299EB8612D01E4AF8D9931F2` (`integer_main`), + CONSTRAINT `FK_FB77604C299EB8612D01E4AF8D9931F2` FOREIGN KEY (`integer_main`) REFERENCES `auto_increment_test` (`int_auto_increment_with_nullable`) ON DELETE CASCADE, + CONSTRAINT `TEST_TABLE_SMALLINT_MAIN_REFERENCE_TABLE_SMALLINT_REF` FOREIGN KEY (`smallint_main`) REFERENCES `reference_table` (`smallint_ref`) ON DELETE CASCADE, + CONSTRAINT `TEST_TABLE_TINYINT_REFERENCE_TABLE_TINYINT_REF` FOREIGN KEY (`tinyint`) REFERENCES `reference_table` (`tinyint_ref`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.mariadb10427.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.mariadb10427.php new file mode 100644 index 0000000000000..45cb5f6938b41 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.mariadb10427.php @@ -0,0 +1,27 @@ + [ + 'store' => 'CREATE TABLE `store` ( + `store_owner_id` smallint(6) DEFAULT NULL COMMENT \'Store Owner Reference\', + KEY `STORE_STORE_OWNER_ID_STORE_OWNER_OWNER_ID` (`store_owner_id`), + CONSTRAINT `STORE_STORE_OWNER_ID_STORE_OWNER_OWNER_ID` FOREIGN KEY (`store_owner_id`) REFERENCES `store_owner` (`owner_id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'store_owner' => 'CREATE TABLE `store_owner` ( + `owner_id` smallint(6) NOT NULL AUTO_INCREMENT, + `store_owner_name` varchar(255) DEFAULT NULL COMMENT \'Store Owner Name\', + PRIMARY KEY (`owner_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT=\'Store owner information\'' + ], + 'after' => [ + 'store' => 'CREATE TABLE `store` ( + `store_owner` varchar(255) DEFAULT NULL COMMENT \'Store Owner Name\' +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci' + ] +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.mysql829.php new file mode 100644 index 0000000000000..57b70edd9e3f2 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.mysql829.php @@ -0,0 +1,27 @@ + [ + 'store' => 'CREATE TABLE `store` ( + `store_owner_id` smallint DEFAULT NULL COMMENT \'Store Owner Reference\', + KEY `STORE_STORE_OWNER_ID_STORE_OWNER_OWNER_ID` (`store_owner_id`), + CONSTRAINT `STORE_STORE_OWNER_ID_STORE_OWNER_OWNER_ID` FOREIGN KEY (`store_owner_id`) REFERENCES `store_owner` (`owner_id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'store_owner' => 'CREATE TABLE `store_owner` ( + `owner_id` smallint NOT NULL AUTO_INCREMENT, + `store_owner_name` varchar(255) DEFAULT NULL COMMENT \'Store Owner Name\', + PRIMARY KEY (`owner_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT=\'Store owner information\'' + ], + 'after' => [ + 'store' => 'CREATE TABLE `store` ( + `store_owner` varchar(255) DEFAULT NULL COMMENT \'Store Owner Name\' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3' + ] +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.mariadb10427.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.mariadb10427.php new file mode 100644 index 0000000000000..bc469f23f6e2b --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.mariadb10427.php @@ -0,0 +1,14 @@ + 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int(10) unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint(5) unsigned DEFAULT 0, + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci' +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.mysql829.php new file mode 100644 index 0000000000000..9ca6fcbc22751 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.mysql829.php @@ -0,0 +1,14 @@ + 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint unsigned DEFAULT \'0\', + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3' +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.mariadb10427.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.mariadb10427.php new file mode 100644 index 0000000000000..3c62923cf256f --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.mariadb10427.php @@ -0,0 +1,15 @@ + 'CREATE TABLE `some_table` ( + `some_column` varchar(255) DEFAULT NULL COMMENT \'Some Column Name\' +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'after' => 'CREATE TABLE `some_table_renamed` ( + `some_column` varchar(255) DEFAULT NULL COMMENT \'Some Column Name\' +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.mysql829.php new file mode 100644 index 0000000000000..cb8f53d499a50 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.mysql829.php @@ -0,0 +1,15 @@ + 'CREATE TABLE `some_table` ( + `some_column` varchar(255) DEFAULT NULL COMMENT \'Some Column Name\' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'after' => 'CREATE TABLE `some_table_renamed` ( + `some_column` varchar(255) DEFAULT NULL COMMENT \'Some Column Name\' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.mysql829.php new file mode 100644 index 0000000000000..4d49221074315 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.mysql829.php @@ -0,0 +1,58 @@ + 'CREATE TABLE `test_table_one` ( + `smallint` smallint(6) NOT NULL AUTO_INCREMENT, + `varchar` varchar(254) DEFAULT NULL, + PRIMARY KEY (`smallint`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'test_table_two' => 'CREATE TABLE `test_table_two` ( + `smallint` smallint(6) NOT NULL AUTO_INCREMENT, + `varchar` varchar(254) DEFAULT NULL, + PRIMARY KEY (`smallint`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'reference_table' => 'CREATE TABLE `reference_table` ( + `tinyint_ref` tinyint(4) NOT NULL AUTO_INCREMENT, + `tinyint_without_padding` tinyint(4) NOT NULL DEFAULT 0, + `bigint_without_padding` bigint(20) NOT NULL DEFAULT 0, + `smallint_without_padding` smallint(6) NOT NULL DEFAULT 0, + `integer_without_padding` int(11) NOT NULL DEFAULT 0, + `smallint_with_big_padding` smallint(6) NOT NULL DEFAULT 0, + `smallint_without_default` smallint(6) DEFAULT NULL, + `int_without_unsigned` int(11) DEFAULT NULL, + `int_unsigned` int(10) unsigned DEFAULT NULL, + `bigint_default_nullable` bigint(20) unsigned DEFAULT 1, + `bigint_not_default_not_nullable` bigint(20) unsigned NOT NULL, + PRIMARY KEY (`tinyint_ref`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci', + 'auto_increment_test' => 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int(10) unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint(5) unsigned DEFAULT 0, + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci' +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule2/fixture/shards.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule2/fixture/shards.mysql829.php new file mode 100644 index 0000000000000..2507abeef2994 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule2/fixture/shards.mysql829.php @@ -0,0 +1,38 @@ + 'CREATE TABLE `test_table_one` ( + `smallint` smallint NOT NULL AUTO_INCREMENT, + `varchar` varchar(254) DEFAULT NULL, + PRIMARY KEY (`smallint`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'test_table_two' => 'CREATE TABLE `test_table_two` ( + `smallint` smallint NOT NULL AUTO_INCREMENT, + `varchar` varchar(254) DEFAULT NULL, + PRIMARY KEY (`smallint`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'reference_table' => 'CREATE TABLE `reference_table` ( + `tinyint_ref` tinyint NOT NULL AUTO_INCREMENT, + `tinyint_without_padding` tinyint NOT NULL DEFAULT \'0\', + `bigint_without_padding` bigint NOT NULL DEFAULT \'0\', + `smallint_without_padding` smallint NOT NULL DEFAULT \'0\', + `integer_without_padding` int NOT NULL DEFAULT \'0\', + `smallint_with_big_padding` smallint NOT NULL DEFAULT \'0\', + `smallint_without_default` smallint DEFAULT NULL, + `int_without_unsigned` int DEFAULT NULL, + `int_unsigned` int unsigned DEFAULT NULL, + `bigint_default_nullable` bigint unsigned DEFAULT \'1\', + `bigint_not_default_not_nullable` bigint unsigned NOT NULL, + PRIMARY KEY (`tinyint_ref`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3', + 'auto_increment_test' => 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint unsigned DEFAULT \'0\', + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3' +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.mariadb10427.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.mariadb10427.php new file mode 100644 index 0000000000000..bc469f23f6e2b --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.mariadb10427.php @@ -0,0 +1,14 @@ + 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int(10) unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint(5) unsigned DEFAULT 0, + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci' +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.mysql829.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.mysql829.php new file mode 100644 index 0000000000000..9ca6fcbc22751 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.mysql829.php @@ -0,0 +1,14 @@ + 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint unsigned DEFAULT \'0\', + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3' +]; diff --git a/dev/tests/setup-integration/framework/Magento/TestFramework/Annotation/DataProviderFromFile.php b/dev/tests/setup-integration/framework/Magento/TestFramework/Annotation/DataProviderFromFile.php index b7c45b628b44b..a4e7e74f330ba 100644 --- a/dev/tests/setup-integration/framework/Magento/TestFramework/Annotation/DataProviderFromFile.php +++ b/dev/tests/setup-integration/framework/Magento/TestFramework/Annotation/DataProviderFromFile.php @@ -20,14 +20,16 @@ class DataProviderFromFile /** * @var string */ - const FALLBACK_VALUE = 'default'; + public const FALLBACK_VALUE = 'default'; /** * @var array */ - const POSSIBLE_SUFFIXES = [ + public const POSSIBLE_SUFFIXES = [ SqlVersionProvider::MYSQL_8_0_VERSION => 'mysql8', - SqlVersionProvider::MARIA_DB_10_VERSION => 'mariadb10', + SqlVersionProvider::MARIA_DB_10_4_VERSION => 'mariadb10', + SqlVersionProvider::MARIA_DB_10_4_27_VERSION => 'mariadb10427', + SqlVersionProvider::MYSQL_8_0_29_VERSION => 'mysql829' ]; /** diff --git a/dev/tests/setup-integration/framework/Magento/TestFramework/TestCase/SetupTestCase.php b/dev/tests/setup-integration/framework/Magento/TestFramework/TestCase/SetupTestCase.php index 166a46970f84c..2d42d863c859a 100644 --- a/dev/tests/setup-integration/framework/Magento/TestFramework/TestCase/SetupTestCase.php +++ b/dev/tests/setup-integration/framework/Magento/TestFramework/TestCase/SetupTestCase.php @@ -107,7 +107,13 @@ private function getDbKey(): string $this->dbKey = DataProviderFromFile::FALLBACK_VALUE; foreach (DataProviderFromFile::POSSIBLE_SUFFIXES as $possibleVersion => $suffix) { - if (strpos($this->getDatabaseVersion(), (string)$possibleVersion) !== false) { + if ($this->sqlVersionProvider->isMariaDBGte10427()) { + $this->dbKey = DataProviderFromFile::POSSIBLE_SUFFIXES[SqlVersionProvider::MARIA_DB_10_4_27_VERSION]; + break; + } elseif ($this->sqlVersionProvider->isMysqlGte8029()) { + $this->dbKey = DataProviderFromFile::POSSIBLE_SUFFIXES[SqlVersionProvider::MYSQL_8_0_29_VERSION]; + break; + } elseif (strpos($this->getDatabaseVersion(), (string)$possibleVersion) !== false) { $this->dbKey = $suffix; break; } diff --git a/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt b/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt index b1810d9daa1a1..dfeec1ebd27a8 100644 --- a/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt +++ b/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt @@ -13,9 +13,11 @@ lib/web/mage/adminhtml/varienLoader.js lib/web/magnifier/magnifier.js lib/web/magnifier/magnify.js lib/web/varien/js.js +lib/web/prototype/**/*.js // MINIFIED FILES app/code/**/*.min.js +lib/web/legacy-build.min.js // TEST vendor/magento/magento-coding-standard/Magento2/Tests/Eslint/* diff --git a/dev/tools/grunt/tools/collect-validation-files.js b/dev/tools/grunt/tools/collect-validation-files.js index 15fb5e50be0a2..d707a276a9606 100644 --- a/dev/tools/grunt/tools/collect-validation-files.js +++ b/dev/tools/grunt/tools/collect-validation-files.js @@ -3,58 +3,60 @@ * See COPYING.txt for license details. */ -'use strict'; - -var glob = require('glob'), - fs = require('fs'), - _ = require('underscore'), - fst = require('../tools/fs-tools'), - pc = require('../configs/path'); - -module.exports = { - readFiles: function (paths) { - var data = []; - - _.each(paths, function (path) { - data = _.union(data, fst.getData(path)); - }); - - return data; - }, - - getFilesForValidate: function () { - var blackListFiles = glob.sync(pc.static.blacklist + '*.txt'), - whiteListFiles = glob.sync(pc.static.whitelist + '*.txt'), - blackList = this.readFiles(blackListFiles).filter(this.isListEntryValid), - whiteList = this.readFiles(whiteListFiles).filter(this.isListEntryValid), - files = [], - entireBlackList = []; - - fst.arrayRead(blackList, function (data) { - entireBlackList = _.union(entireBlackList, data); - }); - - fst.arrayRead(whiteList, function (data) { - files = _.difference(data, entireBlackList); - }); - - return files; - }, - - isListEntryValid: function(line) { - line = line.trim(); - return line.length > 0 && line.startsWith('// ') !== true; - }, - - getFiles: function (file) { - if (file) { - return file.split(','); +module.exports = (function (glob, fs, _, fst, pc) { + 'use strict'; + return { + readFiles: function (paths) { + var data = []; + + _.each(paths, function (path) { + data = _.union(data, fst.getData(path)); + }); + + return data; + }, + + getFilesForValidate: function () { + var blackListFiles = glob.sync(pc.static.blacklist + '*.txt'), + whiteListFiles = glob.sync(pc.static.whitelist + '*.txt'), + blackList = this.readFiles(blackListFiles).filter(this.isListEntryValid), + whiteList = this.readFiles(whiteListFiles).filter(this.isListEntryValid), + files = [], + entireBlackList = []; + + fst.arrayRead(blackList, function (data) { + entireBlackList = _.union(entireBlackList, data); + }); + + fst.arrayRead(whiteList, function (data) { + files = _.difference(data, entireBlackList); + }); + + return files; + }, + + isListEntryValid: function (line) { + line = line.trim(); + return line.length > 0 && line.startsWith('// ') !== true; + }, + + getFiles: function (file) { + var files; + + if (file) { + return file.split(','); + } + + if (!fs.existsSync(pc.static.tmp)) { + fst.write(pc.static.tmp, this.getFilesForValidate()); + } + + files = fst.getData(pc.static.tmp); + if (files.length === 1 && files[0] === '') { + files = []; + } + + return files; } - - if (!fs.existsSync(pc.static.tmp)) { - fst.write(pc.static.tmp, this.getFilesForValidate()); - } - - return fst.getData(pc.static.tmp); - } -}; + }; +})(require('glob'),require('fs'),require('underscore'),require('../tools/fs-tools'),require('../configs/path')); diff --git a/lib/internal/Magento/Framework/Amqp/composer.json b/lib/internal/Magento/Framework/Amqp/composer.json index 701f2df167065..038d0ff3bd8f8 100644 --- a/lib/internal/Magento/Framework/Amqp/composer.json +++ b/lib/internal/Magento/Framework/Amqp/composer.json @@ -1,25 +1,27 @@ { "name": "magento/framework-amqp", "description": "N/A", - "config": { - "sort-packages": true - }, "type": "magento2-library", "license": [ "OSL-3.0", "AFL-3.0" ], + "config": { + "sort-packages": true + }, + "version": "100.4.3", "require": { - "magento/framework": "*", + "magento/framework": "103.0.*", "php": "~7.4.0||~8.1.0", "php-amqplib/php-amqplib": "~3.2.0" }, "autoload": { - "psr-4": { - "Magento\\Framework\\Amqp\\": "" - }, "files": [ "registration.php" - ] + ], + "psr-4": { + "Magento\\Framework\\Amqp\\": "" + } } } + diff --git a/lib/internal/Magento/Framework/App/Http/Context.php b/lib/internal/Magento/Framework/App/Http/Context.php index b3fa5a5cca67b..92c4b282784d0 100644 --- a/lib/internal/Magento/Framework/App/Http/Context.php +++ b/lib/internal/Magento/Framework/App/Http/Context.php @@ -7,7 +7,9 @@ namespace Magento\Framework\App\Http; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\Serialize\Serializer\Json; /** @@ -20,7 +22,7 @@ class Context /** * Currency cache context */ - const CONTEXT_CURRENCY = 'current_currency'; + public const CONTEXT_CURRENCY = 'current_currency'; /** * Data storage @@ -39,6 +41,11 @@ class Context */ private $serializer; + /** + * @var DeploymentConfig|null + */ + private ?DeploymentConfig $deploymentConfig = null; + /** * @param array $data * @param array $default @@ -116,8 +123,11 @@ public function getVaryString() { $data = $this->getData(); if (!empty($data)) { + $salt = (string)$this->getDeploymentConfig()->get( + ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY + ); ksort($data); - return sha1($this->serializer->serialize($data)); + return hash('sha256', $this->serializer->serialize($data) . '|' . $salt); } return null; } @@ -134,4 +144,18 @@ public function toArray() 'default' => $this->default ]; } + + /** + * Get DeploymentConfig + * + * @return DeploymentConfig + */ + private function getDeploymentConfig() : DeploymentConfig + { + if ($this->deploymentConfig === null) { + $this->deploymentConfig = ObjectManager::getInstance() + ->get(DeploymentConfig::class); + } + return $this->deploymentConfig; + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php index a1ab0f3d3f5f7..17aeb74988323 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Http/ContextTest.php @@ -7,7 +7,10 @@ namespace Magento\Framework\App\Test\Unit\Http; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\Http\Context; +use Magento\Framework\Config\ConfigOptionsListConstants; +use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\MockObject\MockObject; @@ -25,6 +28,16 @@ class ContextTest extends TestCase */ protected $object; + /** + * @var DeploymentConfig|MockObject + */ + private $deploymentConfig; + + /** + * @var ObjectManagerInterface|MockObject + */ + private $objectManagerMock; + /** * @var Json|MockObject */ @@ -32,6 +45,12 @@ class ContextTest extends TestCase protected function setUp(): void { + $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['create']) + ->getMockForAbstractClass(); + \Magento\Framework\App\ObjectManager::setInstance($this->objectManagerMock); + $this->objectManager = new ObjectManager($this); $this->serializerMock = $this->getMockBuilder(Json::class) ->setMethods(['serialize']) @@ -50,6 +69,10 @@ function ($value) { 'serializer' => $this->serializerMock ] ); + $this->deploymentConfig = $this->createPartialMock( + DeploymentConfig::class, + ['get'] + ); } public function testGetValue() @@ -81,6 +104,16 @@ public function testGetData() public function testGetVaryString() { + $this->objectManagerMock->expects($this->any()) + ->method('get') + ->with(DeploymentConfig::class) + ->willReturn($this->deploymentConfig); + + $this->deploymentConfig->expects($this->any()) + ->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY) + ->willReturn('448198e08af35844a42d3c93c1ef4e03'); + $this->object->setValue('key2', 'value2', 'default2'); $this->object->setValue('key1', 'value1', 'default1'); $data = [ @@ -88,7 +121,11 @@ public function testGetVaryString() 'key1' => 'value1' ]; ksort($data); - $this->assertEquals(sha1(json_encode($data)), $this->object->getVaryString()); + + $salt = $this->deploymentConfig->get(ConfigOptionsListConstants::CONFIG_PATH_CRYPT_KEY); + $cacheKey = hash('sha256', $this->serializerMock->serialize($data) . '|' . $salt); + + $this->assertEquals($cacheKey, $this->object->getVaryString()); } public function testToArray() diff --git a/lib/internal/Magento/Framework/Bulk/composer.json b/lib/internal/Magento/Framework/Bulk/composer.json index 7733ce9fea4c6..0e601aac8ade9 100644 --- a/lib/internal/Magento/Framework/Bulk/composer.json +++ b/lib/internal/Magento/Framework/Bulk/composer.json @@ -1,24 +1,26 @@ { "name": "magento/framework-bulk", "description": "N/A", - "config": { - "sort-packages": true - }, "type": "magento2-library", "license": [ "OSL-3.0", "AFL-3.0" ], + "config": { + "sort-packages": true + }, + "version": "101.0.1", "require": { - "magento/framework": "*", + "magento/framework": "103.0.*", "php": "~7.4.0||~8.1.0" }, "autoload": { - "psr-4": { - "Magento\\Framework\\Bulk\\": "" - }, "files": [ "registration.php" - ] + ], + "psr-4": { + "Magento\\Framework\\Bulk\\": "" + } } } + diff --git a/lib/internal/Magento/Framework/DB/Adapter/SqlVersionProvider.php b/lib/internal/Magento/Framework/DB/Adapter/SqlVersionProvider.php index def51db16454d..09c1d63a26528 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/SqlVersionProvider.php +++ b/lib/internal/Magento/Framework/DB/Adapter/SqlVersionProvider.php @@ -25,6 +25,12 @@ class SqlVersionProvider public const MARIA_DB_10_VERSION = '10.'; + public const MARIA_DB_10_4_VERSION = '10.4.'; + + public const MARIA_DB_10_4_27_VERSION = '10.4.27'; + + public const MYSQL_8_0_29_VERSION = '8.0.29'; + /**#@-*/ /** @@ -116,4 +122,38 @@ private function fetchSqlVersion(string $resource): string return $versionOutput[self::VERSION_VAR_NAME]; } + + /** + * Check if MariaDB version is greater than equal to 10.4.27 + * + * @return bool + * @throws ConnectionException + */ + public function isMariaDBGte10427(): bool + { + $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION); + //check if mariadb is 10.4 or 10.5 + $pattern="/^10.([4-5]\.).*$/"; + if (preg_match($pattern, $this->getSqlVersion()) && version_compare($sqlExactVersion, '10.4.27', '>=')) { + return true; + } + return false; + } + + /** + * Check if MySQL version is greater than equal to 8.0.29 + * + * @return bool + * @throws ConnectionException + */ + public function isMysqlGte8029() + { + $sqlVersion = $this->getSqlVersion(); + $isMariaDB = str_contains($sqlVersion, SqlVersionProvider::MARIA_DB_10_VERSION); + $sqlExactVersion = $this->fetchSqlVersion(ResourceConnection::DEFAULT_CONNECTION); + if (!$isMariaDB && version_compare($sqlExactVersion, '8.0.29', '>=')) { + return true; + } + return false; + } } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Image.php b/lib/internal/Magento/Framework/Data/Form/Element/Image.php index 4197415f5dc79..5c57e9ddda9f0 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Image.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Image.php @@ -71,30 +71,27 @@ public function getElementHtml() { $html = ''; - if ((string)$this->getValue()) { + if ((string)$this->getEscapedValue()) { $url = $this->_getUrl(); if (!preg_match("/^http\:\/\/|https\:\/\//", $url)) { $url = $this->_urlBuilder->getBaseUrl(['_type' => UrlInterface::URL_TYPE_MEDIA]) . $url; } - $linkId = 'linkId' .$this->random->getRandomString(8); - $html = 'random->getRandomString(8); + $html = '_getUiId( 'link' ) . '>' . - '' .
-                $this->getValue() .
+                $this->getEscapedValue() .
                 '_getUiId() . ' />' . @@ -120,7 +117,7 @@ public function getElementHtml() protected function _getDeleteCheckbox() { $html = ''; - if ($this->getValue()) { + if ($this->getEscapedValue()) { $label = (string)new \Magento\Framework\Phrase('Delete Image'); $html .= ''; $html .= ''; + return ''; } /** @@ -163,7 +161,7 @@ protected function _getHiddenInput() */ protected function _getUrl() { - return $this->getValue(); + return $this->getEscapedValue(); } /** diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/ImageTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/ImageTest.php index 54f9aca257620..20efa21bd4721 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/ImageTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/ImageTest.php @@ -15,12 +15,13 @@ use Magento\Framework\Data\Form\Element\Image; use Magento\Framework\DataObject; use Magento\Framework\Escaper; +use Magento\Framework\Math\Random; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\Url; use Magento\Framework\UrlInterface; +use Magento\Framework\View\Helper\SecureHtmlRenderer; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Magento\Framework\Math\Random; -use Magento\Framework\View\Helper\SecureHtmlRenderer; /** * Test for the widget. @@ -44,11 +45,16 @@ class ImageTest extends TestCase */ protected $_image; + /** + * @var array + */ + protected $testData; + protected function setUp(): void { + $objectManager = new ObjectManager($this); $factoryMock = $this->createMock(Factory::class); $collectionFactoryMock = $this->createMock(CollectionFactory::class); - $escaperMock = $this->createMock(Escaper::class); $this->urlBuilder = $this->createMock(Url::class); $randomMock = $this->createMock(Random::class); $randomMock->method('getRandomString')->willReturn('some-rando-string'); @@ -67,18 +73,28 @@ function (string $tag, array $attrs, ?string $content): string { return "<$tag {$attrs->serialize()}>$content"; } ); - $this->_image = new Image( - $factoryMock, - $collectionFactoryMock, - $escaperMock, - $this->urlBuilder, - [], - $secureRendererMock, - $randomMock + $this->_image = $objectManager->getObject( + Image::class, + [ + 'factoryMock'=>$factoryMock, + 'collectionFactoryMock'=>$collectionFactoryMock, + 'urlBuilder' => $this->urlBuilder, + '_escaper' => $objectManager->getObject(Escaper::class), + 'random' => $randomMock, + 'secureRenderer' => $secureRendererMock, + ] ); + $this->testData = [ + 'html_id_prefix' => 'test_id_prefix_', + 'html_id' => 'test_id', + 'html_id_suffix' => '_test_id_suffix', + 'path' => 'catalog/product/placeholder', + 'value' => 'test_value', + ]; + $formMock = new DataObject(); - $formMock->getHtmlIdPrefix('id_prefix'); - $formMock->getHtmlIdPrefix('id_suffix'); + $formMock->getHtmlIdPrefix($this->testData['html_id_prefix']); + $formMock->getHtmlIdPrefix($this->testData['html_id_suffix']); $this->_image->setForm($formMock); } @@ -117,21 +133,32 @@ public function testGetElementHtmlWithoutValue() */ public function testGetElementHtmlWithValue() { - $this->_image->setValue('test_value'); - $this->urlBuilder->expects($this->once()) - ->method('getBaseUrl') - ->with(['_type' => UrlInterface::URL_TYPE_MEDIA]) - ->willReturn('http://localhost/media/'); + $url = 'http://test.example.com/media/'; + + $this->_image->setValue($this->testData['value']); + $this->_image->setHtmlId($this->testData['html_id']); + + $this->urlBuilder->expects($this->once())->method('getBaseUrl') + ->with(['_type' => UrlInterface::URL_TYPE_MEDIA])->willReturn($url); + + $expectedHtmlId = $this->testData['html_id']; + $html = $this->_image->getElementHtml(); + $this->assertStringContainsString('class="input-file"', $html); $this->assertStringContainsString('assertStringContainsString('type="file"', $html); $this->assertStringContainsString('value="test_value"', $html); + $this->assertStringContainsString( - 'assertStringContainsString("imagePreview('_image');\nreturn false;", $html); + + $this->assertStringContainsString("imagePreview('{$expectedHtmlId}_image');\nreturn false;", $html); $this->assertStringContainsString('driver->getRealPathSafety($path); } - if (mb_strpos($actualPath, $realDirectoryPath) !== 0 - && rtrim($path, DIRECTORY_SEPARATOR) !== $realDirectoryPath + if (preg_match('/(?:^-|\s-\S)/', $path) + || ( + mb_strpos($actualPath, $realDirectoryPath) !== 0 + && rtrim($path, DIRECTORY_SEPARATOR) !== $realDirectoryPath + ) ) { throw new ValidatorException( new Phrase( diff --git a/lib/internal/Magento/Framework/Filesystem/File/Write.php b/lib/internal/Magento/Framework/Filesystem/File/Write.php index d3d5a23e65992..a1c6e78bf448a 100644 --- a/lib/internal/Magento/Framework/Filesystem/File/Write.php +++ b/lib/internal/Magento/Framework/Filesystem/File/Write.php @@ -8,6 +8,7 @@ use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Phrase; class Write extends Read implements WriteInterface { @@ -28,18 +29,22 @@ public function __construct($path, DriverInterface $driver, $mode) * Assert file existence for proper mode * * @return void - * @throws \Magento\Framework\Exception\FileSystemException + * @throws FileSystemException */ protected function assertValid() { $fileExists = $this->driver->isExists($this->path); $mode = $this->mode ?? ''; - if (!$fileExists && preg_match('/r/', $mode)) { + if (preg_match('/(?:^-|\s-)/', basename($this->path))) { + throw new FileSystemException( + new Phrase('The filename "%1" contains invalid characters', [basename($this->path)]) + ); + } elseif (!$fileExists && preg_match('/r/', $mode)) { throw new FileSystemException( - new \Magento\Framework\Phrase('The "%1" file doesn\'t exist.', [$this->path]) + new Phrase('The "%1" file doesn\'t exist.', [$this->path]) ); } elseif ($fileExists && preg_match('/x/', $mode)) { - throw new FileSystemException(new \Magento\Framework\Phrase('The file "%1" already exists', [$this->path])); + throw new FileSystemException(new Phrase('The file "%1" already exists', [$this->path])); } } @@ -56,7 +61,7 @@ public function write($data) return $this->driver->fileWrite($this->resource, $data); } catch (FileSystemException $e) { throw new FileSystemException( - new \Magento\Framework\Phrase('Cannot write to the "%1" file. %2', [$this->path, $e->getMessage()]) + new Phrase('Cannot write to the "%1" file. %2', [$this->path, $e->getMessage()]) ); } } @@ -76,7 +81,7 @@ public function writeCsv(array $data, $delimiter = ',', $enclosure = '"') return $this->driver->filePutCsv($this->resource, $data, $delimiter, $enclosure); } catch (FileSystemException $e) { throw new FileSystemException( - new \Magento\Framework\Phrase('Cannot write to the "%1" file. %2', [$this->path, $e->getMessage()]) + new Phrase('Cannot write to the "%1" file. %2', [$this->path, $e->getMessage()]) ); } } @@ -93,7 +98,7 @@ public function flush() return $this->driver->fileFlush($this->resource); } catch (FileSystemException $e) { throw new FileSystemException( - new \Magento\Framework\Phrase('Cannot flush the "%1" file. %2', [$this->path, $e->getMessage()]) + new Phrase('Cannot flush the "%1" file. %2', [$this->path, $e->getMessage()]) ); } } diff --git a/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php b/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php index b74268a869537..717e12df7f9ab 100644 --- a/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php +++ b/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php @@ -1,7 +1,5 @@ string = $string; $this->setVariables($variables); @@ -118,6 +134,12 @@ public function __construct( $this->variableResolver = $variableResolver ?? ObjectManager::getInstance() ->get(VariableResolverInterface::class); + $this->signatureProvider = $signatureProvider ?? ObjectManager::getInstance() + ->get(SignatureProvider::class); + + $this->filteringDepthMeter = $filteringDepthMeter ?? ObjectManager::getInstance() + ->get(FilteringDepthMeter::class); + if (empty($directiveProcessors)) { $this->directiveProcessors = [ 'depend' => ObjectManager::getInstance()->get(DependDirective::class), @@ -180,6 +202,60 @@ public function filter($value) )->render()); } + $this->filteringDepthMeter->descend(); + + // Processing of template directives. + $templateDirectivesResults = $this->processDirectives($value); + + foreach ($templateDirectivesResults as $result) { + $value = str_replace($result['directive'], $result['output'], $value); + } + + // Processing of deferred directives received from child templates + // or nested directives. + $deferredDirectivesResults = $this->processDirectives($value, true); + + foreach ($deferredDirectivesResults as $result) { + $value = str_replace($result['directive'], $result['output'], $value); + } + + if ($this->filteringDepthMeter->showMark() > 1) { + // Signing own deferred directives (if any). + $signature = $this->signatureProvider->get(); + + foreach ($templateDirectivesResults as $result) { + if ($result['directive'] === $result['output']) { + $value = str_replace( + $result['output'], + $signature . $result['output'] . $signature, + $value + ); + } + } + } + + $value = $this->afterFilter($value); + + $this->filteringDepthMeter->ascend(); + + return $value; + } + + /** + * Processes template directives and returns an array that contains results produced by each directive. + * + * @param string $value + * @param bool $isSigned + * + * @return array + * + * @throws InvalidArgumentException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function processDirectives($value, $isSigned = false): array + { + $results = []; + foreach ($this->directiveProcessors as $directiveProcessor) { if (!$directiveProcessor instanceof DirectiveProcessorInterface) { throw new InvalidArgumentException( @@ -187,15 +263,57 @@ public function filter($value) ); } - if (preg_match_all($directiveProcessor->getRegularExpression(), $value, $constructions, PREG_SET_ORDER)) { + $pattern = $directiveProcessor->getRegularExpression(); + + if ($isSigned) { + $pattern = $this->embedSignatureIntoPattern($pattern); + } + + if (preg_match_all($pattern, $value, $constructions, PREG_SET_ORDER)) { foreach ($constructions as $construction) { $replacedValue = $directiveProcessor->process($construction, $this, $this->templateVars); - $value = str_replace($construction[0], $replacedValue, $value); + + $results[] = [ + 'directive' => $construction[0], + 'output' => $replacedValue + ]; } } } - return $this->afterFilter($value); + return $results; + } + + /** + * Modifies given regular expression pattern to be able to recognize signed directives. + * + * @param string $pattern + * + * @return string + * + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function embedSignatureIntoPattern(string $pattern): string + { + $signature = $this->signatureProvider->get(); + + $closingDelimiters = [ + '(' => ')', + '{' => '}', + '[' => ']', + '<' => '>' + ]; + + $closingDelimiter = $openingDelimiter = substr(trim($pattern), 0, 1); + + if (array_key_exists($openingDelimiter, $closingDelimiters)) { + $closingDelimiter = $closingDelimiters[$openingDelimiter]; + } + + $pattern = substr_replace($pattern, $signature, strpos($pattern, $openingDelimiter) + 1, 0); + $pattern = substr_replace($pattern, $signature, strrpos($pattern, $closingDelimiter), 0); + + return $pattern; } /** @@ -251,6 +369,7 @@ protected function resetAfterFilterCallbacks() * @param string[] $construction * @return string * @deprecated 102.0.4 Use the directive interfaces instead + * @see \Magento\Framework\Filter\DirectiveProcessorInterface */ public function varDirective($construction) { @@ -266,6 +385,7 @@ public function varDirective($construction) * @param string[] $construction * @return string * @deprecated 102.0.4 Use the directive interfaces instead + * @see \Magento\Framework\Filter\DirectiveProcessorInterface * @since 102.0.4 */ public function forDirective($construction) @@ -291,6 +411,7 @@ public function forDirective($construction) * @param string[] $construction * @return mixed * @deprecated 102.0.4 Use the directive interfaces instead + * @see \Magento\Framework\Filter\DirectiveProcessorInterface */ public function templateDirective($construction) { @@ -306,6 +427,7 @@ public function templateDirective($construction) * @param string[] $construction * @return string * @deprecated 102.0.4 Use the directive interfaces instead + * @see \Magento\Framework\Filter\DirectiveProcessorInterface */ public function dependDirective($construction) { @@ -323,6 +445,7 @@ public function dependDirective($construction) * @param string[] $construction * @return string * @deprecated 102.0.4 Use the directive interfaces instead + * @see \Magento\Framework\Filter\DirectiveProcessorInterface */ public function ifDirective($construction) { @@ -340,6 +463,7 @@ public function ifDirective($construction) * @param string $value raw parameters * @return array * @deprecated 102.0.4 Use the directive interfaces instead + * @see \Magento\Framework\Filter\DirectiveProcessorInterface */ protected function getParameters($value) { @@ -361,6 +485,7 @@ protected function getParameters($value) * @param string $default default value * @return string * @deprecated 102.0.4 Use \Magento\Framework\Filter\VariableResolverInterface instead + * @see \Magento\Framework\Filter\VariableResolverInterface */ protected function getVariable($value, $default = '{no_value_defined}') { @@ -377,6 +502,7 @@ protected function getVariable($value, $default = '{no_value_defined}') * @param array $stack * @return array * @deprecated 102.0.4 Use new directive processor interfaces + * @see \Magento\Framework\Filter\DirectiveProcessorInterface */ protected function getStackArgs($stack) { @@ -405,6 +531,7 @@ protected function getStackArgs($stack) * @return bool The previous mode from before the change * @since 102.0.4 * @deprecated The method is not in use anymore. + * @see https://developer.adobe.com/commerce/frontend-core/guide/templates/email-migration/#remove-the-legacy-variable-resolver */ public function setStrictMode(bool $strictMode): bool { @@ -420,6 +547,7 @@ public function setStrictMode(bool $strictMode): bool * @return bool * @since 102.0.4 * @deprecated The method is not in use anymore. + * @see https://developer.adobe.com/commerce/frontend-core/guide/templates/email-migration/#remove-the-legacy-variable-resolver */ public function isStrictMode(): bool { diff --git a/lib/internal/Magento/Framework/Filter/Template/FilteringDepthMeter.php b/lib/internal/Magento/Framework/Filter/Template/FilteringDepthMeter.php new file mode 100644 index 0000000000000..57257325be797 --- /dev/null +++ b/lib/internal/Magento/Framework/Filter/Template/FilteringDepthMeter.php @@ -0,0 +1,52 @@ +depth++; + } + + /** + * Decreases filtering depth. + * + * @return void + */ + public function ascend() + { + $this->depth--; + } + + /** + * Shows current filtering depth. + * + * @return int + */ + public function showMark(): int + { + return $this->depth; + } +} diff --git a/lib/internal/Magento/Framework/Filter/Template/SignatureProvider.php b/lib/internal/Magento/Framework/Filter/Template/SignatureProvider.php new file mode 100644 index 0000000000000..3e476f3e5d79e --- /dev/null +++ b/lib/internal/Magento/Framework/Filter/Template/SignatureProvider.php @@ -0,0 +1,53 @@ +random = $random; + } + + /** + * Generates a random string which will be used as a signature during runtime. + * + * @return string + * + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function get(): string + { + if ($this->signature === null) { + $this->signature = $this->random->getRandomString(32); + } + + return $this->signature; + } +} diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/Template/SignatureProviderTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/Template/SignatureProviderTest.php new file mode 100644 index 0000000000000..3cf685dc1ffd7 --- /dev/null +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/Template/SignatureProviderTest.php @@ -0,0 +1,50 @@ +random = $this->createPartialMock( + \Magento\Framework\Math\Random::class, + ['getRandomString'] + ); + + $this->signatureProvider = new \Magento\Framework\Filter\Template\SignatureProvider( + $this->random + ); + } + + public function testGet() + { + $expectedResult = 'Z0FFbeCU2R8bsVGJuTdkXyiiZBzsaceV'; + + $this->random->expects($this->once()) + ->method('getRandomString') + ->with(32) + ->willReturn($expectedResult); + + $this->assertEquals($expectedResult, $this->signatureProvider->get()); + + $this->random->expects($this->never()) + ->method('getRandomString'); + + $this->assertEquals($expectedResult, $this->signatureProvider->get()); + } +} diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php index 973f7ac1d268f..dffb22c2b776f 100644 --- a/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php @@ -28,11 +28,43 @@ class TemplateTest extends TestCase */ private $store; + /** + * @var \Magento\Framework\Filter\Template\SignatureProvider|\PHPUnit\Framework\MockObject\MockObject + */ + protected $signatureProvider; + + /** + * @var \Magento\Framework\Filter\Template\FilteringDepthMeter|\PHPUnit\Framework\MockObject\MockObject + */ + protected $filteringDepthMeter; + protected function setUp(): void { $objectManager = new ObjectManager($this); - $this->templateFilter = $objectManager->getObject(Template::class); + $this->store = $objectManager->getObject(Store::class); + + $this->signatureProvider = $this->createPartialMock( + \Magento\Framework\Filter\Template\SignatureProvider::class, + ['get'] + ); + + $this->signatureProvider->expects($this->any()) + ->method('get') + ->willReturn('Z0FFbeCU2R8bsVGJuTdkXyiiZBzsaceV'); + + $this->filteringDepthMeter = $this->createPartialMock( + \Magento\Framework\Filter\Template\FilteringDepthMeter::class, + ['showMark'] + ); + + $this->templateFilter = $objectManager->getObject( + \Magento\Framework\Filter\Template::class, + [ + 'signatureProvider' => $this->signatureProvider, + 'filteringDepthMeter' => $this->filteringDepthMeter + ] + ); } /** @@ -44,6 +76,10 @@ public function testAfterFilter() $value = 'test string'; $expectedResult = 'TEST STRING'; + $this->filteringDepthMeter->expects($this->any()) + ->method('showMark') + ->willReturn(1); + // Build arbitrary object to pass into the addAfterFilterCallback method $callbackObject = $this->getMockBuilder('stdObject') ->setMethods(['afterFilterCallbackMethod']) @@ -72,6 +108,10 @@ public function testAfterFilterCallbackReset() $value = 'test string'; $expectedResult = 'TEST STRING'; + $this->filteringDepthMeter->expects($this->any()) + ->method('showMark') + ->willReturn(1); + // Build arbitrary object to pass into the addAfterFilterCallback method $callbackObject = $this->getMockBuilder('stdObject') ->setMethods(['afterFilterCallbackMethod']) @@ -127,7 +167,7 @@ public function getTemplateAndExpectedResults($type)
    {{for in order.all_visible_items}}
  • - name: , lastname: , age: + name: , lastname: , age:
  • {{/for}}
@@ -137,14 +177,14 @@ public function getTemplateAndExpectedResults($type) $template = <<