diff --git a/.htaccess b/.htaccess index 01f8f8dd24df5bf518774b24c9843bdd0d61d73a..9f630df0ae681e1dea7bca6587f1d2e082e7a23c 100644 --- a/.htaccess +++ b/.htaccess @@ -65,13 +65,6 @@ SecFilterScanPOST Off </IfModule> -<IfModule mod_headers.c> -############################################ -## prevent clickjacking - - Header set X-Frame-Options SAMEORIGIN -</IfModule> - <IfModule mod_deflate.c> ############################################ @@ -187,4 +180,4 @@ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags - #FileETag none \ No newline at end of file + #FileETag none diff --git a/app/code/Magento/Backend/etc/adminhtml/di.xml b/app/code/Magento/Backend/etc/adminhtml/di.xml index 553d30e85541f36dbd0a50834d1d146d99b84d5d..cccd487a9f5662ca3ee4ec187ecb4a4942ce8f9e 100644 --- a/app/code/Magento/Backend/etc/adminhtml/di.xml +++ b/app/code/Magento/Backend/etc/adminhtml/di.xml @@ -122,4 +122,9 @@ </argument> </arguments> </type> + <type name="Magento\Framework\App\Response\XFrameOptPlugin"> + <arguments> + <argument name="xFrameOpt" xsi:type="const">Magento\Framework\App\Response\XFrameOptPlugin::BACKEND_X_FRAME_OPT</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index 320253c316b88638328936cb3b273f3972a2ec22..a1f8ad99744b075c2675937f9d117e0da2a0d617 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -296,4 +296,8 @@ </argument> </arguments> </type> + <type name="Magento\Framework\App\Response\Http"> + <plugin name="xFrameOptionsHeader" type="Magento\Framework\App\Response\XFrameOptPlugin"/> + </type> + </config> diff --git a/app/etc/di.xml b/app/etc/di.xml index bf956a4b271b6fb22be26b6d668267db074349e8..0fb3f6ad44f65e3696ba97568fd424ad0ac11863 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -134,6 +134,11 @@ <preference for="Magento\Framework\Api\ImageContentValidatorInterface" type="Magento\Framework\Api\ImageContentValidator" /> <preference for="Magento\Framework\Api\ImageProcessorInterface" type="Magento\Framework\Api\ImageProcessor" /> <preference for="Magento\Framework\Code\Reader\ClassReaderInterface" type="Magento\Framework\Code\Reader\ClassReader" /> + <type name="Magento\Framework\App\Response\XFrameOptPlugin"> + <arguments> + <argument name="xFrameOpt" xsi:type="init_parameter">Magento\Framework\App\Response\XFrameOptPlugin::DEPLOYMENT_CONFIG_X_FRAME_OPT</argument> + </arguments> + </type> <type name="Magento\Framework\Model\Resource\Db\TransactionManager" shared="false" /> <type name="Magento\Framework\Logger\Handler\Base"> <arguments> diff --git a/lib/internal/Magento/Framework/App/Response/Http.php b/lib/internal/Magento/Framework/App/Response/Http.php index 55cda163252ab018807cc5d3323fc134e27146f0..864a203cf98b47418f73a248a3408a84bae6858d 100644 --- a/lib/internal/Magento/Framework/App/Response/Http.php +++ b/lib/internal/Magento/Framework/App/Response/Http.php @@ -21,6 +21,9 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response /** Format for expiration timestamp headers */ const EXPIRATION_TIMESTAMP_FORMAT = 'D, d M Y H:i:s T'; + /** X-FRAME-OPTIONS Header name */ + const HEADER_X_FRAME_OPT = 'X-Frame-Options'; + /** @var \Magento\Framework\Stdlib\CookieManagerInterface */ protected $cookieManager; @@ -51,6 +54,18 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response $this->dateTime = $dateTime; } + /** + * Sends the X-FRAME-OPTIONS header to protect against click-jacking + * + * @param string $value + * @return void + * @codeCoverageIgnore + */ + public function setXFrameOptions($value) + { + $this->setHeader(self::HEADER_X_FRAME_OPT, $value); + } + /** * Send Vary cookie * @@ -109,6 +124,7 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response * Set headers for no-cache responses * * @return void + * @codeCoverageIgnore */ public function setNoCacheHeaders() { @@ -122,6 +138,7 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response * * @param string $content String in JSON format * @return \Magento\Framework\App\Response\Http + * @codeCoverageIgnore */ public function representJson($content) { @@ -131,6 +148,7 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response /** * @return string[] + * @codeCoverageIgnore */ public function __sleep() { @@ -141,6 +159,7 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response * Need to reconstruct dependencies when being de-serialized. * * @return void + * @codeCoverageIgnore */ public function __wakeup() { @@ -154,6 +173,7 @@ class Http extends \Magento\Framework\HTTP\PhpEnvironment\Response * * @param string $time * @return string + * @codeCoverageIgnore */ protected function getExpirationHeader($time) { diff --git a/lib/internal/Magento/Framework/App/Response/XFrameOptPlugin.php b/lib/internal/Magento/Framework/App/Response/XFrameOptPlugin.php new file mode 100644 index 0000000000000000000000000000000000000000..d3441ccc2717caac472511657722894fec7e2cae --- /dev/null +++ b/lib/internal/Magento/Framework/App/Response/XFrameOptPlugin.php @@ -0,0 +1,43 @@ +<?php +/*** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\App\Response; + +/** + * Adds an X-FRAME-OPTIONS header to HTTP responses to safeguard against click-jacking. + * @codeCoverageIgnore + */ +class XFrameOptPlugin +{ + /** Deployment config key for frontend x-frame-options header value */ + const DEPLOYMENT_CONFIG_X_FRAME_OPT = 'x-frame-options'; + + /** Always send DENY in backend x-frame-options header */ + const BACKEND_X_FRAME_OPT = 'DENY'; + + /** + *The header value + * @var string + */ + private $xFrameOpt; + + /** + * @param string $xFrameOpt + */ + public function __construct($xFrameOpt) + { + $this->xFrameOpt = $xFrameOpt; + } + + /** + * @param \Magento\Framework\App\Response\Http $subject + * @return void + */ + public function beforeSendResponse(\Magento\Framework\App\Response\Http $subject) + { + $subject->setXFrameOptions($this->xFrameOpt); + } +} diff --git a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php index f6de3f89043da2b1ce6eddc60dc635fb45a1d2cb..3148109e53938eaabe116b2340b9a5ac2cd4e350 100644 --- a/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php +++ b/lib/internal/Magento/Framework/Config/ConfigOptionsListConstants.php @@ -22,6 +22,7 @@ class ConfigOptionsListConstants const CONFIG_PATH_DB_CONNECTION_DEFAULT = 'db/connection/default'; const CONFIG_PATH_DB_CONNECTIONS = 'db/connection'; const CONFIG_PATH_DB_PREFIX = 'db/table_prefix'; + const CONFIG_PATH_X_FRAME_OPT = 'x-frame-options'; /**#@-*/ /**#@+ @@ -67,7 +68,7 @@ class ConfigOptionsListConstants const KEY_INIT_STATEMENTS = 'initStatements'; const KEY_ACTIVE = 'active'; /**#@-*/ - + /** * Db config key */ diff --git a/setup/src/Magento/Setup/Model/ConfigGenerator.php b/setup/src/Magento/Setup/Model/ConfigGenerator.php index a1a07d52e20bd78e6542f82aa6c8534913ae19b6..42701b7da258da4fa4715aed4e3c9582e0c7dd5c 100644 --- a/setup/src/Magento/Setup/Model/ConfigGenerator.php +++ b/setup/src/Magento/Setup/Model/ConfigGenerator.php @@ -210,4 +210,18 @@ class ConfigGenerator return $configData; } + + /** + * Creates x-frame-options header config data + * + * @return ConfigData + */ + public function createXFrameConfig() + { + $configData = new ConfigData(ConfigFilePool::APP_ENV); + if ($this->deploymentConfig->get(ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT) === null) { + $configData->set(ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT, 'DENY'); + } + return $configData; + } } diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList.php b/setup/src/Magento/Setup/Model/ConfigOptionsList.php index 64f2a58515eca8addc153c5a8e6151b6b2a5bbfc..ee76fbf6d4ee9851c4b46052fbad10ea6f274a9c 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList.php @@ -158,6 +158,7 @@ class ConfigOptionsList implements ConfigOptionsListInterface } $configData[] = $this->configGenerator->createDbConfig($data); $configData[] = $this->configGenerator->createResourceConfig(); + $configData[] = $this->configGenerator->createXFrameConfig(); return $configData; } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..00a02f83b5f9d5f91344fbd3180b1f25ae382554 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigGeneratorTest.php @@ -0,0 +1,41 @@ +<?php +/*** + * Copyright © 2015 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Setup\Test\Unit\Model; + + +use Magento\Framework\Config\ConfigOptionsListConstants; + +class ConfigGeneratorTest extends \PHPUnit_Framework_TestCase +{ + /** @var \Magento\Framework\App\DeploymentConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $deploymentConfigMock; + /** @var \Magento\Setup\Model\ConfigGenerator | \PHPUnit_Framework_MockObject_MockObject */ + private $model; + + public function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->deploymentConfigMock = $this->getMockBuilder('Magento\Framework\App\DeploymentConfig') + ->disableOriginalConstructor() + ->getMock(); + $this->model = $objectManager->getObject( + 'Magento\Setup\Model\ConfigGenerator', + ['deploymentConfig' => $this->deploymentConfigMock] + ); + } + + public function testCreateXFrameConfig() + { + $this->deploymentConfigMock->expects($this->atLeastOnce()) + ->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT) + ->willReturn(null); + $configData = $this->model->createXFrameConfig(); + $this->assertSame('DENY', $configData->getData()[ConfigOptionsListConstants::CONFIG_PATH_X_FRAME_OPT]); + } +}