Permalink
Browse files

perf(http): serve-file URLs can respond without booting core

Ensures unit tests can't call Application::bootCore
Fixes ServeFileHandler tests so request objects have cookies.
Injects specific services into ServeFileHandler.
Injects siteSecret into ElggCrypto.
Gets ServeFileHandler from service provider.
Moves cookie config to `Elgg\Config`.
Fixes site secret in PHPUnit bootstrap.
Ensures settings file loaded before Database is set up.
Ensures local settings.php isn't loaded in units (we don't want to override `wwwroot`, etc).
  • Loading branch information...
mrclay committed Feb 10, 2016
1 parent 7ea15f7 commit 4f587df02062c7d8f6b239041789030299af2bd6
@@ -232,6 +232,10 @@ public function bootCore() {
$config = $this->services->config;
+ if ($config->getVolatile('Elgg\Application_phpunit')) {
+ throw new \RuntimeException('Unit tests should not call ' . __METHOD__);
+ }
+
if ($config->getVolatile('boot_complete')) {
return;
}
@@ -392,7 +396,7 @@ public function run() {
}
if (0 === strpos($path, '/serve-file/')) {
- (new Application\ServeFileHandler($this))->getResponse($this->services->request)->send();
+ $this->services->serveFileHandler->getResponse($this->services->request)->send();
return true;
}
@@ -4,6 +4,7 @@
use DateTime;
use Elgg\Application;
+use Elgg\Config;
use Elgg\Http\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Response;
@@ -17,16 +18,25 @@
*/
class ServeFileHandler {
- /** @var Application */
- private $application;
+ /**
+ * @var \ElggCrypto
+ */
+ private $crypto;
+
+ /**
+ * @var Config
+ */
+ private $config;
/**
* Constructor
*
- * @param Application $app Elgg Application
+ * @param \ElggCrypto $crypto Crypto service
+ * @param Config $config Config service
*/
- public function __construct(Application $app) {
- $this->application = $app;
+ public function __construct(\ElggCrypto $crypto, Config $config) {
+ $this->crypto = $crypto;
+ $this->config = $config;
}
/**
@@ -35,7 +45,7 @@ public function __construct(Application $app) {
* @param Request $request HTTP request
* @return Response
*/
- public function getResponse($request) {
+ public function getResponse(Request $request) {
$response = new Response();
$response->prepare($request);
@@ -57,9 +67,6 @@ public function getResponse($request) {
return $response;
}
- // @todo: change to minimal boot without plugins
- $this->application->bootCore();
-
$hmac_data = array(
'expires' => (int) $expires,
'last_updated' => (int) $last_updated,
@@ -68,16 +75,16 @@ public function getResponse($request) {
'use_cookie' => (int) $use_cookie,
);
if ((bool) $use_cookie) {
- $hmac_data['cookie'] = _elgg_services()->session->getId();
+ $hmac_data['cookie'] = $this->getCookieValue($request);
}
ksort($hmac_data);
- $hmac = elgg_build_hmac($hmac_data);
+ $hmac = $this->crypto->getHmac($hmac_data);
if (!$hmac->matchesToken($mac)) {
return $response->setStatusCode(403)->setContent('HMAC mistmatch');
}
- $dataroot = _elgg_services()->config->getDataPath();
+ $dataroot = $this->config->getDataPath();
$filenameonfilestore = "{$dataroot}{$path_from_dataroot}";
if (!is_readable($filenameonfilestore)) {
@@ -105,4 +112,15 @@ public function getResponse($request) {
return $response;
}
+ /**
+ * Get the session ID from the cookie
+ *
+ * @param Request $request Elgg request
+ * @return string
+ */
+ private function getCookieValue(Request $request) {
+ $config = $this->config->getCookieConfig();
+ $session_name = $config['session']['name'];
+ return $request->cookies->get($session_name, '');
+ }
}
@@ -21,6 +21,11 @@ class Config implements Services\Config {
*/
private $settings_loaded = false;
+ /**
+ * @var bool
+ */
+ private $cookies_configured = false;
+
/**
* Constructor
*
@@ -80,6 +85,42 @@ public function getPluginsPath() {
return $this->config->pluginspath;
}
+ /**
+ * Set up and return the cookie configuration array resolved from settings.php
+ *
+ * @return array
+ */
+ public function getCookieConfig() {
+ $c = $this->config;
+
+ if ($this->cookies_configured) {
+ return $c->cookies;
+ }
+
+ $this->loadSettingsFile();
+
+ // set cookie values for session and remember me
+ if (!isset($c->cookies)) {
+ $c->cookies = array();
+ }
+ if (!isset($c->cookies['session'])) {
+ $c->cookies['session'] = array();
+ }
+ $session_defaults = session_get_cookie_params();
+ $session_defaults['name'] = 'Elgg';
+ $c->cookies['session'] = array_merge($session_defaults, $c->cookies['session']);
+ if (!isset($c->cookies['remember_me'])) {
+ $c->cookies['remember_me'] = array();
+ }
+ $session_defaults['name'] = 'elggperm';
+ $session_defaults['expire'] = strtotime("+30 days");
+ $c->cookies['remember_me'] = array_merge($session_defaults, $c->cookies['remember_me']);
+
+ $this->cookies_configured = true;
+
+ return $c->cookies;
+ }
+
/**
* {@inheritdoc}
*/
@@ -21,6 +21,35 @@
*/
class SiteSecret {
+ /**
+ * @var Datalist
+ */
+ private $datalist;
+
+ /**
+ * Constructor
+ *
+ * @param Datalist $datalist Datalist table
+ */
+ public function __construct(Datalist $datalist) {
+ $this->datalist = $datalist;
+ }
+
+ /**
+ * @var string
+ */
+ private $test_secret = '';
+
+ /**
+ * Set a secret to be used in testing
+ *
+ * @param string $secret Testing site secret. 32 alphanums starting with "z"
+ * @return void
+ */
+ public function setTestingSecret($secret) {
+ $this->test_secret = $secret;
+ }
+
/**
* Initialise the site secret (32 bytes: "z" to indicate format + 186-bit key in Base64 URL).
*
@@ -34,7 +63,7 @@ class SiteSecret {
function init() {
$secret = 'z' . _elgg_services()->crypto->getRandomString(31);
- if (_elgg_services()->datalist->set('__site_secret__', $secret)) {
+ if ($this->datalist->set('__site_secret__', $secret)) {
return $secret;
}
@@ -52,9 +81,13 @@ function init() {
* @access private
*/
function get($raw = false) {
- $secret = _elgg_services()->datalist->get('__site_secret__');
+ if ($this->test_secret) {
+ $secret = $this->test_secret;
+ } else {
+ $secret = $this->datalist->get('__site_secret__');
+ }
if (!$secret) {
- $secret = init_site_secret();
+ $secret = $this->init();
}
if ($raw) {
@@ -86,7 +119,7 @@ function get($raw = false) {
* @access private
*/
function getStrength() {
- $secret = get_site_secret();
+ $secret = $this->get();
if ($secret[0] !== 'z') {
$rand_max = getrandmax();
if ($rand_max < pow(2, 16)) {
@@ -98,5 +131,4 @@ function getStrength() {
}
return 'strong';
}
-
}
@@ -51,6 +51,7 @@
* @property-read \Elgg\Http\Request $request
* @property-read \Elgg\Database\RelationshipsTable $relationshipsTable
* @property-read \Elgg\Router $router
+ * @property-read \Elgg\Application\ServeFileHandler $serveFileHandler
* @property-read \ElggSession $session
* @property-read \Elgg\Cache\SimpleCache $simpleCache
* @property-read \Elgg\Database\SiteSecret $siteSecret
@@ -124,7 +125,9 @@ public function __construct(\Elgg\Config $config) {
$this->setClassName('context', \Elgg\Context::class);
- $this->setClassName('crypto', \ElggCrypto::class);
+ $this->setFactory('crypto', function(ServiceProvider $c) {
+ return new \ElggCrypto($c->siteSecret);
+ });
$this->setFactory('datalist', function(ServiceProvider $c) {
// TODO(ewinslow): Add back memcached support
@@ -135,6 +138,8 @@ public function __construct(\Elgg\Config $config) {
});
$this->setFactory('db', function(ServiceProvider $c) {
+ // gonna need dbprefix from settings
+ $c->config->loadSettingsFile();
$db_config = new \Elgg\Database\Config($c->config->getStorageObject());
// we inject the logger in _elgg_engine_boot()
@@ -208,7 +213,7 @@ public function __construct(\Elgg\Config $config) {
});
$this->setFactory('persistentLogin', function(ServiceProvider $c) {
- $global_cookies_config = $c->config->get('cookies');
+ $global_cookies_config = $c->config->getCookieConfig();
$cookie_config = $global_cookies_config['remember_me'];
$cookie_name = $cookie_config['name'];
$cookie_token = $c->request->cookies->get($cookie_name, '');
@@ -255,8 +260,12 @@ public function __construct(\Elgg\Config $config) {
return $router;
});
+ $this->setFactory('serveFileHandler', function(ServiceProvider $c) {
+ return new \Elgg\Application\ServeFileHandler($c->crypto, $c->config);
+ });
+
$this->setFactory('session', function(ServiceProvider $c) {
- $params = $c->config->get('cookies')['session'];
+ $params = $c->config->getCookieConfig()['session'];
$options = [
// session.cache_limiter is unfortunately set to "" by the NativeSessionStorage
// constructor, so we must capture and inject it directly.
@@ -280,7 +289,9 @@ public function __construct(\Elgg\Config $config) {
return new \Elgg\Cache\SimpleCache($c->config, $c->datalist, $c->views);
});
- $this->setClassName('siteSecret', \Elgg\Database\SiteSecret::class);
+ $this->setFactory('siteSecret', function(ServiceProvider $c) {
+ return new \Elgg\Database\SiteSecret($c->datalist);
+ });
$this->setClassName('stickyForms', \Elgg\Forms\StickyForms::class);
@@ -87,7 +87,7 @@ public function getURL() {
}
$relative_path = '';
- $root_prefix = _elgg_services()->config->get('dataroot');
+ $root_prefix = _elgg_services()->config->getDataPath();
$path = $this->file->getFilenameOnFilestore();
if (substr($path, 0, strlen($root_prefix)) == $root_prefix) {
$relative_path = substr($path, strlen($root_prefix));
@@ -124,7 +124,7 @@ public function getURL() {
}
ksort($data);
- $mac = elgg_build_hmac($data)->getToken();
+ $mac = _elgg_services()->crypto->getHmac($data)->getToken();
$url_segments = array(
'serve-file',
@@ -1,4 +1,7 @@
<?php
+
+use \Elgg\Database\SiteSecret;
+
/**
* \ElggCrypto
*
@@ -19,6 +22,20 @@ class ElggCrypto {
*/
const CHARS_HEX = '0123456789abcdef';
+ /**
+ * @var SiteSecret
+ */
+ private $siteSecret;
+
+ /**
+ * Constructor
+ *
+ * @param SiteSecret $siteSecret Secret service
+ */
+ public function __construct(SiteSecret $siteSecret) {
+ $this->siteSecret = $siteSecret;
+ }
+
/**
* Generate a string of highly randomized bytes (over the full 8-bit range).
*
@@ -168,7 +185,7 @@ public function getRandomBytes($length) {
*/
public function getHmac($data, $algo = 'sha256', $key = '') {
if (!$key) {
- $key = _elgg_services()->siteSecret->get(true);
+ $key = $this->siteSecret->get(true);
}
return new Elgg\Security\Hmac($key, [$this, 'areEqual'], $data, $algo);
}
@@ -836,7 +836,7 @@ protected function finishBootstraping($step) {
$this->CONFIG->site_id = $this->CONFIG->site_guid;
$this->CONFIG->site = get_entity($this->CONFIG->site_guid);
$this->CONFIG->dataroot = _elgg_services()->datalist->get('dataroot');
- _elgg_configure_cookies($this->CONFIG);
+ _elgg_services()->config->getCookieConfig();
_elgg_session_boot();
}
Oops, something went wrong.

0 comments on commit 4f587df

Please sign in to comment.