diff --git a/vendor/magento/module-staging/Block/Adminhtml/Update/Preview.php b/vendor/magento/module-staging/Block/Adminhtml/Update/Preview.php index e13b91a9b822..b3ddc9e0c060 100644 --- a/vendor/magento/module-staging/Block/Adminhtml/Update/Preview.php +++ b/vendor/magento/module-staging/Block/Adminhtml/Update/Preview.php @@ -236,6 +236,7 @@ public function getPreviewVersion() * @return string * @since 100.1.0 * @deprecated SID query parameter is not used in URLs anymore. + * @see \Magento\Framework\Url::getSid() For deprecation details */ public function getSidParamName() { @@ -396,14 +397,24 @@ private function modifyHost($url) $host = parse_url($url, PHP_URL_HOST); // phpcs:disable Magento2.Functions.DiscouragedFunction.Discouraged $port = parse_url($url, PHP_URL_PORT); - if ($port) { - $host .= ':' . $port; + + if (!$host) { + return $url; } - return $url = str_replace( - $host, - $this->_request->getServer('HTTP_HOST'), - $url - ); + + $adminHost = $this->_request->getServer('HTTP_HOST'); + // phpcs:disable Magento2.Functions.DiscouragedFunction.Discouraged + $adminHostBase = parse_url('//' . $adminHost, PHP_URL_HOST); + + if ($host === $adminHostBase) { + $urlHost = $host; + if ($port) { + $urlHost .= ':' . $port; + } + return str_replace($urlHost, $adminHost, $url); + } + + return $url; } /** diff --git a/vendor/magento/module-staging/Plugin/Csp/Model/CspRendererPlugin.php b/vendor/magento/module-staging/Plugin/Csp/Model/CspRendererPlugin.php new file mode 100644 index 000000000000..25ab42507dd5 --- /dev/null +++ b/vendor/magento/module-staging/Plugin/Csp/Model/CspRendererPlugin.php @@ -0,0 +1,188 @@ +versionManager = $versionManager; + $this->scopeConfig = $scopeConfig; + $this->request = $request; + $this->policyCollector = $policyCollector; + $this->rendererPool = $rendererPool; + $this->uriHandler = $uriHandler; + } + + /** + * Enhance frame-ancestors CSP policy for staging preview requests + * + * @param CspRendererInterface $subject + * @param callable $proceed + * @param HttpResponse $response + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundRender( + CspRendererInterface $subject, + callable $proceed, + HttpResponse $response + ): void { + if (!$this->versionManager->isPreviewVersion()) { + $proceed($response); + return; + } + + $adminDomain = $this->getAdminDomain(); + + $policies = $this->policyCollector->collect(); + foreach ($policies as $policy) { + if ($policy instanceof FetchPolicy && $policy->getId() === 'frame-ancestors' && $adminDomain) { + $policy = $this->addAdminDomainToPolicy($policy, $adminDomain); + } + $this->rendererPool->getRenderer($policy)->render($policy, $response); + } + } + + /** + * Get admin domain from configuration + * + * @return string|null + */ + private function getAdminDomain(): ?string + { + if ($this->scopeConfig->isSetFlag(FrontNameResolver::XML_PATH_USE_CUSTOM_ADMIN_URL)) { + $adminUrl = $this->scopeConfig->getValue(FrontNameResolver::XML_PATH_CUSTOM_ADMIN_URL); + } else { + $xmlPath = $this->request->isSecure() + ? Store::XML_PATH_SECURE_BASE_URL + : Store::XML_PATH_UNSECURE_BASE_URL; + $adminUrl = $this->scopeConfig->getValue($xmlPath); + } + + if (!$adminUrl) { + return null; + } + + $this->uriHandler->parse($adminUrl); + $scheme = $this->uriHandler->getScheme(); + $host = $this->uriHandler->getHost(); + $port = $this->uriHandler->getPort(); + + if (!$scheme || !$host) { + return null; + } + + $domain = $scheme . '://' . $host; + if ($port && !in_array((int)$port, [80, 443])) { + $domain .= ':' . $port; + } + + return $domain; + } + + /** + * Add admin domain to frame-ancestors policy + * + * @param FetchPolicy $policy + * @param string $adminDomain + * @return FetchPolicy + */ + private function addAdminDomainToPolicy(FetchPolicy $policy, string $adminDomain): FetchPolicy + { + $hostSources = $policy->getHostSources(); + + if (!in_array($adminDomain, $hostSources)) { + $hostSources[] = $adminDomain; + } + + return new FetchPolicy( + $policy->getId(), + $policy->isNoneAllowed(), + $hostSources, + $policy->getSchemeSources(), + $policy->isSelfAllowed(), + $policy->isInlineAllowed(), + $policy->isEvalAllowed(), + $policy->getNonceValues(), + $policy->getHashes(), + $policy->isDynamicAllowed(), + $policy->areEventHandlersAllowed() + ); + } +} diff --git a/vendor/magento/module-staging/Plugin/Framework/App/Response/HeaderProvider/XFrameOptionsPlugin.php b/vendor/magento/module-staging/Plugin/Framework/App/Response/HeaderProvider/XFrameOptionsPlugin.php new file mode 100644 index 000000000000..958af4667d38 --- /dev/null +++ b/vendor/magento/module-staging/Plugin/Framework/App/Response/HeaderProvider/XFrameOptionsPlugin.php @@ -0,0 +1,58 @@ +versionManager = $versionManager; + } + + /** + * Prevent X-Frame-Options header from being set during staging preview + * + * @param XFrameOptions $subject + * @param callable $proceed + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundCanApply(XFrameOptions $subject, callable $proceed): bool + { + if ($this->versionManager->isPreviewVersion()) { + return false; + } + + return $proceed(); + } +} diff --git a/vendor/magento/module-staging/etc/frontend/di.xml b/vendor/magento/module-staging/etc/frontend/di.xml index c049188bcf57..b8cb06398217 100644 --- a/vendor/magento/module-staging/etc/frontend/di.xml +++ b/vendor/magento/module-staging/etc/frontend/di.xml @@ -7,4 +7,12 @@ --> + + + + + + + + diff --git a/vendor/magento/module-staging/etc/module.xml b/vendor/magento/module-staging/etc/module.xml index 9e9d7620dd16..b0c5ec9c6e46 100644 --- a/vendor/magento/module-staging/etc/module.xml +++ b/vendor/magento/module-staging/etc/module.xml @@ -10,6 +10,7 @@ + diff --git a/vendor/magento/module-staging/view/adminhtml/web/js/preview/preview.js b/vendor/magento/module-staging/view/adminhtml/web/js/preview/preview.js index 11a7e0ee1c5a..b62c05826392 100644 --- a/vendor/magento/module-staging/view/adminhtml/web/js/preview/preview.js +++ b/vendor/magento/module-staging/view/adminhtml/web/js/preview/preview.js @@ -208,14 +208,25 @@ define([ initIFrameAjaxInterceptor: function () { var iFrameWindow = this.getIFrameWindow(), iFrameAjaxCallback = this.processIFrameAjax.bind(this), - timer = setInterval(function () { + timer; + + if (!iFrameWindow) { + return this; + } + + timer = setInterval(function () { + try { if (typeof iFrameWindow.require === 'function') { iFrameWindow.require(['jquery'], function (jQuery) { jQuery.ajaxPrefilter(iFrameAjaxCallback); }); clearInterval(timer); } - }, 100); + // eslint-disable-next-line no-unused-vars + } catch (e) { + clearInterval(timer); + } + }, 100); return this; }, @@ -228,8 +239,16 @@ define([ version = this.getPreviewVersion(), iFrameWindow = this.getIFrameWindow(), iframeUrl = this.getIFrameUrl(), - timer = setInterval(function () { - var breadcrumbs = iFrameWindow.document.querySelectorAll(selectors.breadcrumbsLinks); + timer, + breadcrumbs; + + if (!iFrameWindow) { + return; + } + + timer = setInterval(function () { + try { + breadcrumbs = iFrameWindow.document.querySelectorAll(selectors.breadcrumbsLinks); if (breadcrumbs.length) { breadcrumbs.forEach(function (item) { @@ -247,7 +266,11 @@ define([ }); clearInterval(timer); } - }, 100); + // eslint-disable-next-line no-unused-vars + } catch (e) { + clearInterval(timer); + } + }, 100); }, /** @@ -399,12 +422,17 @@ define([ /** * Returns window object of the IFrame. * - * @returns {Window} + * @returns {Window|null} */ getIFrameWindow: function () { var iframe = this.$iframe; - return iframe.contentDocument.defaultView; + try { + return iframe.contentDocument.defaultView; + // eslint-disable-next-line no-unused-vars + } catch (e) { + return null; + } }, /** @@ -413,7 +441,18 @@ define([ * @returns {String} */ getIFrameUrl: function () { - return this.getIFrameWindow().location.href; + var iFrameWindow = this.getIFrameWindow(); + + if (!iFrameWindow) { + return this.$iframe.src; + } + + try { + return iFrameWindow.location.href; + // eslint-disable-next-line no-unused-vars + } catch (e) { + return this.$iframe.src; + } }, /** @@ -422,7 +461,18 @@ define([ * @returns {Boolean} */ isIFrameLoaded: function () { - return this.getIFrameWindow().document.readyState === 'complete'; + var iFrameWindow = this.getIFrameWindow(); + + if (!iFrameWindow) { + return !!this.$iframe; + } + + try { + return iFrameWindow.document.readyState === 'complete'; + // eslint-disable-next-line no-unused-vars + } catch (e) { + return true; + } }, /**