From d60cec33be2352a9639e28044bc98cae617842c1 Mon Sep 17 00:00:00 2001 From: Franz Liedke Date: Sun, 18 Mar 2018 15:58:31 +0100 Subject: [PATCH] Bind session handling to request lifecycle With this change, session objects are no longer instantiated globally, but instead created within a middleware during the request lifecycle. In addition, session garbage collection is integrated with the already existing middleware for this purpose. --- framework/core/src/Foundation/Application.php | 2 - framework/core/src/Foundation/Site.php | 4 -- .../src/Http/Middleware/CollectGarbage.php | 25 ++++++++ .../core/src/Http/Middleware/StartSession.php | 63 ++++++++----------- .../core/src/User/UserServiceProvider.php | 24 ++++++- 5 files changed, 73 insertions(+), 45 deletions(-) diff --git a/framework/core/src/Foundation/Application.php b/framework/core/src/Foundation/Application.php index 1c8ba7718..2753291b5 100644 --- a/framework/core/src/Foundation/Application.php +++ b/framework/core/src/Foundation/Application.php @@ -718,8 +718,6 @@ class Application extends Container implements ApplicationContract 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], 'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class], - 'session' => [\Illuminate\Session\SessionManager::class], - 'session.driver' => [\Illuminate\Contracts\Session\Session::class], 'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class], 'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class], ]; diff --git a/framework/core/src/Foundation/Site.php b/framework/core/src/Foundation/Site.php index f1978f217..9f17b4a27 100644 --- a/framework/core/src/Foundation/Site.php +++ b/framework/core/src/Foundation/Site.php @@ -240,12 +240,8 @@ class Site ] ], 'session' => [ - 'driver' => 'file', 'lifetime' => 120, - 'expire_on_close' => false, - 'encrypt' => false, 'files' => $app->storagePath().'/sessions', - 'lottery' => [2, 100], 'cookie' => 'session' ] ]); diff --git a/framework/core/src/Http/Middleware/CollectGarbage.php b/framework/core/src/Http/Middleware/CollectGarbage.php index 4754f2300..f4882c9fb 100644 --- a/framework/core/src/Http/Middleware/CollectGarbage.php +++ b/framework/core/src/Http/Middleware/CollectGarbage.php @@ -15,12 +15,30 @@ use Flarum\Http\AccessToken; use Flarum\User\AuthToken; use Flarum\User\EmailToken; use Flarum\User\PasswordToken; +use Illuminate\Contracts\Config\Repository as ConfigRepository; use Interop\Http\ServerMiddleware\DelegateInterface; use Interop\Http\ServerMiddleware\MiddlewareInterface; use Psr\Http\Message\ServerRequestInterface as Request; +use SessionHandlerInterface; class CollectGarbage implements MiddlewareInterface { + /** + * @var SessionHandlerInterface + */ + protected $sessionHandler; + + /** + * @var array + */ + protected $sessionConfig; + + public function __construct(SessionHandlerInterface $handler, ConfigRepository $config) + { + $this->sessionHandler = $handler; + $this->sessionConfig = $config->get('session'); + } + public function process(Request $request, DelegateInterface $delegate) { $this->collectGarbageSometimes(); @@ -43,10 +61,17 @@ class CollectGarbage implements MiddlewareInterface EmailToken::where('created_at', '<=', $earliestToKeep)->delete(); PasswordToken::where('created_at', '<=', $earliestToKeep)->delete(); AuthToken::where('created_at', '<=', $earliestToKeep)->delete(); + + $this->sessionHandler->gc($this->getSessionLifetimeInSeconds()); } private function hit() { return mt_rand(1, 100) <= 2; } + + private function getSessionLifetimeInSeconds() + { + return $this->sessionConfig['lifetime'] * 60; + } } diff --git a/framework/core/src/Http/Middleware/StartSession.php b/framework/core/src/Http/Middleware/StartSession.php index 701a30732..7447aceea 100644 --- a/framework/core/src/Http/Middleware/StartSession.php +++ b/framework/core/src/Http/Middleware/StartSession.php @@ -13,19 +13,21 @@ namespace Flarum\Http\Middleware; use Dflydev\FigCookies\FigResponseCookies; use Flarum\Http\CookieFactory; +use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Contracts\Session\Session; -use Illuminate\Session\SessionManager; +use Illuminate\Session\Store; use Interop\Http\ServerMiddleware\DelegateInterface; use Interop\Http\ServerMiddleware\MiddlewareInterface; use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ServerRequestInterface as Request; +use SessionHandlerInterface; class StartSession implements MiddlewareInterface { /** - * @var SessionManager + * @var SessionHandlerInterface */ - protected $manager; + protected $handler; /** * @var CookieFactory @@ -33,25 +35,29 @@ class StartSession implements MiddlewareInterface protected $cookie; /** - * @param SessionManager $manager + * @var array + */ + protected $config; + + /** * @param CookieFactory $cookie */ - public function __construct(SessionManager $manager, CookieFactory $cookie) + public function __construct(SessionHandlerInterface $handler, CookieFactory $cookie, ConfigRepository $config) { - $this->manager = $manager; + $this->handler = $handler; $this->cookie = $cookie; + $this->config = $config->get('session'); } public function process(Request $request, DelegateInterface $delegate) { - $request = $request->withAttribute('session', - $session = $this->startSession($request) + $request = $request->withAttribute( + 'session', + $session = $this->makeSession($request) ); - $this->collectGarbage($session); - + $session->start(); $response = $delegate->process($request); - $session->save(); $response = $this->withCsrfTokenHeader($response, $session); @@ -59,37 +65,20 @@ class StartSession implements MiddlewareInterface return $this->withSessionCookie($response, $session); } - private function startSession(Request $request) + private function makeSession(Request $request) { - $session = $this->manager->driver(); + $cookieName = $this->cookie->getName($this->config['cookie']); - $id = array_get($request->getCookieParams(), $this->cookie->getName($session->getName())); - - $session->setId($id); - $session->start(); - - return $session; - } - - private function collectGarbage(Session $session) - { - $config = $this->manager->getSessionConfig(); - - if ($this->configHitsLottery($config)) { - $session->getHandler()->gc($this->getSessionLifetimeInSeconds()); - } - } - - private function configHitsLottery(array $config) - { - return random_int(1, $config['lottery'][1]) <= $config['lottery'][0]; + return new Store( + $cookieName, + $this->handler, + array_get($request->getCookieParams(), $cookieName) + ); } private function withCsrfTokenHeader(Response $response, Session $session) { - $response = $response->withHeader('X-CSRF-Token', $session->token()); - - return $response; + return $response->withHeader('X-CSRF-Token', $session->token()); } private function withSessionCookie(Response $response, Session $session) @@ -102,6 +91,6 @@ class StartSession implements MiddlewareInterface private function getSessionLifetimeInSeconds() { - return ($this->manager->getSessionConfig()['lifetime'] ?? null) * 60; + return $this->config['lifetime'] * 60; } } diff --git a/framework/core/src/User/UserServiceProvider.php b/framework/core/src/User/UserServiceProvider.php index 03f3cbf55..a52d128ad 100644 --- a/framework/core/src/User/UserServiceProvider.php +++ b/framework/core/src/User/UserServiceProvider.php @@ -15,7 +15,9 @@ use Flarum\Event\ConfigureUserPreferences; use Flarum\Event\GetPermission; use Flarum\Foundation\AbstractServiceProvider; use Illuminate\Contracts\Container\Container; +use Illuminate\Session\FileSessionHandler; use RuntimeException; +use SessionHandlerInterface; class UserServiceProvider extends AbstractServiceProvider { @@ -23,6 +25,26 @@ class UserServiceProvider extends AbstractServiceProvider * {@inheritdoc} */ public function register() + { + $this->registerSession(); + $this->registerGate(); + $this->registerAvatarsFilesystem(); + } + + protected function registerSession() + { + $this->app->singleton('session.handler', function ($app) { + return new FileSessionHandler( + $app['files'], + $app['config']['session.files'], + $app['config']['session.lifetime'] + ); + }); + + $this->app->alias('session.handler', SessionHandlerInterface::class); + } + + protected function registerGate() { $this->app->singleton('flarum.gate', function ($app) { return new Gate($app, function () { @@ -32,8 +54,6 @@ class UserServiceProvider extends AbstractServiceProvider $this->app->alias('flarum.gate', 'Illuminate\Contracts\Auth\Access\Gate'); $this->app->alias('flarum.gate', Gate::class); - - $this->registerAvatarsFilesystem(); } protected function registerAvatarsFilesystem()