diff --git a/app/Config/App.php b/app/Config/App.php index ebe7cbe28fd2..aac2c2449901 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -149,6 +149,8 @@ class App extends BaseConfig * - `CodeIgniter\Session\Handlers\RedisHandler` * * @var string + * + * @deprecated use Config\Session::$driver instead. */ public $sessionDriver = FileHandler::class; @@ -158,6 +160,8 @@ class App extends BaseConfig * -------------------------------------------------------------------------- * * The session cookie name, must contain only [0-9a-z_-] characters + * + * @deprecated use Config\Session::$cookieName instead. */ public string $sessionCookieName = 'ci_session'; @@ -168,6 +172,8 @@ class App extends BaseConfig * * The number of SECONDS you want the session to last. * Setting to 0 (zero) means expire when the browser is closed. + * + * @deprecated use Config\Session::$expiration instead. */ public int $sessionExpiration = 7200; @@ -185,6 +191,8 @@ class App extends BaseConfig * Please read up the manual for the format with other session drivers. * * IMPORTANT: You are REQUIRED to set a valid save path! + * + * @deprecated use Config\Session::$savePath instead. */ public string $sessionSavePath = WRITEPATH . 'session'; @@ -197,6 +205,8 @@ class App extends BaseConfig * * WARNING: If you're using the database driver, don't forget to update * your session table's PRIMARY KEY when changing this setting. + * + * @deprecated use Config\Session::$matchIP instead. */ public bool $sessionMatchIP = false; @@ -206,6 +216,8 @@ class App extends BaseConfig * -------------------------------------------------------------------------- * * How many seconds between CI regenerating the session ID. + * + * @deprecated use Config\Session::$timeToUpdate instead. */ public int $sessionTimeToUpdate = 300; @@ -217,9 +229,22 @@ class App extends BaseConfig * Whether to destroy session data associated with the old session ID * when auto-regenerating the session ID. When set to FALSE, the data * will be later deleted by the garbage collector. + * + * @deprecated use Config\Session::$regenerateDestroy instead. */ public bool $sessionRegenerateDestroy = false; + /** + * -------------------------------------------------------------------------- + * Session Database Group + * -------------------------------------------------------------------------- + * + * DB Group for the database session. + * + * @deprecated use Config\Session::$DBGroup instead. + */ + public ?string $sessionDBGroup = null; + /** * -------------------------------------------------------------------------- * Cookie Prefix diff --git a/app/Config/Session.php b/app/Config/Session.php new file mode 100644 index 000000000000..4538d1440950 --- /dev/null +++ b/app/Config/Session.php @@ -0,0 +1,102 @@ + + */ + public string $driver = FileHandler::class; + + /** + * -------------------------------------------------------------------------- + * Session Cookie Name + * -------------------------------------------------------------------------- + * + * The session cookie name, must contain only [0-9a-z_-] characters + */ + public string $cookieName = 'ci_session'; + + /** + * -------------------------------------------------------------------------- + * Session Expiration + * -------------------------------------------------------------------------- + * + * The number of SECONDS you want the session to last. + * Setting to 0 (zero) means expire when the browser is closed. + */ + public int $expiration = 7200; + + /** + * -------------------------------------------------------------------------- + * Session Save Path + * -------------------------------------------------------------------------- + * + * The location to save sessions to and is driver dependent. + * + * For the 'files' driver, it's a path to a writable directory. + * WARNING: Only absolute paths are supported! + * + * For the 'database' driver, it's a table name. + * Please read up the manual for the format with other session drivers. + * + * IMPORTANT: You are REQUIRED to set a valid save path! + */ + public string $savePath = WRITEPATH . 'session'; + + /** + * -------------------------------------------------------------------------- + * Session Match IP + * -------------------------------------------------------------------------- + * + * Whether to match the user's IP address when reading the session data. + * + * WARNING: If you're using the database driver, don't forget to update + * your session table's PRIMARY KEY when changing this setting. + */ + public bool $matchIP = false; + + /** + * -------------------------------------------------------------------------- + * Session Time to Update + * -------------------------------------------------------------------------- + * + * How many seconds between CI regenerating the session ID. + */ + public int $timeToUpdate = 300; + + /** + * -------------------------------------------------------------------------- + * Session Regenerate Destroy + * -------------------------------------------------------------------------- + * + * Whether to destroy session data associated with the old session ID + * when auto-regenerating the session ID. When set to FALSE, the data + * will be later deleted by the garbage collector. + */ + public bool $regenerateDestroy = false; + + /** + * -------------------------------------------------------------------------- + * Session Database Group + * -------------------------------------------------------------------------- + * + * DB Group for the database session. + */ + public ?string $DBGroup = null; +} diff --git a/env b/env index fc796609639f..cb99e19c3a80 100644 --- a/env +++ b/env @@ -24,15 +24,6 @@ # If you have trouble with `.`, you could also use `_`. # app_baseURL = '' # app.forceGlobalSecureRequests = false - -# app.sessionDriver = 'CodeIgniter\Session\Handlers\FileHandler' -# app.sessionCookieName = 'ci_session' -# app.sessionExpiration = 7200 -# app.sessionSavePath = null -# app.sessionMatchIP = false -# app.sessionTimeToUpdate = 300 -# app.sessionRegenerateDestroy = false - # app.CSPEnabled = false #-------------------------------------------------------------------- @@ -127,6 +118,18 @@ # security.redirect = false # security.samesite = 'Lax' +#-------------------------------------------------------------------- +# SESSION +#-------------------------------------------------------------------- + +# session.driver = 'CodeIgniter\Session\Handlers\FileHandler' +# session.cookieName = 'ci_session' +# session.expiration = 7200 +# session.savePath = null +# session.matchIP = false +# session.timeToUpdate = 300 +# session.regenerateDestroy = false + #-------------------------------------------------------------------- # LOGGER #-------------------------------------------------------------------- diff --git a/system/Commands/Generators/MigrationGenerator.php b/system/Commands/Generators/MigrationGenerator.php index 24ab3ea72330..5cc3b6dfd56a 100644 --- a/system/Commands/Generators/MigrationGenerator.php +++ b/system/Commands/Generators/MigrationGenerator.php @@ -14,6 +14,8 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; use CodeIgniter\CLI\GeneratorTrait; +use Config\App as AppConfig; +use Config\Session as SessionConfig; /** * Generates a skeleton migration file. @@ -105,7 +107,14 @@ protected function prepare(string $class): string $data['table'] = is_string($table) ? $table : 'ci_sessions'; $data['DBGroup'] = is_string($DBGroup) ? $DBGroup : 'default'; $data['DBDriver'] = config('Database')->{$data['DBGroup']}['DBDriver']; - $data['matchIP'] = config('App')->sessionMatchIP; + + /** @var AppConfig $config */ + $config = config('App'); + /** @var SessionConfig|null $session */ + $session = config('Session'); + + $data['matchIP'] = ($session instanceof SessionConfig) + ? $session->matchIP : $config->sessionMatchIP; } return $this->parseTemplate($class, [], [], $data); diff --git a/system/Session/Handlers/BaseHandler.php b/system/Session/Handlers/BaseHandler.php index c441aa086153..f7d6ff1a90c7 100644 --- a/system/Session/Handlers/BaseHandler.php +++ b/system/Session/Handlers/BaseHandler.php @@ -13,6 +13,7 @@ use Config\App as AppConfig; use Config\Cookie as CookieConfig; +use Config\Session as SessionConfig; use Psr\Log\LoggerAwareTrait; use SessionHandlerInterface; @@ -106,6 +107,21 @@ abstract class BaseHandler implements SessionHandlerInterface public function __construct(AppConfig $config, string $ipAddress) { + /** @var SessionConfig|null $session */ + $session = config('Session'); + + // Store Session configurations + if ($session instanceof SessionConfig) { + $this->cookieName = $session->cookieName; + $this->matchIP = $session->matchIP; + $this->savePath = $session->savePath; + } else { + // `Config/Session.php` is absence + $this->cookieName = $config->sessionCookieName; + $this->matchIP = $config->sessionMatchIP; + $this->savePath = $config->sessionSavePath; + } + /** @var CookieConfig|null $cookie */ $cookie = config('Cookie'); @@ -123,10 +139,7 @@ public function __construct(AppConfig $config, string $ipAddress) $this->cookieSecure = $config->cookieSecure; } - $this->cookieName = $config->sessionCookieName; - $this->matchIP = $config->sessionMatchIP; - $this->savePath = $config->sessionSavePath; - $this->ipAddress = $ipAddress; + $this->ipAddress = $ipAddress; } /** diff --git a/system/Session/Handlers/DatabaseHandler.php b/system/Session/Handlers/DatabaseHandler.php index 8cbf43ca280a..0e994764f0f4 100644 --- a/system/Session/Handlers/DatabaseHandler.php +++ b/system/Session/Handlers/DatabaseHandler.php @@ -16,6 +16,7 @@ use CodeIgniter\Session\Exceptions\SessionException; use Config\App as AppConfig; use Config\Database; +use Config\Session as SessionConfig; use ReturnTypeWillChange; /** @@ -66,16 +67,24 @@ class DatabaseHandler extends BaseHandler public function __construct(AppConfig $config, string $ipAddress) { parent::__construct($config, $ipAddress); - $this->table = $config->sessionSavePath; + /** @var SessionConfig|null $session */ + $session = config('Session'); + + // Store Session configurations + if ($session instanceof SessionConfig) { + $this->DBGroup = $session->DBGroup ?? config(Database::class)->defaultGroup; + } else { + // `Config/Session.php` is absence + $this->DBGroup = $config->sessionDBGroup ?? config(Database::class)->defaultGroup; + } + + $this->table = $this->savePath; if (empty($this->table)) { throw SessionException::forMissingDatabaseTable(); } - $this->DBGroup = $config->sessionDBGroup ?? config(Database::class)->defaultGroup; - - $this->db = Database::connect($this->DBGroup); - + $this->db = Database::connect($this->DBGroup); $this->platform = $this->db->getPlatform(); } diff --git a/system/Session/Handlers/FileHandler.php b/system/Session/Handlers/FileHandler.php index c05cb84227b2..cc8a6694f692 100644 --- a/system/Session/Handlers/FileHandler.php +++ b/system/Session/Handlers/FileHandler.php @@ -67,9 +67,9 @@ public function __construct(AppConfig $config, string $ipAddress) { parent::__construct($config, $ipAddress); - if (! empty($config->sessionSavePath)) { - $this->savePath = rtrim($config->sessionSavePath, '/\\'); - ini_set('session.save_path', $config->sessionSavePath); + if (! empty($this->savePath)) { + $this->savePath = rtrim($this->savePath, '/\\'); + ini_set('session.save_path', $this->savePath); } else { $sessionPath = rtrim(ini_get('session.save_path'), '/\\'); @@ -80,8 +80,6 @@ public function __construct(AppConfig $config, string $ipAddress) $this->savePath = $sessionPath; } - $this->matchIP = $config->sessionMatchIP; - $this->configureSessionIDRegex(); } diff --git a/system/Session/Handlers/MemcachedHandler.php b/system/Session/Handlers/MemcachedHandler.php index 0dbfeb4b9e1d..ad475c449960 100644 --- a/system/Session/Handlers/MemcachedHandler.php +++ b/system/Session/Handlers/MemcachedHandler.php @@ -14,6 +14,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Session\Exceptions\SessionException; use Config\App as AppConfig; +use Config\Session as SessionConfig; use Memcached; use ReturnTypeWillChange; @@ -57,6 +58,12 @@ public function __construct(AppConfig $config, string $ipAddress) { parent::__construct($config, $ipAddress); + /** @var SessionConfig|null $session */ + $session = config('Session'); + + $this->sessionExpiration = ($session instanceof SessionConfig) + ? $session->expiration : $config->sessionExpiration; + if (empty($this->savePath)) { throw SessionException::forEmptySavepath(); } @@ -68,8 +75,6 @@ public function __construct(AppConfig $config, string $ipAddress) if (! empty($this->keyPrefix)) { ini_set('memcached.sess_prefix', $this->keyPrefix); } - - $this->sessionExpiration = $config->sessionExpiration; } /** diff --git a/system/Session/Handlers/RedisHandler.php b/system/Session/Handlers/RedisHandler.php index 06cacb6ec661..54f91c636653 100644 --- a/system/Session/Handlers/RedisHandler.php +++ b/system/Session/Handlers/RedisHandler.php @@ -14,6 +14,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Session\Exceptions\SessionException; use Config\App as AppConfig; +use Config\Session as SessionConfig; use Redis; use RedisException; use ReturnTypeWillChange; @@ -69,15 +70,26 @@ public function __construct(AppConfig $config, string $ipAddress) { parent::__construct($config, $ipAddress); + /** @var SessionConfig|null $session */ + $session = config('Session'); + + // Store Session configurations + if ($session instanceof SessionConfig) { + $this->sessionExpiration = empty($session->expiration) + ? (int) ini_get('session.gc_maxlifetime') + : (int) $session->expiration; + } else { + // `Config/Session.php` is absence + $this->sessionExpiration = empty($config->sessionExpiration) + ? (int) ini_get('session.gc_maxlifetime') + : (int) $config->sessionExpiration; + } + $this->setSavePath(); if ($this->matchIP === true) { $this->keyPrefix .= $this->ipAddress . ':'; } - - $this->sessionExpiration = empty($config->sessionExpiration) - ? (int) ini_get('session.gc_maxlifetime') - : (int) $config->sessionExpiration; } protected function setSavePath(): void diff --git a/system/Session/Session.php b/system/Session/Session.php index 90aa2bdd6668..497111b60030 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -16,6 +16,7 @@ use Config\App; use Config\Cookie as CookieConfig; use Config\Services; +use Config\Session as SessionConfig; use Psr\Log\LoggerAwareTrait; use SessionHandlerInterface; @@ -115,7 +116,7 @@ class Session implements SessionInterface * * @var string * - * @deprecated + * @deprecated No longer used. */ protected $cookieDomain = ''; @@ -125,7 +126,7 @@ class Session implements SessionInterface * * @var string * - * @deprecated + * @deprecated No longer used. */ protected $cookiePath = '/'; @@ -134,7 +135,7 @@ class Session implements SessionInterface * * @var bool * - * @deprecated + * @deprecated No longer used. */ protected $cookieSecure = false; @@ -144,7 +145,7 @@ class Session implements SessionInterface * * @var string * - * @deprecated + * @deprecated No longer used. */ protected $cookieSameSite = Cookie::SAMESITE_LAX; @@ -164,13 +165,28 @@ public function __construct(SessionHandlerInterface $driver, App $config) { $this->driver = $driver; - $this->sessionDriverName = $config->sessionDriver; - $this->sessionCookieName = $config->sessionCookieName ?? $this->sessionCookieName; - $this->sessionExpiration = $config->sessionExpiration ?? $this->sessionExpiration; - $this->sessionSavePath = $config->sessionSavePath; - $this->sessionMatchIP = $config->sessionMatchIP ?? $this->sessionMatchIP; - $this->sessionTimeToUpdate = $config->sessionTimeToUpdate ?? $this->sessionTimeToUpdate; - $this->sessionRegenerateDestroy = $config->sessionRegenerateDestroy ?? $this->sessionRegenerateDestroy; + /** @var SessionConfig|null $session */ + $session = config('Session'); + + // Store Session configurations + if ($session instanceof SessionConfig) { + $this->sessionDriverName = $session->driver; + $this->sessionCookieName = $session->cookieName ?? $this->sessionCookieName; + $this->sessionExpiration = $session->expiration ?? $this->sessionExpiration; + $this->sessionSavePath = $session->savePath; + $this->sessionMatchIP = $session->matchIP ?? $this->sessionMatchIP; + $this->sessionTimeToUpdate = $session->timeToUpdate ?? $this->sessionTimeToUpdate; + $this->sessionRegenerateDestroy = $session->regenerateDestroy ?? $this->sessionRegenerateDestroy; + } else { + // `Config/Session.php` is absence + $this->sessionDriverName = $config->sessionDriver; + $this->sessionCookieName = $config->sessionCookieName ?? $this->sessionCookieName; + $this->sessionExpiration = $config->sessionExpiration ?? $this->sessionExpiration; + $this->sessionSavePath = $config->sessionSavePath; + $this->sessionMatchIP = $config->sessionMatchIP ?? $this->sessionMatchIP; + $this->sessionTimeToUpdate = $config->sessionTimeToUpdate ?? $this->sessionTimeToUpdate; + $this->sessionRegenerateDestroy = $config->sessionRegenerateDestroy ?? $this->sessionRegenerateDestroy; + } // DEPRECATED COOKIE MANAGEMENT $this->cookiePath = $config->cookiePath ?? $this->cookiePath; diff --git a/tests/system/Session/Handlers/Database/AbstractHandlerTestCase.php b/tests/system/Session/Handlers/Database/AbstractHandlerTestCase.php index c6d20f4267a9..a93f0aa896c3 100644 --- a/tests/system/Session/Handlers/Database/AbstractHandlerTestCase.php +++ b/tests/system/Session/Handlers/Database/AbstractHandlerTestCase.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Session\Handlers\Database; +use CodeIgniter\Session\Handlers\DatabaseHandler; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use CodeIgniter\Test\ReflectionHelper; @@ -25,8 +26,12 @@ abstract class AbstractHandlerTestCase extends CIUnitTestCase use DatabaseTestTrait; use ReflectionHelper; - protected $refresh = true; - protected $seed = CITestSeeder::class; + protected $refresh = true; + protected $seed = CITestSeeder::class; + protected string $sessionDriver = DatabaseHandler::class; + protected string $sessionSavePath = 'ci_sessions'; + protected string $sessionName = 'ci_session'; + protected string $userIpAddress = '127.0.0.1'; protected function setUp(): void { diff --git a/tests/system/Session/Handlers/Database/MySQLiHandlerTest.php b/tests/system/Session/Handlers/Database/MySQLiHandlerTest.php index 4de5d81c10be..b18f1b047eef 100644 --- a/tests/system/Session/Handlers/Database/MySQLiHandlerTest.php +++ b/tests/system/Session/Handlers/Database/MySQLiHandlerTest.php @@ -11,9 +11,10 @@ namespace CodeIgniter\Session\Handlers\Database; -use CodeIgniter\Session\Handlers\DatabaseHandler; +use CodeIgniter\Config\Factories; use Config\App as AppConfig; use Config\Database as DatabaseConfig; +use Config\Session as SessionConfig; /** * @group DatabaseLive @@ -34,27 +35,22 @@ protected function setUp(): void protected function getInstance($options = []) { $defaults = [ - 'sessionDriver' => DatabaseHandler::class, - 'sessionCookieName' => 'ci_session', - 'sessionExpiration' => 7200, - 'sessionSavePath' => 'ci_sessions', - 'sessionMatchIP' => false, - 'sessionTimeToUpdate' => 300, - 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', + 'driver' => $this->sessionDriver, + 'cookieName' => $this->sessionName, + 'expiration' => 7200, + 'savePath' => $this->sessionSavePath, + 'matchIP' => false, + 'timeToUpdate' => 300, + 'regenerateDestroy' => false, ]; + $sessionConfig = new SessionConfig(); + $config = array_merge($defaults, $options); - $config = array_merge($defaults, $options); - $appConfig = new AppConfig(); - - foreach ($config as $key => $c) { - $appConfig->{$key} = $c; + foreach ($config as $key => $value) { + $sessionConfig->{$key} = $value; } + Factories::injectMock('config', 'Session', $sessionConfig); - return new MySQLiHandler($appConfig, '127.0.0.1'); + return new MySQLiHandler(new AppConfig(), $this->userIpAddress); } } diff --git a/tests/system/Session/Handlers/Database/PostgreHandlerTest.php b/tests/system/Session/Handlers/Database/PostgreHandlerTest.php index b5cde67afd3b..f9db847faab0 100644 --- a/tests/system/Session/Handlers/Database/PostgreHandlerTest.php +++ b/tests/system/Session/Handlers/Database/PostgreHandlerTest.php @@ -11,9 +11,10 @@ namespace CodeIgniter\Session\Handlers\Database; -use CodeIgniter\Session\Handlers\DatabaseHandler; +use CodeIgniter\Config\Factories; use Config\App as AppConfig; use Config\Database as DatabaseConfig; +use Config\Session as SessionConfig; /** * @group DatabaseLive @@ -34,27 +35,22 @@ protected function setUp(): void protected function getInstance($options = []) { $defaults = [ - 'sessionDriver' => DatabaseHandler::class, - 'sessionCookieName' => 'ci_session', - 'sessionExpiration' => 7200, - 'sessionSavePath' => 'ci_sessions', - 'sessionMatchIP' => false, - 'sessionTimeToUpdate' => 300, - 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', + 'driver' => $this->sessionDriver, + 'cookieName' => $this->sessionName, + 'expiration' => 7200, + 'savePath' => $this->sessionSavePath, + 'matchIP' => false, + 'timeToUpdate' => 300, + 'regenerateDestroy' => false, ]; + $sessionConfig = new SessionConfig(); + $config = array_merge($defaults, $options); - $config = array_merge($defaults, $options); - $appConfig = new AppConfig(); - - foreach ($config as $key => $c) { - $appConfig->{$key} = $c; + foreach ($config as $key => $value) { + $sessionConfig->{$key} = $value; } + Factories::injectMock('config', 'Session', $sessionConfig); - return new PostgreHandler($appConfig, '127.0.0.1'); + return new PostgreHandler(new AppConfig(), $this->userIpAddress); } } diff --git a/tests/system/Session/Handlers/Database/RedisHandlerTest.php b/tests/system/Session/Handlers/Database/RedisHandlerTest.php index 4659d82a5d58..4a4cdcc45f19 100644 --- a/tests/system/Session/Handlers/Database/RedisHandlerTest.php +++ b/tests/system/Session/Handlers/Database/RedisHandlerTest.php @@ -11,9 +11,11 @@ namespace CodeIgniter\Session\Handlers\Database; +use CodeIgniter\Config\Factories; use CodeIgniter\Session\Handlers\RedisHandler; use CodeIgniter\Test\CIUnitTestCase; use Config\App as AppConfig; +use Config\Session as SessionConfig; use Redis; /** @@ -25,41 +27,37 @@ */ final class RedisHandlerTest extends CIUnitTestCase { + private string $sessionDriver = RedisHandler::class; private string $sessionName = 'ci_session'; private string $sessionSavePath = 'tcp://127.0.0.1:6379'; private string $userIpAddress = '127.0.0.1'; - private function getInstance($options = []) + protected function getInstance($options = []) { $defaults = [ - 'sessionDriver' => RedisHandler::class, - 'sessionCookieName' => $this->sessionName, - 'sessionExpiration' => 7200, - 'sessionSavePath' => $this->sessionSavePath, - 'sessionMatchIP' => false, - 'sessionTimeToUpdate' => 300, - 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', + 'driver' => $this->sessionDriver, + 'cookieName' => $this->sessionName, + 'expiration' => 7200, + 'savePath' => $this->sessionSavePath, + 'matchIP' => false, + 'timeToUpdate' => 300, + 'regenerateDestroy' => false, ]; + $sessionConfig = new SessionConfig(); + $config = array_merge($defaults, $options); - $config = array_merge($defaults, $options); - $appConfig = new AppConfig(); - - foreach ($config as $key => $c) { - $appConfig->{$key} = $c; + foreach ($config as $key => $value) { + $sessionConfig->{$key} = $value; } + Factories::injectMock('config', 'Session', $sessionConfig); - return new RedisHandler($appConfig, $this->userIpAddress); + return new RedisHandler(new AppConfig(), $this->userIpAddress); } public function testSavePathTimeoutFloat() { $handler = $this->getInstance( - ['sessionSavePath' => 'tcp://127.0.0.1:6379?timeout=2.5'] + ['savePath' => 'tcp://127.0.0.1:6379?timeout=2.5'] ); $savePath = $this->getPrivateProperty($handler, 'savePath'); @@ -70,7 +68,7 @@ public function testSavePathTimeoutFloat() public function testSavePathTimeoutInt() { $handler = $this->getInstance( - ['sessionSavePath' => 'tcp://127.0.0.1:6379?timeout=10'] + ['savePath' => 'tcp://127.0.0.1:6379?timeout=10'] ); $savePath = $this->getPrivateProperty($handler, 'savePath'); diff --git a/tests/system/Session/SessionTest.php b/tests/system/Session/SessionTest.php index f4a4ad5283d8..8e0170d0fee1 100644 --- a/tests/system/Session/SessionTest.php +++ b/tests/system/Session/SessionTest.php @@ -20,6 +20,7 @@ use Config\App as AppConfig; use Config\Cookie as CookieConfig; use Config\Logger as LoggerConfig; +use Config\Session as SessionConfig; /** * @runTestsInSeparateProcesses @@ -42,27 +43,24 @@ protected function setUp(): void protected function getInstance($options = []) { + $appConfig = new AppConfig(); + $defaults = [ - 'sessionDriver' => FileHandler::class, - 'sessionCookieName' => 'ci_session', - 'sessionExpiration' => 7200, - 'sessionSavePath' => '', - 'sessionMatchIP' => false, - 'sessionTimeToUpdate' => 300, - 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', + 'driver' => FileHandler::class, + 'cookieName' => 'ci_session', + 'expiration' => 7200, + 'savePath' => '', + 'matchIP' => false, + 'timeToUpdate' => 300, + 'regenerateDestroy' => false, ]; + $sessionConfig = new SessionConfig(); + $config = array_merge($defaults, $options); - $config = array_merge($defaults, $options); - $appConfig = new AppConfig(); - - foreach ($config as $key => $c) { - $appConfig->{$key} = $c; + foreach ($config as $key => $value) { + $sessionConfig->{$key} = $value; } + Factories::injectMock('config', 'Session', $sessionConfig); $session = new MockSession(new FileHandler($appConfig, '127.0.0.1'), $appConfig); $session->setLogger(new TestLogger(new LoggerConfig())); @@ -626,7 +624,7 @@ public function testInvalidSameSite() public function testExpires() { - $session = $this->getInstance(['sessionExpiration' => 8000]); + $session = $this->getInstance(['expiration' => 8000]); $session->start(); $cookies = $session->cookies; @@ -636,7 +634,7 @@ public function testExpires() public function testExpiresOnClose() { - $session = $this->getInstance(['sessionExpiration' => 0]); + $session = $this->getInstance(['expiration' => 0]); $session->start(); $cookies = $session->cookies; diff --git a/user_guide_src/source/changelogs/v4.3.0.rst b/user_guide_src/source/changelogs/v4.3.0.rst index 4a052f49bfc8..97a9294d1415 100644 --- a/user_guide_src/source/changelogs/v4.3.0.rst +++ b/user_guide_src/source/changelogs/v4.3.0.rst @@ -320,6 +320,7 @@ Others - **View:** Added ``Controlled Cells`` that provide more structure and flexibility to your View Cells. See :ref:`View Cells ` for details. - **Validation:** Added Closure validation rule. See :ref:`validation-using-closure-rule` for details. - **Config:** Now you can specify Composer packages to auto-discover manually. See :ref:`Code Modules `. +- **Config:** Added ``Config\Session`` class to handle session configuration. - **Debug:** Kint has been updated to 5.0.1. - **Request:** Added new ``$request->getRawInputVar()`` method to return a specified variable from raw stream. See :ref:`Retrieving Raw data `. @@ -353,6 +354,7 @@ Deprecations - The public property ``IncomingRequest::$uri`` is deprecated. It will be protected. Use ``IncomingRequest::getUri()`` instead. - The public property ``IncomingRequest::$config`` is deprecated. It will be protected. - The method ``CLI::isWindows()`` is deprecated. Use ``is_windows()`` instead. +- The ``Config\App`` session properties in favor of the new session config class ``Config\Session``. Bugs Fixed ********** diff --git a/user_guide_src/source/installation/upgrade_430.rst b/user_guide_src/source/installation/upgrade_430.rst index 6852389a2b41..5d7192822c31 100644 --- a/user_guide_src/source/installation/upgrade_430.rst +++ b/user_guide_src/source/installation/upgrade_430.rst @@ -272,6 +272,8 @@ Config - app/Config/Security.php - Changed the value of the property ``$redirect`` to ``false`` to prevent redirection when a CSRF check fails. This is to make it easier to recognize that it is a CSRF error. +- app/Config/Session.php + - Added to handle session configuration. - app/Config/Validation.php - The default Validation Rules have been changed to Strict Rules for better security. See :ref:`validation-traditional-and-strict-rules`. diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst index cf9fb7c19a1f..33c6d91174ab 100644 --- a/user_guide_src/source/libraries/sessions.rst +++ b/user_guide_src/source/libraries/sessions.rst @@ -387,32 +387,35 @@ Sessions are a very sensitive component of any application, so some careful configuration must be done. Please take your time to consider all of the options and their effects. +.. note:: Since v4.3.0, the new **app/Config/Session.php** has been added. + Previously, the Session Preferences were in your **app/Config/App.php** file. + You'll find the following Session related preferences in your -**app/Config/App.php** file: +**app/Config/Session.php** file: -============================== ============================================ ================================================= ============================================================================================ +======================= ============================================ ================================================= ============================================================================================ Preference Default Options Description -============================== ============================================ ================================================= ============================================================================================ -**sessionDriver** CodeIgniter\\Session\\Handlers\\FileHandler CodeIgniter\\Session\\Handlers\\FileHandler The session storage driver to use. - CodeIgniter\\Session\\Handlers\\DatabaseHandler - CodeIgniter\\Session\\Handlers\\MemcachedHandler - CodeIgniter\\Session\\Handlers\\RedisHandler - CodeIgniter\\Session\\Handlers\\ArrayHandler -**sessionCookieName** ci_session [A-Za-z\_-] characters only The name used for the session cookie. -**sessionExpiration** 7200 (2 hours) Time in seconds (integer) The number of seconds you would like the session to last. - If you would like a non-expiring session (until browser is closed) set the value to zero: 0 -**sessionSavePath** null None Specifies the storage location, depends on the driver being used. -**sessionMatchIP** false true/false (boolean) Whether to validate the user's IP address when reading the session cookie. - Note that some ISPs dynamically changes the IP, so if you want a non-expiring session you - will likely set this to false. -**sessionTimeToUpdate** 300 Time in seconds (integer) This option controls how often the session class will regenerate itself and create a new - session ID. Setting it to 0 will disable session ID regeneration. -**sessionRegenerateDestroy** false true/false (boolean) Whether to destroy session data associated with the old session ID when auto-regenerating - the session ID. When set to false, the data will be later deleted by the garbage collector. -============================== ============================================ ================================================= ============================================================================================ +======================= ============================================ ================================================= ============================================================================================ +**driver** CodeIgniter\\Session\\Handlers\\FileHandler CodeIgniter\\Session\\Handlers\\FileHandler The session storage driver to use. + CodeIgniter\\Session\\Handlers\\DatabaseHandler + CodeIgniter\\Session\\Handlers\\MemcachedHandler + CodeIgniter\\Session\\Handlers\\RedisHandler + CodeIgniter\\Session\\Handlers\\ArrayHandler +**cookieName** ci_session [A-Za-z\_-] characters only The name used for the session cookie. +**expiration** 7200 (2 hours) Time in seconds (integer) The number of seconds you would like the session to last. + If you would like a non-expiring session (until browser is closed) set the value to zero: 0 +**savePath** null None Specifies the storage location, depends on the driver being used. +**matchIP** false true/false (boolean) Whether to validate the user's IP address when reading the session cookie. + Note that some ISPs dynamically changes the IP, so if you want a non-expiring session you + will likely set this to false. +**timeToUpdate** 300 Time in seconds (integer) This option controls how often the session class will regenerate itself and create a new + session ID. Setting it to 0 will disable session ID regeneration. +**regenerateDestroy** false true/false (boolean) Whether to destroy session data associated with the old session ID when auto-regenerating + the session ID. When set to false, the data will be later deleted by the garbage collector. +======================= ============================================ ================================================= ============================================================================================ .. note:: As a last resort, the Session library will try to fetch PHP's - session related INI settings, as well as legacy CI settings such as + session related INI settings, as well as CodeIgniter 3 settings such as 'sess_expire_on_close' when any of the above is not configured. However, you should never rely on this behavior as it can cause unexpected results or be changed in the future. Please configure @@ -423,20 +426,19 @@ Preference Default Opti (often the default value of ``1440``). This needs to be changed in ``php.ini`` or via ``ini_set()`` as needed. -In addition to the values above, the cookie and native drivers apply the -following configuration values shared by the :doc:`IncomingRequest ` and -:doc:`Security ` classes: +In addition to the values above, the Session cookie uses the +following configuration values in your **app/Config/Cookie.php** file: -==================== =============== =========================================================================== +============== =============== =========================================================================== Preference Default Description -==================== =============== =========================================================================== -**cookieDomain** '' The domain for which the session is applicable -**cookiePath** / The path to which the session is applicable -**cookieSecure** false Whether to create the session cookie only on encrypted (HTTPS) connections -**cookieSameSite** Lax The SameSite setting for the session cookie -==================== =============== =========================================================================== - -.. note:: The 'cookieHTTPOnly' setting doesn't have an effect on sessions. +============== =============== =========================================================================== +**domain** '' The domain for which the session is applicable +**path** / The path to which the session is applicable +**secure** false Whether to create the session cookie only on encrypted (HTTPS) connections +**sameSite** Lax The SameSite setting for the session cookie +============== =============== =========================================================================== + +.. note:: The ``httponly`` setting doesn't have an effect on sessions. Instead the HttpOnly parameter is always enabled, for security reasons. Additionally, the ``Config\Cookie::$prefix`` setting is completely ignored. @@ -457,8 +459,8 @@ By default, the ``FileHandler`` Driver will be used when a session is initialize because it is the safest choice and is expected to work everywhere (virtually every environment has a file system). -However, any other driver may be selected via the ``public $sessionDriver`` -line in your **app/Config/App.php** file, if you chose to do so. +However, any other driver may be selected via the ``public $driver`` +line in your **app/Config/Session.php** file, if you chose to do so. Have it in mind though, every driver has different caveats, so be sure to get yourself familiar with them (below) before you make that choice. @@ -479,12 +481,12 @@ To be more specific, it doesn't support PHP's `directory level and mode formats used in session.save_path `_, and it has most of the options hard-coded for safety. Instead, only -absolute paths are supported for ``public $sessionSavePath``. +absolute paths are supported for ``public string $savePath``. Another important thing that you should know, is to make sure that you don't use a publicly-readable or shared directory for storing your session files. Make sure that *only you* have access to see the contents of your -chosen *sessionSavePath* directory. Otherwise, anybody who can do that, can +chosen *savePath* directory. Otherwise, anybody who can do that, can also steal any of the current sessions (also known as "session fixation" attack). @@ -532,7 +534,7 @@ However, there are some conditions that must be met: In order to use the 'DatabaseHandler' session driver, you must also create this table that we already mentioned and then set it as your -``$sessionSavePath`` value. +``$savePath`` value. For example, if you would like to use 'ci_sessions' as your table name, you would do this: @@ -574,7 +576,7 @@ setting**. The examples below work both on MySQL and PostgreSQL:: ALTER TABLE ci_sessions DROP PRIMARY KEY; You can choose the Database group to use by adding a new line to the -**app/Config/App.php** file with the name of the group to use: +**app/Config/Session.php** file with the name of the group to use: .. literalinclude:: sessions/040.php @@ -584,7 +586,7 @@ from the cli to generate a migration file for you:: > php spark make:migration --session > php spark migrate -This command will take the **sessionSavePath** and **sessionMatchIP** settings into account +This command will take the **savePath** and **matchIP** settings into account when it generates the code. .. important:: Only MySQL and PostgreSQL databases are officially @@ -615,7 +617,7 @@ both familiar with Redis and using it for other purposes. Just as with the 'FileHandler' and 'DatabaseHandler' drivers, you must also configure the storage location for your sessions via the -``$sessionSavePath`` setting. +``$savePath`` setting. The format here is a bit different and complicated at the same time. It is best explained by the *phpredis* extension's README file, so we'll simply link you to it: @@ -653,7 +655,7 @@ deleted after Y seconds have passed (but not necessarily that it won't expire earlier than that time). This happens very rarely, but should be considered as it may result in loss of sessions. -The ``$sessionSavePath`` format is fairly straightforward here, +The ``$savePath`` format is fairly straightforward here, being just a ``host:port`` pair: .. literalinclude:: sessions/042.php diff --git a/user_guide_src/source/libraries/sessions/039.php b/user_guide_src/source/libraries/sessions/039.php index 3aedc2f047af..77a0a02b0222 100644 --- a/user_guide_src/source/libraries/sessions/039.php +++ b/user_guide_src/source/libraries/sessions/039.php @@ -3,10 +3,13 @@ namespace Config; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Session\Handlers\FileHandler; -class App extends BaseConfig +class Session extends BaseConfig { - public $sessionDriver = 'CodeIgniter\Session\Handlers\DatabaseHandler'; - public $sessionSavePath = 'ci_sessions'; + // ... + public string $driver = 'CodeIgniter\Session\Handlers\DatabaseHandler'; + // ... + public string $savePath = 'ci_sessions'; // ... } diff --git a/user_guide_src/source/libraries/sessions/040.php b/user_guide_src/source/libraries/sessions/040.php index f645887ec2be..386c83a0293b 100644 --- a/user_guide_src/source/libraries/sessions/040.php +++ b/user_guide_src/source/libraries/sessions/040.php @@ -3,9 +3,10 @@ namespace Config; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Session\Handlers\FileHandler; -class App extends BaseConfig +class Session extends BaseConfig { - public $sessionDBGroup = 'groupName'; // ... + public ?string $DBGroup = 'groupName'; } diff --git a/user_guide_src/source/libraries/sessions/041.php b/user_guide_src/source/libraries/sessions/041.php index f0aed919a816..4f26bf79661b 100644 --- a/user_guide_src/source/libraries/sessions/041.php +++ b/user_guide_src/source/libraries/sessions/041.php @@ -3,10 +3,13 @@ namespace Config; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Session\Handlers\FileHandler; -class App extends BaseConfig +class Session extends BaseConfig { - public $sessionDiver = 'CodeIgniter\Session\Handlers\RedisHandler'; - public $sessionSavePath = 'tcp://localhost:6379'; + // ... + public string $driver = 'CodeIgniter\Session\Handlers\RedisHandler'; + // ... + public string $savePath = 'tcp://localhost:6379'; // ... } diff --git a/user_guide_src/source/libraries/sessions/042.php b/user_guide_src/source/libraries/sessions/042.php index cad56413151c..3cdbe41f2ae1 100644 --- a/user_guide_src/source/libraries/sessions/042.php +++ b/user_guide_src/source/libraries/sessions/042.php @@ -3,10 +3,13 @@ namespace Config; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Session\Handlers\FileHandler; -class App extends BaseConfig +class Session extends BaseConfig { - public $sessionDriver = 'CodeIgniter\Session\Handlers\MemcachedHandler'; - public $sessionSavePath = 'localhost:11211'; + // ... + public string $driver = 'CodeIgniter\Session\Handlers\MemcachedHandler'; + // ... + public string $savePath = 'localhost:11211'; // ... } diff --git a/user_guide_src/source/libraries/sessions/043.php b/user_guide_src/source/libraries/sessions/043.php index e5fab8602236..f75e31430f93 100644 --- a/user_guide_src/source/libraries/sessions/043.php +++ b/user_guide_src/source/libraries/sessions/043.php @@ -3,12 +3,15 @@ namespace Config; use CodeIgniter\Config\BaseConfig; +use CodeIgniter\Session\Handlers\FileHandler; -class App extends BaseConfig +class Session extends BaseConfig { + // ... + // localhost will be given higher priority (5) here, // compared to 192.0.2.1 with a weight of 1. - public $sessionSavePath = 'localhost:11211:5,192.0.2.1:11211:1'; + public string $savePath = 'localhost:11211:5,192.0.2.1:11211:1'; // ... }