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.
This commit is contained in:
Franz Liedke 2018-03-18 15:58:31 +01:00
parent 5672819549
commit bb49e24ffe
No known key found for this signature in database
GPG Key ID: 9A0231A879B055F4
5 changed files with 73 additions and 45 deletions

View File

@ -718,8 +718,6 @@ class Application extends Container implements ApplicationContract
'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class],
'hash' => [\Illuminate\Contracts\Hashing\Hasher::class], 'hash' => [\Illuminate\Contracts\Hashing\Hasher::class],
'mailer' => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::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], 'validator' => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class], 'view' => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
]; ];

View File

@ -240,12 +240,8 @@ class Site
] ]
], ],
'session' => [ 'session' => [
'driver' => 'file',
'lifetime' => 120, 'lifetime' => 120,
'expire_on_close' => false,
'encrypt' => false,
'files' => $app->storagePath().'/sessions', 'files' => $app->storagePath().'/sessions',
'lottery' => [2, 100],
'cookie' => 'session' 'cookie' => 'session'
] ]
]); ]);

View File

@ -15,12 +15,30 @@ use Flarum\Http\AccessToken;
use Flarum\User\AuthToken; use Flarum\User\AuthToken;
use Flarum\User\EmailToken; use Flarum\User\EmailToken;
use Flarum\User\PasswordToken; use Flarum\User\PasswordToken;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Interop\Http\ServerMiddleware\DelegateInterface; use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface; use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use SessionHandlerInterface;
class CollectGarbage implements MiddlewareInterface 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) public function process(Request $request, DelegateInterface $delegate)
{ {
$this->collectGarbageSometimes(); $this->collectGarbageSometimes();
@ -43,10 +61,17 @@ class CollectGarbage implements MiddlewareInterface
EmailToken::where('created_at', '<=', $earliestToKeep)->delete(); EmailToken::where('created_at', '<=', $earliestToKeep)->delete();
PasswordToken::where('created_at', '<=', $earliestToKeep)->delete(); PasswordToken::where('created_at', '<=', $earliestToKeep)->delete();
AuthToken::where('created_at', '<=', $earliestToKeep)->delete(); AuthToken::where('created_at', '<=', $earliestToKeep)->delete();
$this->sessionHandler->gc($this->getSessionLifetimeInSeconds());
} }
private function hit() private function hit()
{ {
return mt_rand(1, 100) <= 2; return mt_rand(1, 100) <= 2;
} }
private function getSessionLifetimeInSeconds()
{
return $this->sessionConfig['lifetime'] * 60;
}
} }

View File

@ -13,19 +13,21 @@ namespace Flarum\Http\Middleware;
use Dflydev\FigCookies\FigResponseCookies; use Dflydev\FigCookies\FigResponseCookies;
use Flarum\Http\CookieFactory; use Flarum\Http\CookieFactory;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Illuminate\Contracts\Session\Session; use Illuminate\Contracts\Session\Session;
use Illuminate\Session\SessionManager; use Illuminate\Session\Store;
use Interop\Http\ServerMiddleware\DelegateInterface; use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface; use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use SessionHandlerInterface;
class StartSession implements MiddlewareInterface class StartSession implements MiddlewareInterface
{ {
/** /**
* @var SessionManager * @var SessionHandlerInterface
*/ */
protected $manager; protected $handler;
/** /**
* @var CookieFactory * @var CookieFactory
@ -33,25 +35,29 @@ class StartSession implements MiddlewareInterface
protected $cookie; protected $cookie;
/** /**
* @param SessionManager $manager * @var array
*/
protected $config;
/**
* @param CookieFactory $cookie * @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->cookie = $cookie;
$this->config = $config->get('session');
} }
public function process(Request $request, DelegateInterface $delegate) public function process(Request $request, DelegateInterface $delegate)
{ {
$request = $request->withAttribute('session', $request = $request->withAttribute(
$session = $this->startSession($request) 'session',
$session = $this->makeSession($request)
); );
$this->collectGarbage($session); $session->start();
$response = $delegate->process($request); $response = $delegate->process($request);
$session->save(); $session->save();
$response = $this->withCsrfTokenHeader($response, $session); $response = $this->withCsrfTokenHeader($response, $session);
@ -59,37 +65,20 @@ class StartSession implements MiddlewareInterface
return $this->withSessionCookie($response, $session); 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())); return new Store(
$cookieName,
$session->setId($id); $this->handler,
$session->start(); array_get($request->getCookieParams(), $cookieName)
);
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];
} }
private function withCsrfTokenHeader(Response $response, Session $session) private function withCsrfTokenHeader(Response $response, Session $session)
{ {
$response = $response->withHeader('X-CSRF-Token', $session->token()); return $response->withHeader('X-CSRF-Token', $session->token());
return $response;
} }
private function withSessionCookie(Response $response, Session $session) private function withSessionCookie(Response $response, Session $session)
@ -102,6 +91,6 @@ class StartSession implements MiddlewareInterface
private function getSessionLifetimeInSeconds() private function getSessionLifetimeInSeconds()
{ {
return ($this->manager->getSessionConfig()['lifetime'] ?? null) * 60; return $this->config['lifetime'] * 60;
} }
} }

View File

@ -15,7 +15,9 @@ use Flarum\Event\ConfigureUserPreferences;
use Flarum\Event\GetPermission; use Flarum\Event\GetPermission;
use Flarum\Foundation\AbstractServiceProvider; use Flarum\Foundation\AbstractServiceProvider;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Session\FileSessionHandler;
use RuntimeException; use RuntimeException;
use SessionHandlerInterface;
class UserServiceProvider extends AbstractServiceProvider class UserServiceProvider extends AbstractServiceProvider
{ {
@ -23,6 +25,26 @@ class UserServiceProvider extends AbstractServiceProvider
* {@inheritdoc} * {@inheritdoc}
*/ */
public function register() 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) { $this->app->singleton('flarum.gate', function ($app) {
return new Gate($app, function () { 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', 'Illuminate\Contracts\Auth\Access\Gate');
$this->app->alias('flarum.gate', Gate::class); $this->app->alias('flarum.gate', Gate::class);
$this->registerAvatarsFilesystem();
} }
protected function registerAvatarsFilesystem() protected function registerAvatarsFilesystem()