diff --git a/.htaccess b/.htaccess index a9ecde6a53358..61a6e70d03e7f 100644 --- a/.htaccess +++ b/.htaccess @@ -206,7 +206,7 @@ ########################################### ## Deny access to root files to hide sensitive application information - RedirectMatch 404 /\.git + RedirectMatch 403 /\.git order allow,deny @@ -277,10 +277,14 @@ deny from all - order allow,deny - deny from all + order allow,deny + deny from all +# For 404s and 403s that aren't handled by the application, show plain 404 response +ErrorDocument 404 /pub/errors/404.php +ErrorDocument 403 /pub/errors/404.php + ################################ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags diff --git a/.htaccess.sample b/.htaccess.sample index a6818c1e6b89a..61a6e70d03e7f 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -1,11 +1,12 @@ ############################################ -## Optional override of deployment mode. We recommend you use the -## command bin/magento deploy:mode:set to switch modes instead -# SetEnv MAGE_MODE default # or production or developer +## overrides deployment configuration mode value +## use command bin/magento deploy:mode:set to switch modes + +# SetEnv MAGE_MODE developer ############################################ -## Uncomment these lines for CGI mode. -## Make sure to specify the correct cgi php binary file name +## uncomment these lines for CGI mode +## make sure to specify the correct cgi php binary file name ## it might be /cgi-bin/php-cgi # Action php5-cgi /cgi-bin/php5-cgi @@ -16,42 +17,42 @@ # Options -MultiViews -## You might also need to add this line to php.ini +## you might also need to add this line to php.ini ## cgi.fix_pathinfo = 1 -## If it still doesn't work, rename php.ini to php5.ini +## if it still doesn't work, rename php.ini to php5.ini ############################################ -## This line is specific for 1and1 hosting +## this line is specific for 1and1 hosting #AddType x-mapp-php5 .php #AddHandler x-mapp-php5 .php ############################################ -## Default index file +## default index file DirectoryIndex index.php ############################################ -## Adjust memory limit +## adjust memory limit php_value memory_limit 768M php_value max_execution_time 18000 ############################################ -## Disable automatic session start +## disable automatic session start ## before autoload was initialized php_flag session.auto_start off ############################################ -## Enable resulting html compression +## enable resulting html compression #php_flag zlib.output_compression on ########################################### -## Disable user agent verification to not break multiple image upload +## disable user agent verification to not break multiple image upload php_flag suhosin.session.cryptua off @@ -60,24 +61,24 @@ ############################################ -## Adjust memory limit +## adjust memory limit php_value memory_limit 768M php_value max_execution_time 18000 ############################################ -## Disable automatic session start +## disable automatic session start ## before autoload was initialized php_flag session.auto_start off ############################################ -## Enable resulting html compression +## enable resulting html compression #php_flag zlib.output_compression on ########################################### -## Disable user agent verification to not break multiple image upload +## disable user agent verification to not break multiple image upload php_flag suhosin.session.cryptua off @@ -85,7 +86,7 @@ ########################################### -## Disable POST processing to not break multiple image upload +## disable POST processing to not break multiple image upload SecFilterEngine Off SecFilterScanPOST Off @@ -94,7 +95,7 @@ ############################################ -## Enable apache served files compression +## enable apache served files compression ## http://developer.yahoo.com/performance/rules.html#gzip # Insert filter on all content @@ -122,14 +123,14 @@ ############################################ -## Make HTTPS env vars available for CGI mode +## make HTTPS env vars available for CGI mode SSLOptions StdEnvVars ############################################ -## Workaround for Apache 2.4.6 CentOS build when working via ProxyPassMatch with HHVM (or any other) +## workaround for Apache 2.4.6 CentOS build when working via ProxyPassMatch with HHVM (or any other) ## Please, set it on virtual host configuration level ## SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1 @@ -138,19 +139,19 @@ ############################################ -## Enable rewrites +## enable rewrites Options +FollowSymLinks RewriteEngine on ############################################ -## You can put here your magento root folder +## you can put here your magento root folder ## path relative to web root #RewriteBase /magento/ ############################################ -## Workaround for HTTP authorization +## workaround for HTTP authorization ## in CGI environment RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] @@ -162,21 +163,21 @@ RewriteRule .* - [L,R=405] ############################################ -## Redirect for mobile user agents +## redirect for mobile user agents #RewriteCond %{REQUEST_URI} !^/mobiledirectoryhere/.*$ #RewriteCond %{HTTP_USER_AGENT} "android|blackberry|ipad|iphone|ipod|iemobile|opera mobile|palmos|webos|googlebot-mobile" [NC] #RewriteRule ^(.*)$ /mobiledirectoryhere/ [L,R=302] ############################################ -## Never rewrite for existing files, directories and links +## never rewrite for existing files, directories and links RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-l ############################################ -## Rewrite everything else to index.php +## rewrite everything else to index.php RewriteRule .* index.php [L] @@ -205,7 +206,7 @@ ########################################### ## Deny access to root files to hide sensitive application information - RedirectMatch 404 /\.git + RedirectMatch 403 /\.git order allow,deny @@ -280,6 +281,10 @@ deny from all +# For 404s and 403s that aren't handled by the application, show plain 404 response +ErrorDocument 404 /pub/errors/404.php +ErrorDocument 403 /pub/errors/404.php + ################################ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags diff --git a/app/code/Magento/PageCache/Model/Config.php b/app/code/Magento/PageCache/Model/Config.php index 729da5f73f45c..222d9d57e467a 100644 --- a/app/code/Magento/PageCache/Model/Config.php +++ b/app/code/Magento/PageCache/Model/Config.php @@ -147,7 +147,13 @@ protected function _getReplacements() \Magento\Store\Model\ScopeInterface::SCOPE_STORE ), '/* {{ ips }} */' => $this->_getAccessList(), - '/* {{ design_exceptions_code }} */' => $this->_getDesignExceptions() + '/* {{ design_exceptions_code }} */' => $this->_getDesignExceptions(), + // http headers get transformed by php `X-Forwarded-Proto: https` becomes $SERVER['HTTP_X_FORWARDED_PROTO'] = 'https' + // Apache and Nginx drop all headers with underlines by default. + '/* {{ ssl_offloaded_header }} */' => str_replace('_', '-', $this->_scopeConfig->getValue( + \Magento\Framework\HTTP\PhpEnvironment\Request::XML_PATH_OFFLOADER_HEADER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE)) + ]; } diff --git a/app/code/Magento/PageCache/Observer/ProcessLayoutRenderElement.php b/app/code/Magento/PageCache/Observer/ProcessLayoutRenderElement.php index f811e0386b56c..aa96c1060207e 100644 --- a/app/code/Magento/PageCache/Observer/ProcessLayoutRenderElement.php +++ b/app/code/Magento/PageCache/Observer/ProcessLayoutRenderElement.php @@ -59,6 +59,8 @@ protected function _wrapEsi( 'handles' => json_encode($layout->getUpdate()->getHandles()) ] ); + // Varnish does not support ESI over HTTPS must change to HTTP + $url = substr($url, 0, 5) === 'https' ? 'http' . substr($url, 5) : $url; return sprintf('', $url); } diff --git a/app/code/Magento/PageCache/etc/varnish3.vcl b/app/code/Magento/PageCache/etc/varnish3.vcl index 70a56aa46f052..763e462756d94 100644 --- a/app/code/Magento/PageCache/etc/varnish3.vcl +++ b/app/code/Magento/PageCache/etc/varnish3.vcl @@ -1,5 +1,7 @@ import std; # The minimal Varnish version is 3.0.5 +# For SSL offloading, pass the following header in your proxy server or load balancer: '/* {{ ssl_offloaded_header }} */: https' + backend default { .host = "/* {{ host }} */"; @@ -61,6 +63,7 @@ sub vcl_recv { # static files are always cacheable. remove SSL flag and cookie if (req.url ~ "^/(pub/)?(media|static)/.*\.(ico|css|js|jpg|jpeg|png|gif|tiff|bmp|gz|tgz|bz2|tbz|mp3|ogg|svg|swf|woff|woff2|eot|ttf|otf)$") { unset req.http.Https; + unset req.http./* {{ ssl_offloaded_header }} */; unset req.http.Cookie; } @@ -73,6 +76,10 @@ sub vcl_hash { if (req.http.cookie ~ "X-Magento-Vary=") { hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1")); } + + if (req.http./* {{ ssl_offloaded_header }} */) { + hash_data(req.http./* {{ ssl_offloaded_header }} */); + } /* {{ design_exceptions_code }} */ } diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index d9931c8eff35a..49a8fe1585519 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -2,6 +2,7 @@ vcl 4.0; import std; # The minimal Varnish version is 4.0 +# For SSL offloading, pass the following header in your proxy server or load balancer: '/* {{ ssl_offloaded_header }} */: https' backend default { .host = "/* {{ host }} */"; @@ -74,6 +75,7 @@ sub vcl_recv { # static files are always cacheable. remove SSL flag and cookie if (req.url ~ "^/(pub/)?(media|static)/.*\.(ico|css|js|jpg|jpeg|png|gif|tiff|bmp|mp3|ogg|svg|swf|woff|woff2|eot|ttf|otf)$") { unset req.http.Https; + unset req.http./* {{ ssl_offloaded_header }} */; unset req.http.Cookie; } @@ -93,8 +95,8 @@ sub vcl_hash { } # To make sure http users don't see ssl warning - if (req.http.X-Forwarded-Proto) { - hash_data(req.http.X-Forwarded-Proto); + if (req.http./* {{ ssl_offloaded_header }} */) { + hash_data(req.http./* {{ ssl_offloaded_header }} */); } /* {{ design_exceptions_code }} */ } diff --git a/app/code/Magento/Store/etc/config.xml b/app/code/Magento/Store/etc/config.xml index cf3abe126ea08..6bcc78be7baa4 100644 --- a/app/code/Magento/Store/etc/config.xml +++ b/app/code/Magento/Store/etc/config.xml @@ -76,7 +76,7 @@ {{secure_base_url}} 0 0 - SSL_OFFLOADED + X-Forwarded-Proto 0 diff --git a/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php b/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php index 32b2bfdcf352a..8698cb47dcc19 100644 --- a/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php @@ -49,12 +49,16 @@ class SoapTest extends \PHPUnit_Framework_TestCase */ protected $_appStateMock; + + protected $_appconfig; /** * Set up Controller object. */ protected function setUp() { parent::setUp(); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->_soapServerMock = $this->getMockBuilder('Magento\Webapi\Model\Soap\Server') ->disableOriginalConstructor() @@ -95,6 +99,15 @@ protected function setUp() ->method('getHeaders') ->will($this->returnValue(new \Zend\Http\Headers())); + $appconfig = $this->getMock(\Magento\Framework\App\Config::class, [], [], '' , false); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->_requestMock, + 'appConfig', + $appconfig + ); + + + $this->_soapServerMock->expects($this->any())->method('setWSDL')->will($this->returnSelf()); $this->_soapServerMock->expects($this->any())->method('setEncoding')->will($this->returnSelf()); $this->_soapServerMock->expects($this->any())->method('setReturnResponse')->will($this->returnSelf()); diff --git a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php index 8be53da3b14d2..1b34a887f2052 100644 --- a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php @@ -372,7 +372,7 @@ public function isUseStoreInUrlDataProvider() * * @param bool $expected * @param array $serverValues - * @magentoConfigFixture current_store web/secure/offloader_header SSL_OFFLOADED + * @magentoConfigFixture current_store web/secure/offloader_header X_FORWARDED_PROTO * @magentoConfigFixture current_store web/secure/base_url https://example.com:80 */ public function testIsCurrentlySecure($expected, $serverValues) @@ -391,8 +391,8 @@ public function isCurrentlySecureDataProvider() { return [ [true, ['HTTPS' => 'on']], - [true, ['SSL_OFFLOADED' => 'https']], - [true, ['HTTP_SSL_OFFLOADED' => 'https']], + [true, ['X_FORWARDED_PROTO' => 'https']], + [true, ['HTTP_X_FORWARDED_PROTO' => 'https']], [true, ['HTTPS' => 'on', 'SERVER_PORT' => 80]], [false, ['SERVER_PORT' => 80]], [false, []], diff --git a/lib/internal/Magento/Framework/App/Bootstrap.php b/lib/internal/Magento/Framework/App/Bootstrap.php index 6658b099c1204..2420e5ac72362 100644 --- a/lib/internal/Magento/Framework/App/Bootstrap.php +++ b/lib/internal/Magento/Framework/App/Bootstrap.php @@ -404,15 +404,18 @@ public function getErrorCode() */ public function isDeveloperMode() { - if (isset($this->server[State::PARAM_MODE]) && $this->server[State::PARAM_MODE] == State::MODE_DEVELOPER) { - return true; - } - /** @var \Magento\Framework\App\DeploymentConfig $deploymentConfig */ - $deploymentConfig = $this->getObjectManager()->get('Magento\Framework\App\DeploymentConfig'); - if ($deploymentConfig->get(State::PARAM_MODE) == State::MODE_DEVELOPER) { - return true; + $mode = 'default'; + if (isset($this->server[State::PARAM_MODE])) { + $mode = $this->server[State::PARAM_MODE]; + } else { + $deploymentConfig = $this->getObjectManager()->get(DeploymentConfig::class); + $configMode = $deploymentConfig->get(State::PARAM_MODE); + if ($configMode) { + $mode = $configMode; + } } - return false; + + return $mode == State::MODE_DEVELOPER; } /** diff --git a/lib/internal/Magento/Framework/App/Config/ScopePool.php b/lib/internal/Magento/Framework/App/Config/ScopePool.php index 172d1117d2e8f..d366349722f0f 100644 --- a/lib/internal/Magento/Framework/App/Config/ScopePool.php +++ b/lib/internal/Magento/Framework/App/Config/ScopePool.php @@ -91,8 +91,14 @@ private function getRequest() public function getScope($scopeType, $scopeCode = null) { $scopeCode = $this->_getScopeCode($scopeType, $scopeCode); - $baseUrl = $this->getRequest()->getDistroBaseUrl(); - $code = $scopeType . '|' . $scopeCode . '|' . $baseUrl; + + // Key by url to support dynamic {{base_url}} and port assignments + $host = $this->getRequest()->getHttpHost(); + $port = $this->getRequest()->getServer('SERVER_PORT'); + $path = $this->getRequest()->getBasePath(); + $urlInfo = $host . $port . trim($path, '/'); + $code = $scopeType . '|' . $scopeCode . '|' . $urlInfo; + if (!isset($this->_scopes[$code])) { $cacheKey = $this->_cacheId . '|' . $code; $data = $this->_cache->load($cacheKey); diff --git a/lib/internal/Magento/Framework/App/Request/Http.php b/lib/internal/Magento/Framework/App/Request/Http.php index 2bb5380be937a..434a2e3ebae16 100644 --- a/lib/internal/Magento/Framework/App/Request/Http.php +++ b/lib/internal/Magento/Framework/App/Request/Http.php @@ -5,7 +5,6 @@ */ namespace Magento\Framework\App\Request; -use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\RequestSafetyInterface; use Magento\Framework\App\Route\ConfigInterface\Proxy as ConfigInterface; @@ -318,16 +317,14 @@ public function getDistroBaseUrl() { $headerHttpHost = $this->getServer('HTTP_HOST'); $headerHttpHost = $this->converter->cleanString($headerHttpHost); - $headerServerPort = $this->getServer('SERVER_PORT'); $headerScriptName = $this->getServer('SCRIPT_NAME'); - $headerHttps = $this->getServer('HTTPS'); if (isset($headerScriptName) && isset($headerHttpHost)) { - $secure = !empty($headerHttps) - && $headerHttps != 'off' - || isset($headerServerPort) - && $headerServerPort == '443'; - $scheme = ($secure ? 'https' : 'http') . '://'; + if ($secure = $this->isSecure()) { + $scheme = 'https://'; + } else { + $scheme = 'http://'; + } $hostArr = explode(':', $headerHttpHost); $host = $hostArr[0]; @@ -403,29 +400,7 @@ public function __sleep() return []; } - /** - * {@inheritdoc} - * - * @return bool - */ - public function isSecure() - { - if ($this->immediateRequestSecure()) { - return true; - } - /* TODO: Untangle Config dependence on Scope, so that this class can be instantiated even if app is not - installed MAGETWO-31756 */ - // Check if a proxy sent a header indicating an initial secure request - $config = $this->objectManager->get('Magento\Framework\App\Config'); - $offLoaderHeader = trim( - (string)$config->getValue( - self::XML_PATH_OFFLOADER_HEADER, - ScopeConfigInterface::SCOPE_TYPE_DEFAULT - ) - ); - - return $this->initialRequestSecure($offLoaderHeader); - } + /** * {@inheritdoc} @@ -442,28 +417,5 @@ public function isSafeMethod() return $this->isSafeMethod; } - /** - * Checks if the immediate request is delivered over HTTPS - * - * @return bool - */ - protected function immediateRequestSecure() - { - $https = $this->getServer('HTTPS'); - return !empty($https) && ($https != 'off'); - } - - /** - * In case there is a proxy server, checks if the initial request to the proxy was delivered over HTTPS - * - * @param string $offLoaderHeader - * @return bool - */ - protected function initialRequestSecure($offLoaderHeader) - { - $header = $this->getServer($offLoaderHeader); - $httpHeader = $this->getServer('HTTP_' . $offLoaderHeader); - return !empty($offLoaderHeader) - && (isset($header) && ($header === 'https') || isset($httpHeader) && ($httpHeader === 'https')); - } + } diff --git a/lib/internal/Magento/Framework/App/StaticResource.php b/lib/internal/Magento/Framework/App/StaticResource.php index d591debb68f72..4367e8905e8c2 100644 --- a/lib/internal/Magento/Framework/App/StaticResource.php +++ b/lib/internal/Magento/Framework/App/StaticResource.php @@ -5,7 +5,9 @@ */ namespace Magento\Framework\App; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\ObjectManager\ConfigLoaderInterface; +use Magento\Framework\Filesystem; /** * Entry point for retrieving static resources like JS, CSS, images by requested public path @@ -14,46 +16,33 @@ */ class StaticResource implements \Magento\Framework\AppInterface { - /** - * @var State - */ + /** @var State */ private $state; - /** - * @var \Magento\Framework\App\Response\FileInterface - */ + /** @var \Magento\Framework\App\Response\FileInterface */ private $response; - /** - * @var Request\Http - */ + /** @var Request\Http */ private $request; - /** - * @var View\Asset\Publisher - */ + /** @var View\Asset\Publisher */ private $publisher; - /** - * @var \Magento\Framework\View\Asset\Repository - */ + /** @var \Magento\Framework\View\Asset\Repository */ private $assetRepo; - /** - * @var \Magento\Framework\Module\ModuleList - */ + /** @var \Magento\Framework\Module\ModuleList */ private $moduleList; - /** - * @var \Magento\Framework\ObjectManagerInterface - */ + /** @var \Magento\Framework\ObjectManagerInterface */ private $objectManager; - /** - * @var ConfigLoaderInterface - */ + /** @var ConfigLoaderInterface */ private $configLoader; + /** @var Filesystem */ + private $filesystem; + /** * @param State $state * @param Response\FileInterface $response @@ -116,12 +105,14 @@ public function launch() */ public function catchException(Bootstrap $bootstrap, \Exception $exception) { - $this->response->setHttpResponseCode(404); - $this->response->setHeader('Content-Type', 'text/plain'); if ($bootstrap->isDeveloperMode()) { + $this->response->setHttpResponseCode(404); + $this->response->setHeader('Content-Type', 'text/plain'); $this->response->setBody($exception->getMessage() . "\n" . $exception->getTraceAsString()); + $this->response->sendResponse(); + } else { + require $this->getFilesystem()->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/404.php'); } - $this->response->sendResponse(); return true; } @@ -156,4 +147,18 @@ protected function parsePath($path) $result['file'] = $parts[5]; return $result; } + + /** + * Lazyload filesystem driver + * + * @deprecated + * @return Filesystem + */ + private function getFilesystem() + { + if (!$this->filesystem) { + $this->filesystem = $this->objectManager->get(Filesystem::class); + } + return $this->filesystem; + } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php b/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php index e4b5de3f74ee8..8929407ce48b8 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/BootstrapTest.php @@ -162,26 +162,35 @@ public function testGetObjectManager() $this->assertSame($this->objectManager, $bootstrap->getObjectManager()); } - public function testIsDeveloperMode() + /** + * @param $modeFromEnvironment + * @param $modeFromDeployment + * @param $isDeveloper + * + * @dataProvider testIsDeveloperModeDataProvider + */ + public function testIsDeveloperMode($modeFromEnvironment, $modeFromDeployment, $isDeveloper) { - $bootstrap = self::createBootstrap(); - $this->assertFalse($bootstrap->isDeveloperMode()); - $testParams = [State::PARAM_MODE => State::MODE_DEVELOPER]; + $testParams = []; + if ($modeFromEnvironment) { + $testParams[State::PARAM_MODE] = $modeFromEnvironment; + } + if ($modeFromDeployment) { + $this->deploymentConfig->method('get')->willReturn($modeFromDeployment); + } $bootstrap = self::createBootstrap($testParams); - $this->assertTrue($bootstrap->isDeveloperMode()); - $this->deploymentConfig->expects($this->any())->method('get')->willReturn(State::MODE_DEVELOPER); - $bootstrap = self::createBootstrap(); - $this->assertTrue($bootstrap->isDeveloperMode()); + $this->assertEquals($isDeveloper, $bootstrap->isDeveloperMode()); } - public function testIsDeveloperModeСontradictoryValues() + public function testIsDeveloperModeDataProvider() { - $this->deploymentConfig->expects($this->any())->method('get')->willReturn(State::MODE_PRODUCTION); - $bootstrap = self::createBootstrap(); - $this->assertFalse($bootstrap->isDeveloperMode()); - $testParams = [State::PARAM_MODE => State::MODE_DEVELOPER]; - $bootstrap = self::createBootstrap($testParams); - $this->assertTrue($bootstrap->isDeveloperMode()); + return [ + [null, null, false], + [State::MODE_DEVELOPER, State::MODE_PRODUCTION, true], + [State::MODE_PRODUCTION, State::MODE_DEVELOPER, false], + [null, State::MODE_DEVELOPER, true], + [null, State::MODE_PRODUCTION, false] + ]; } public function testRunNoErrors() diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Config/ScopePoolTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Config/ScopePoolTest.php index c0799b49eab76..ef95d05dd9c9d 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Config/ScopePoolTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Config/ScopePoolTest.php @@ -58,7 +58,7 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods( [ - 'getDistroBaseUrl', + 'getBasePath', 'getModuleName', 'setModuleName', 'getActionName', @@ -68,6 +68,8 @@ protected function setUp() 'setParams', 'getCookie', 'isSecure', + 'getServer', + 'getHttpHost' ] )->getMock(); $reflection = new \ReflectionClass(get_class($this->_object)); @@ -75,7 +77,7 @@ protected function setUp() $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($this->_object, $requestMock); $requestMock->expects($this->any()) - ->method('getDistroBaseUrl') + ->method('getBasePath') ->willReturn('baseUrl'); } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpTest.php index 1dd2e50fd8334..fcb2046a14d65 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Request/HttpTest.php @@ -32,15 +32,20 @@ class HttpTest extends \PHPUnit_Framework_TestCase protected $_infoProcessorMock; /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager | \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager | \PHPUnit_Framework_MockObject_MockObject */ - protected $objectManager; + protected $objectManagerMock; /** - * @var \Magento\Framework\Stdlib\StringUtils | \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Stdlib\StringUtils | \PHPUnit_Framework_MockObject_MockObject */ protected $converterMock; + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + /** * @var array */ @@ -58,7 +63,7 @@ protected function setUp() ); $this->_infoProcessorMock = $this->getMock('Magento\Framework\App\Request\PathInfoProcessorInterface'); $this->_infoProcessorMock->expects($this->any())->method('process')->will($this->returnArgument(1)); - $this->objectManager = $this->getMock('Magento\Framework\ObjectManagerInterface'); + $this->objectManagerMock = $this->getMock('Magento\Framework\ObjectManagerInterface'); $this->converterMock = $this->getMockBuilder('Magento\Framework\Stdlib\StringUtils') ->disableOriginalConstructor() ->setMethods(['cleanString']) @@ -67,6 +72,8 @@ protected function setUp() // Stash the $_SERVER array to protect it from modification in test $this->serverArray = $_SERVER; + + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); } public function tearDown() @@ -77,19 +84,26 @@ public function tearDown() /** * @return \Magento\Framework\App\Request\Http */ - private function getModel($uri = null) + private function getModel($uri = null, $appConfigMock = true) { - $testFrameworkObjectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager ($this); - return $testFrameworkObjectManager->getObject( + + $model = $this->objectManager->getObject( 'Magento\Framework\App\Request\Http', [ 'routeConfig' => $this->_routerListMock, 'pathInfoProcessor' => $this->_infoProcessorMock, - 'objectManager' => $this->objectManager, + 'objectManager' => $this->objectManagerMock, 'converter' => $this->converterMock, 'uri' => $uri, ] ); + + if ($appConfigMock) { + $configMock = $this->getMock(\Magento\Framework\App\Config::class, [], [], '' , false); + $this->objectManager->setBackwardCompatibleProperty($model, 'appConfig', $configMock); + } + + return $model; } public function testGetOriginalPathInfoWithTestUri() @@ -329,7 +343,7 @@ public function serverVariablesProvider() */ public function testIsSecure($isSecure, $serverHttps, $headerOffloadKey, $headerOffloadValue, $configCall) { - $this->_model = $this->getModel(); + $this->_model = $this->getModel(null, false); $configOffloadHeader = 'Header-From-Proxy'; $configMock = $this->getMockBuilder('Magento\Framework\App\Config') ->disableOriginalConstructor() @@ -339,10 +353,9 @@ public function testIsSecure($isSecure, $serverHttps, $headerOffloadKey, $header ->method('getValue') ->with(\Magento\Framework\App\Request\Http::XML_PATH_OFFLOADER_HEADER, ScopeConfigInterface::SCOPE_TYPE_DEFAULT) ->willReturn($configOffloadHeader); - $this->objectManager->expects($this->exactly($configCall)) - ->method('get') - ->with('Magento\Framework\App\Config') - ->will($this->returnValue($configMock)); + + $this->objectManager->setBackwardCompatibleProperty($this->_model, 'appConfig', $configMock); + $this->objectManager->setBackwardCompatibleProperty($this->_model, 'sslOffloadHeader', null); $this->_model->getServer()->set($headerOffloadKey, $headerOffloadValue); $this->_model->getServer()->set('HTTPS', $serverHttps); @@ -409,18 +422,18 @@ public function isSecureDataProvider() * ] */ return [ - 'Test 1' => [true, 'on', 'Header-From-Proxy', 'https', 0], - 'Test 2' => [true, 'off', 'Header-From-Proxy', 'https', 1], - 'Test 3' => [true, 'any-string', 'Header-From-Proxy', 'https', 0], - 'Test 4' => [true, 'on', 'Header-From-Proxy', 'http', 0], - 'Test 5' => [false, 'off', 'Header-From-Proxy', 'http', 1], - 'Test 6' => [true, 'any-string', 'Header-From-Proxy', 'http', 0], - 'Test 7' => [true, 'on', 'Header-From-Proxy', 'any-string', 0], - 'Test 8' => [false, 'off', 'Header-From-Proxy', 'any-string', 1], - 'Test 9' => [true, 'any-string', 'Header-From-Proxy', 'any-string', 0], - 'blank HTTPS with proxy set https' => [true, '', 'Header-From-Proxy', 'https', 1], - 'blank HTTPS with proxy set http' => [false, '', 'Header-From-Proxy', 'http', 1], - 'HTTPS off with HTTP_ prefixed proxy set to https' => [true, 'off', 'HTTP_Header-From-Proxy', 'https', 1], + 'Test 1' => [true, 'on', 'HEADER_FROM_PROXY', 'https', 0], + 'Test 2' => [true, 'off', 'HEADER_FROM_PROXY', 'https', 1], + 'Test 3' => [true, 'any-string', 'HEADER_FROM_PROXY', 'https', 0], + 'Test 4' => [true, 'on', 'HEADER_FROM_PROXY', 'http', 0], + 'Test 5' => [false, 'off', 'HEADER_FROM_PROXY', 'http', 1], + 'Test 6' => [true, 'any-string', 'HEADER_FROM_PROXY', 'http', 0], + 'Test 7' => [true, 'on', 'HEADER_FROM_PROXY', 'any-string', 0], + 'Test 8' => [false, 'off', 'HEADER_FROM_PROXY', 'any-string', 1], + 'Test 9' => [true, 'any-string', 'HEADER_FROM_PROXY', 'any-string', 0], + 'blank HTTPS with proxy set https' => [true, '', 'HEADER_FROM_PROXY', 'https', 1], + 'blank HTTPS with proxy set http' => [false, '', 'HEADER_FROM_PROXY', 'http', 1], + 'HTTPS off with HTTP_ prefixed proxy set to https' => [true, 'off', 'HTTP_HEADER_FROM_PROXY', 'https', 1], ]; } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/StaticResourceTest.php b/lib/internal/Magento/Framework/App/Test/Unit/StaticResourceTest.php index 78d5cc7f5acc9..32e028347a61d 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/StaticResourceTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/StaticResourceTest.php @@ -8,6 +8,9 @@ namespace Magento\Framework\App\Test\Unit; +use Magento\Framework\App\Bootstrap; +use Magento\Framework\Filesystem; + class StaticResourceTest extends \PHPUnit_Framework_TestCase { /** @@ -188,17 +191,13 @@ public function testLaunchWrongPath() $this->object->launch(); } - public function testCatchException() + public function testCatchExceptionDeveloperMode() { - $bootstrap = $this->getMock('Magento\Framework\App\Bootstrap', [], [], '', false); - $bootstrap->expects($this->at(0))->method('isDeveloperMode')->willReturn(false); - $bootstrap->expects($this->at(1))->method('isDeveloperMode')->willReturn(true); - $exception = new \Exception('message'); - $this->response->expects($this->exactly(2))->method('setHttpResponseCode')->with(404); - $this->response->expects($this->exactly(2))->method('setHeader')->with('Content-Type', 'text/plain'); - $this->response->expects($this->exactly(2))->method('sendResponse'); - $this->response->expects($this->once())->method('setBody')->with($this->stringStartsWith('message')); - $this->assertTrue($this->object->catchException($bootstrap, $exception)); + $bootstrap = $this->getMockBuilder(Bootstrap::class)->disableOriginalConstructor()->getMock(); + $bootstrap->expects($this->once())->method('isDeveloperMode')->willReturn(true); + $exception = new \Exception('Error: nothing works'); + $this->response->expects($this->once())->method('setHttpResponseCode')->with(404); + $this->response->expects($this->once())->method('sendResponse'); $this->assertTrue($this->object->catchException($bootstrap, $exception)); } } diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php index 562346560c0b8..64f8dd4564e65 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php @@ -22,6 +22,9 @@ class Request extends \Zend\Http\PhpEnvironment\Request const SCHEME_HTTPS = 'https'; /**#@-*/ + // Configuration path for SSL Offload http header + const XML_PATH_OFFLOADER_HEADER = 'web/secure/offloader_header'; + /** * @var string */ @@ -85,6 +88,18 @@ class Request extends \Zend\Http\PhpEnvironment\Request */ protected $converter; + /** + * @var \Magento\Framework\App\Config + */ + protected $appConfig; + + /** + * Name of http header to check for ssl offloading default value is X-Forwarded-Proto + * + * @var string + */ + protected $sslOffloadHeader; + /** * @param CookieReaderInterface $cookieReader * @param StringUtils $converter @@ -364,7 +379,7 @@ public function clearParams() */ public function getScheme() { - return ($this->getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP; + return $this->isSecure() ? self::SCHEME_HTTPS : self::SCHEME_HTTP; } /** @@ -396,7 +411,79 @@ public function isDispatched() */ public function isSecure() { - return ($this->getScheme() == self::SCHEME_HTTPS); + if ($this->immediateRequestSecure()) { + return true; + } + + return $this->initialRequestSecure($this->SslOffloadHeader()); + } + + /*** + * Get value of SSL offload http header from configuration - defaults to X-Forwarded-Proto + * + * @return string + */ + private function SslOffloadHeader() + { + // Lets read from db only one time okay. + if ($this->sslOffloadHeader === null) { + + // @todo: Untangle Config dependence on Scope, so that this class can be instantiated even if app is not + // installed MAGETWO-31756 + // Check if a proxy sent a header indicating an initial secure request + $this->sslOffloadHeader = trim( + (string)$this->getAppConfig()->getValue( + self::XML_PATH_OFFLOADER_HEADER, + \Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ) + ); + } + + return $this->sslOffloadHeader; + } + + /** + * Create an instance of Magento\Framework\App\Config + * + * @return \Magento\Framework\App\Config + * @deprecated + */ + private function getAppConfig() + { + if ($this->appConfig == null) { + $this->appConfig = + \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Framework\App\Config::class); + } + return $this->appConfig; + } + + /** + * Checks if the immediate request is delivered over HTTPS + * + * @return bool + */ + protected function immediateRequestSecure() + { + $https = $this->getServer('HTTPS'); + $headerServerPort = $this->getServer('SERVER_PORT'); + return (!empty($https) && $https != 'off') || $headerServerPort == 443; + } + + /** + * In case there is a proxy server, checks if the initial request to the proxy was delivered over HTTPS + * + * @param string $offLoaderHeader + * @return bool + */ + protected function initialRequestSecure($offLoaderHeader) + { + // Transform http header to $_SERVER format ie X-Forwarded-Proto becomes $_SERVER['HTTP_X_FORWARDED_PROTO'] + $offLoaderHeader = str_replace('-', '_', strtoupper($offLoaderHeader)); + // Some webservers do not append HTTP_ + $header = $this->getServer($offLoaderHeader); + // Apache appends HTTP_ + $httpHeader = $this->getServer('HTTP_' . $offLoaderHeader); + return !empty($offLoaderHeader) && ($header === 'https' || $httpHeader === 'https'); } /** diff --git a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php index a1ecfb2ed4f88..95163d1ed8ae7 100644 --- a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php @@ -491,6 +491,7 @@ public function getRebuiltUrlDataProvider() return [ 'with port' => ['https://example.com:88/index.php/catalog/index/view?query=123#hash'], 'without port' => ['https://example.com/index.php/catalog/index/view?query=123#hash'], + 'http' => ['http://example.com/index.php/catalog/index/view?query=123#hash'] ]; } diff --git a/nginx.conf.sample b/nginx.conf.sample index 3f9eaba55defa..95f03f4dd2145 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -30,6 +30,7 @@ root $MAGE_ROOT/pub; index index.php; autoindex off; charset UTF-8; +error_page 404 403 = /errors/404.php; #add_header "X-UA-Compatible" "IE=Edge"; # PHP entry point for setup application @@ -190,6 +191,7 @@ gzip_types image/svg+xml; gzip_vary on; -location ~ \.php$ { +# Banned locations (only reached if the earlier PHP entry point regexes don't match) +location ~* (\.php$|\.htaccess$|\.git) { deny all; } diff --git a/pub/.htaccess b/pub/.htaccess index 6d70d0f19a838..926c012eef6a5 100644 --- a/pub/.htaccess +++ b/pub/.htaccess @@ -197,6 +197,10 @@ deny from all +# For 404s and 403s that aren't handled by the application, show plain 404 response +ErrorDocument 404 /errors/404.php +ErrorDocument 403 /errors/404.php + ############################################ ## If running in cluster environment, uncomment this ## http://developer.yahoo.com/performance/rules.html#etags diff --git a/pub/get.php b/pub/get.php index 760f922adb140..3be707560b8c5 100644 --- a/pub/get.php +++ b/pub/get.php @@ -45,13 +45,13 @@ // Serve file if it's materialized if ($mediaDirectory) { if (!$isAllowed($relativePath, $allowedResources)) { - header('HTTP/1.0 404 Not Found'); + require_once 'errors/404.php'; exit; } $mediaAbsPath = $mediaDirectory . '/' . $relativePath; if (is_readable($mediaAbsPath)) { if (is_dir($mediaAbsPath)) { - header('HTTP/1.0 404 Not Found'); + require_once 'errors/404.php'; exit; } $transfer = new \Magento\Framework\File\Transfer\Adapter\Http(