mirror of
https://github.com/flarum/framework.git
synced 2025-03-22 21:15:16 +08:00
feat: customizable session driver (#3610)
This commit is contained in:
parent
84c31165e5
commit
f6761843b2
42
framework/core/src/Extend/Session.php
Normal file
42
framework/core/src/Extend/Session.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Extend;
|
||||
|
||||
use Flarum\Extension\Extension;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class Session implements ExtenderInterface
|
||||
{
|
||||
private $drivers = [];
|
||||
|
||||
/**
|
||||
* Register a new session driver.
|
||||
*
|
||||
* A driver can currently be selected by setting `session.driver` in `config.php`.
|
||||
*
|
||||
* @param string $name: The name of the driver.
|
||||
* @param string $driverClass: The ::class attribute of the driver.
|
||||
* Driver must implement `\Flarum\User\SessionDriverInterface`.
|
||||
* @return self
|
||||
*/
|
||||
public function driver(string $name, string $driverClass): self
|
||||
{
|
||||
$this->drivers[$name] = $driverClass;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function extend(Container $container, Extension $extension = null)
|
||||
{
|
||||
$container->extend('flarum.session.drivers', function ($drivers) {
|
||||
return array_merge($drivers, $this->drivers);
|
||||
});
|
||||
}
|
||||
}
|
@ -14,10 +14,14 @@ use Flarum\Extension\ExtensionManager;
|
||||
use Flarum\Foundation\Application;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\User\SessionManager;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
use SessionHandlerInterface;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableStyle;
|
||||
|
||||
@ -48,18 +52,32 @@ class InfoCommand extends AbstractCommand
|
||||
*/
|
||||
private $queue;
|
||||
|
||||
/**
|
||||
* @var SessionManager
|
||||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var SessionHandlerInterface
|
||||
*/
|
||||
private $sessionHandler;
|
||||
|
||||
public function __construct(
|
||||
ExtensionManager $extensions,
|
||||
Config $config,
|
||||
SettingsRepositoryInterface $settings,
|
||||
ConnectionInterface $db,
|
||||
Queue $queue
|
||||
Queue $queue,
|
||||
SessionManager $session,
|
||||
SessionHandlerInterface $sessionHandler
|
||||
) {
|
||||
$this->extensions = $extensions;
|
||||
$this->config = $config;
|
||||
$this->settings = $settings;
|
||||
$this->db = $db;
|
||||
$this->queue = $queue;
|
||||
$this->session = $session;
|
||||
$this->sessionHandler = $sessionHandler;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
@ -93,6 +111,7 @@ class InfoCommand extends AbstractCommand
|
||||
$this->output->writeln('<info>Base URL:</info> '.$this->config->url());
|
||||
$this->output->writeln('<info>Installation path:</info> '.getcwd());
|
||||
$this->output->writeln('<info>Queue driver:</info> '.$this->identifyQueueDriver());
|
||||
$this->output->writeln('<info>Session driver:</info> '.$this->identifySessionDriver());
|
||||
$this->output->writeln('<info>Mail driver:</info> '.$this->settings->get('mail_driver', 'unknown'));
|
||||
$this->output->writeln('<info>Debug mode:</info> '.($this->config->inDebugMode() ? '<error>ON</error>' : 'off'));
|
||||
|
||||
@ -169,4 +188,53 @@ class InfoCommand extends AbstractCommand
|
||||
{
|
||||
return $this->db->getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on the session driver in use based on three scenarios:
|
||||
* 1. If the configured session driver is valid and in use, it will be returned.
|
||||
* 2. If the configured session driver is invalid, fallback to the default one and mention it.
|
||||
* 3. If the actual used driver (i.e `session.handler`) is different from the current one (configured or default), mention it.
|
||||
*/
|
||||
private function identifySessionDriver(): string
|
||||
{
|
||||
/*
|
||||
* Get the configured driver and fallback to the default one.
|
||||
*/
|
||||
$defaultDriver = $this->session->getDefaultDriver();
|
||||
$configuredDriver = Arr::get($this->config, 'session.driver', $defaultDriver);
|
||||
$driver = $configuredDriver;
|
||||
|
||||
try {
|
||||
// Try to get the configured driver instance.
|
||||
// Driver instances are created on demand.
|
||||
$this->session->driver($configuredDriver);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
// An exception is thrown if the configured driver is not a valid driver.
|
||||
// So we fallback to the default driver.
|
||||
$driver = $defaultDriver;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get actual driver name from its class name.
|
||||
* And compare that to the current configured driver.
|
||||
*/
|
||||
// Get class name
|
||||
$handlerName = get_class($this->sessionHandler);
|
||||
// Drop the namespace
|
||||
$handlerName = Str::afterLast($handlerName, '\\');
|
||||
// Lowercase the class name
|
||||
$handlerName = strtolower($handlerName);
|
||||
// Drop everything like sessionhandler FileSessionHandler, DatabaseSessionHandler ..etc
|
||||
$handlerName = str_replace('sessionhandler', '', $handlerName);
|
||||
|
||||
if ($driver !== $handlerName) {
|
||||
return "$handlerName <comment>(Code override. Configured to <options=bold,underscore>$configuredDriver</>)</comment>";
|
||||
}
|
||||
|
||||
if ($driver !== $configuredDriver) {
|
||||
return "$driver <comment>(Fallback default driver. Configured to invalid driver <options=bold,underscore>$configuredDriver</>)</comment>";
|
||||
}
|
||||
|
||||
return $driver;
|
||||
}
|
||||
}
|
||||
|
27
framework/core/src/User/SessionDriverInterface.php
Normal file
27
framework/core/src/User/SessionDriverInterface.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use SessionHandlerInterface;
|
||||
|
||||
interface SessionDriverInterface
|
||||
{
|
||||
/**
|
||||
* Build a session handler to handle sessions.
|
||||
* Settings and configuration can either be pulled from the Flarum settings repository
|
||||
* or the config.php file.
|
||||
*
|
||||
* @param SettingsRepositoryInterface $settings: An instance of the Flarum settings repository.
|
||||
* @param Config $config: An instance of the wrapper class around `config.php`.
|
||||
*/
|
||||
public function build(SettingsRepositoryInterface $settings, Config $config): SessionHandlerInterface;
|
||||
}
|
46
framework/core/src/User/SessionManager.php
Normal file
46
framework/core/src/User/SessionManager.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\User;
|
||||
|
||||
use Flarum\Foundation\Config;
|
||||
use Illuminate\Session\SessionManager as IlluminateSessionManager;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SessionHandlerInterface;
|
||||
|
||||
class SessionManager extends IlluminateSessionManager
|
||||
{
|
||||
/**
|
||||
* Returns the configured session handler.
|
||||
* Picks up the driver from `config.php` using the `session.driver` item.
|
||||
* Falls back to the default driver if the configured one is not available,
|
||||
* and logs a critical error in that case.
|
||||
*/
|
||||
public function handler(): SessionHandlerInterface
|
||||
{
|
||||
$config = $this->container->make(Config::class);
|
||||
$driverName = Arr::get($config, 'session.driver');
|
||||
|
||||
try {
|
||||
$driverInstance = parent::driver($driverName);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$defaultDriverName = $this->getDefaultDriver();
|
||||
$driverInstance = parent::driver($defaultDriverName);
|
||||
|
||||
// But we will log a critical error to the webmaster.
|
||||
$this->container->make(LoggerInterface::class)->critical(
|
||||
"The configured session driver [$driverName] is not available. Falling back to default [$defaultDriverName]. Please check your configuration."
|
||||
);
|
||||
}
|
||||
|
||||
return $driverInstance->getHandler();
|
||||
}
|
||||
}
|
@ -10,7 +10,9 @@
|
||||
namespace Flarum\User;
|
||||
|
||||
use Flarum\Foundation\AbstractServiceProvider;
|
||||
use Illuminate\Session\FileSessionHandler;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
use SessionHandlerInterface;
|
||||
|
||||
class SessionServiceProvider extends AbstractServiceProvider
|
||||
@ -20,12 +22,42 @@ class SessionServiceProvider extends AbstractServiceProvider
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->container->singleton('session.handler', function ($container) {
|
||||
return new FileSessionHandler(
|
||||
$container['files'],
|
||||
$container['config']['session.files'],
|
||||
$container['config']['session.lifetime']
|
||||
);
|
||||
$this->container->singleton('flarum.session.drivers', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
$this->container->singleton('session', function (Container $container) {
|
||||
$manager = new SessionManager($container);
|
||||
$drivers = $container->make('flarum.session.drivers');
|
||||
$settings = $container->make(SettingsRepositoryInterface::class);
|
||||
$config = $container->make(Config::class);
|
||||
|
||||
/**
|
||||
* Default to the file driver already defined by Laravel.
|
||||
*
|
||||
* @see \Illuminate\Session\SessionManager::createFileDriver()
|
||||
*/
|
||||
$manager->setDefaultDriver('file');
|
||||
|
||||
foreach ($drivers as $driver => $className) {
|
||||
/** @var SessionDriverInterface $driverInstance */
|
||||
$driverInstance = $container->make($className);
|
||||
|
||||
$manager->extend($driver, function () use ($settings, $config, $driverInstance) {
|
||||
return $driverInstance->build($settings, $config);
|
||||
});
|
||||
}
|
||||
|
||||
return $manager;
|
||||
});
|
||||
|
||||
$this->container->alias('session', SessionManager::class);
|
||||
|
||||
$this->container->singleton('session.handler', function (Container $container): SessionHandlerInterface {
|
||||
/** @var SessionManager $manager */
|
||||
$manager = $container->make('session');
|
||||
|
||||
return $manager->handler();
|
||||
});
|
||||
|
||||
$this->container->alias('session.handler', SessionHandlerInterface::class);
|
||||
|
116
framework/core/tests/integration/extenders/SessionTest.php
Normal file
116
framework/core/tests/integration/extenders/SessionTest.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Tests\integration\extenders;
|
||||
|
||||
use Flarum\Extend;
|
||||
use Flarum\Foundation\Config;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||
use Flarum\Testing\integration\TestCase;
|
||||
use Flarum\User\SessionDriverInterface;
|
||||
use Illuminate\Session\FileSessionHandler;
|
||||
use Illuminate\Session\NullSessionHandler;
|
||||
use InvalidArgumentException;
|
||||
use SessionHandlerInterface;
|
||||
|
||||
class SessionTest extends TestCase
|
||||
{
|
||||
use RetrievesAuthorizedUsers;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function default_driver_exists_by_default()
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
$this->app()->getContainer()->make('session.handler');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_driver_doesnt_exist_by_default()
|
||||
{
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->app()->getContainer()->make('session')->driver('flarum-acme');
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_driver_exists_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Session())->driver('flarum-acme', AcmeSessionDriver::class));
|
||||
|
||||
$driver = $this->app()->getContainer()->make('session')->driver('flarum-acme');
|
||||
|
||||
$this->assertEquals(NullSessionHandler::class, get_class($driver->getHandler()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_driver_overrides_laravel_defined_drivers_if_added()
|
||||
{
|
||||
$this->extend((new Extend\Session())->driver('redis', AcmeSessionDriver::class));
|
||||
|
||||
$driver = $this->app()->getContainer()->make('session')->driver('redis');
|
||||
|
||||
$this->assertEquals(NullSessionHandler::class, get_class($driver->getHandler()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function uses_default_driver_if_driver_from_config_file_not_configured()
|
||||
{
|
||||
$this->config('session.driver', null);
|
||||
|
||||
$handler = $this->app()->getContainer()->make('session.handler');
|
||||
|
||||
$this->assertEquals(FileSessionHandler::class, get_class($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function uses_default_driver_if_configured_driver_from_config_file_unavailable()
|
||||
{
|
||||
$this->config('session.driver', 'nevergonnagiveyouup');
|
||||
|
||||
$handler = $this->app()->getContainer()->make('session.handler');
|
||||
|
||||
$this->assertEquals(FileSessionHandler::class, get_class($handler));
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function uses_custom_driver_from_config_file_if_configured_and_available()
|
||||
{
|
||||
$this->extend(
|
||||
(new Extend\Session)->driver('flarum-acme', AcmeSessionDriver::class)
|
||||
);
|
||||
|
||||
$this->config('session.driver', 'flarum-acme');
|
||||
|
||||
$handler = $this->app()->getContainer()->make('session.handler');
|
||||
|
||||
$this->assertEquals(NullSessionHandler::class, get_class($handler));
|
||||
}
|
||||
}
|
||||
|
||||
class AcmeSessionDriver implements SessionDriverInterface
|
||||
{
|
||||
public function build(SettingsRepositoryInterface $settings, Config $config): SessionHandlerInterface
|
||||
{
|
||||
return new NullSessionHandler();
|
||||
}
|
||||
}
|
@ -148,7 +148,7 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
protected function config(string $key, $value)
|
||||
{
|
||||
$this->config[$key] = $value;
|
||||
Arr::set($this->config, $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user