Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
83fda4b
Merge pull request #1 from yiisoft/master
rhertogh Jul 30, 2015
5231894
Merge branch 'master' of https://github.com/yiisoft/yii2
rhertogh Jan 24, 2020
e388dfc
Merge branch 'master' of https://github.com/yiisoft/yii2
rhertogh Jul 16, 2020
6b56e2c
Merge branch 'master' of https://github.com/yiisoft/yii2
rhertogh Aug 20, 2020
0d8eac2
Proof of concept for respecting the session.use_strict_mode in settin…
rhertogh Aug 20, 2020
6896a92
Respect ini session.use_strict_mode
rhertogh Sep 5, 2020
fcc82c5
Fixed SessionTestTrait when running consecutive tests
rhertogh Sep 5, 2020
4fa0b66
Using session.use_strict_mode to keep track of strict mode status (so…
rhertogh Sep 5, 2020
927f2ae
Merge branch 'master' into respect_ini_session_use_strict_mode
rhertogh Sep 5, 2020
3a8ad23
Added documentation for Respect ini session.use_strict_mode
rhertogh Sep 5, 2020
4b37b14
Support using 'native' session after a custom session handler has bee…
rhertogh Sep 6, 2020
83b9827
Added test to ensure Yii session handler does not interfere with nati…
rhertogh Sep 6, 2020
b0c58cf
Added missing 'since' phpdoc for Session::[set/get]UseStrictMode()
rhertogh Sep 6, 2020
cd8c0e3
Disable session garbage collection when testing (https://www.leaseweb…
rhertogh Sep 6, 2020
002c7e8
Another attempt to solve the "session_start(): ps_files_cleanup_dir: …
rhertogh Sep 6, 2020
8de2a59
Set sessin directory permissions in order to solve the "session_start…
rhertogh Sep 6, 2020
697e2d8
Only set session dir permissions if a sessision.save_path is set. Add…
rhertogh Sep 6, 2020
9093103
Set session directory to github runner temp in order to solve the "se…
rhertogh Sep 6, 2020
4f98235
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Sep 6, 2020
2c8621d
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Sep 6, 2020
0e9e4ab
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Sep 7, 2020
cdef723
Merge branch 'master' of https://github.com/yiisoft/yii2 into respect…
rhertogh Sep 8, 2020
5373ab1
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Sep 11, 2020
574ac5c
Implemented polyfill for Session::$useStrictMode for PHP < 5.5.2
rhertogh Sep 12, 2020
e0f86b1
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Sep 14, 2020
8449ce5
Merge branch 'master' of https://github.com/yiisoft/yii2 into respect…
rhertogh Sep 15, 2020
1956afc
Fixed changelog after upstream merge
rhertogh Sep 15, 2020
cac9cdc
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Sep 21, 2020
7d1e2ed
Merge branch 'master' into respect_ini_session_use_strict_mode
rhertogh Oct 6, 2020
4e48311
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Oct 23, 2020
ec28f9e
Merge branch 'master' into respect_ini_session_use_strict_mode
rhertogh Oct 30, 2020
aab82b9
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Oct 30, 2020
911c10a
Merge branch 'master' into respect_ini_session_use_strict_mode
samdark Oct 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
php-version: ${{ matrix.php }}
tools: pecl
extensions: apc, curl, dom, imagick, intl, mbstring, mcrypt, memcached, mysql, pdo, pdo_mysql, pdo_pgsql, pdo_sqlite, pgsql, sqlite
ini-values: date.timezone='UTC'
ini-values: date.timezone='UTC', session.save_path="${{ runner.temp }}"
- name: Install php-sqlsrv
run: |
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - >/dev/null 2>&1
Expand Down Expand Up @@ -114,6 +114,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: 7.2
ini-values: session.save_path=${{ runner.temp }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
Expand Down
1 change: 1 addition & 0 deletions docs/guide/runtime-sessions-cookies.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,3 +403,4 @@ To use this feature across different PHP versions check the version first. E.g.
As [noted in PHP manual](https://www.php.net/manual/en/session.security.ini.php), `php.ini` has important
session security settings. Please ensure recommended settings are applied. Especially `session.use_strict_mode`
that is not enabled by default in PHP installations.
This setting can also be set with [[yii\web\Session::useStrictMode]].
1 change: 1 addition & 0 deletions framework/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Yii Framework 2 Change Log
2.0.39 under development
------------------------

- Enh #18247: Added support for the 'session.use_strict_mode' ini directive in `yii\web\Session` (rhertogh)
- Bug #18263: Fix writing `\yii\caching\FileCache` files to the same directory when `keyPrefix` is set (githubjeka)
- Bug #18160, #18192: Fixed `registerFile` with argument depends set does not use the position and appendTimestamp argument, also modify the unit view (baleeny)
- Bug #18290: Fix response with non-seekable streams (schmunk42)
Expand Down
35 changes: 35 additions & 0 deletions framework/UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,41 @@ Upgrade from Yii 2.0.37
* Resolving DI references inside of arrays in dependencies was made optional and turned off by default. In order
to turn it on, set `resolveArrays` of container instance to `true`.

* `yii\web\Session` now respects the 'session.use_strict_mode' ini directive.
In case you use a custom `Session` class and have overwritten the `Session::openSession()` and/or
`Session::writeSession()` functions changes might be required:
* When in strict mode the `openSession()` function should check if the requested session id exists
(and mark it for forced regeneration if not).
For example, the `DbSession` does this at the beginning of the function as follows:
```php
if ($this->getUseStrictMode()) {
$id = $this->getId();
if (!$this->getReadQuery($id)->exists()) {
//This session id does not exist, mark it for forced regeneration
$this->_forceRegenerateId = $id;
}
}
// ... normal function continues ...
```
* When in strict mode the `writeSession()` function should ignore writing the session under the old id.
For example, the `DbSession` does this at the beginning of the function as follows:
```php
if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
//Ignore write when forceRegenerate is active for this id
return true;
}
// ... normal function continues ...
```
> Note: The sample code above is specific for the `yii\web\DbSession` class.
Make sure you use the correct implementation based on your parent class,
e.g. `yii\web\CacheSession`, `yii\redis\Session`, `yii\mongodb\Session`, etc.

> Note: In case your custom functions call their `parent` functions, there are probably no changes needed to your
code if those parents implement the `useStrictMode` checks.

> Warning: in case `openSession()` and/or `writeSession()` functions do not implement the `useStrictMode` code
the session could be stored under the forced id without warning even if `useStrictMode` is enabled.

Upgrade from Yii 2.0.36
-----------------------

Expand Down
25 changes: 25 additions & 0 deletions framework/web/CacheSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,26 @@ public function getUseCustomStorage()
return true;
}

/**
* Session open handler.
* @internal Do not call this method directly.
* @param string $savePath session save path
* @param string $sessionName session name
* @return bool whether session is opened successfully
*/
public function openSession($savePath, $sessionName)
{
if ($this->getUseStrictMode()) {
$id = $this->getId();
if (!$this->cache->exists($this->calculateKey($id))) {
//This session id does not exist, mark it for forced regeneration
$this->_forceRegenerateId = $id;
}
}

return parent::openSession($savePath, $sessionName);
}

/**
* Session read handler.
* @internal Do not call this method directly.
Expand All @@ -91,6 +111,11 @@ public function readSession($id)
*/
public function writeSession($id, $data)
{
if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
//Ignore write when forceRegenerate is active for this id
return true;
}

return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout());
}

Expand Down
41 changes: 38 additions & 3 deletions framework/web/DbSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,26 @@ public function init()
$this->db = Instance::ensure($this->db, Connection::className());
}

/**
* Session open handler.
* @internal Do not call this method directly.
* @param string $savePath session save path
* @param string $sessionName session name
* @return bool whether session is opened successfully
*/
public function openSession($savePath, $sessionName)
{
if ($this->getUseStrictMode()) {
$id = $this->getId();
if (!$this->getReadQuery($id)->exists()) {
//This session id does not exist, mark it for forced regeneration
$this->_forceRegenerateId = $id;
}
}

return parent::openSession($savePath, $sessionName);
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -160,9 +180,7 @@ public function close()
*/
public function readSession($id)
{
$query = new Query();
$query->from($this->sessionTable)
->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]);
$query = $this->getReadQuery($id);

if ($this->readCallback !== null) {
$fields = $query->one($this->db);
Expand All @@ -182,6 +200,11 @@ public function readSession($id)
*/
public function writeSession($id, $data)
{
if ($this->getUseStrictMode() && $id === $this->_forceRegenerateId) {
//Ignore write when forceRegenerate is active for this id
return true;
}

// exception must be caught in session write handler
// https://secure.php.net/manual/en/function.session-set-save-handler.php#refsect1-function.session-set-save-handler-notes
try {
Expand Down Expand Up @@ -240,6 +263,18 @@ public function gcSession($maxLifetime)
return true;
}

/**
* Generates a query to get the session from db
* @param string $id The id of the session
* @return Query
*/
protected function getReadQuery($id)
{
return (new Query())
->from($this->sessionTable)
->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]);
}

/**
* Method typecasts $fields before passing them to PDO.
* Default implementation casts field `data` to `\PDO::PARAM_LOB`.
Expand Down
72 changes: 72 additions & 0 deletions framework/web/Session.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,25 @@
* @property bool $useCustomStorage Whether to use custom storage. This property is read-only.
* @property bool $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to
* false.
* @property bool $useStrictMode Whether strict mode is enabled or not.
* Note: Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.
*
* @author Qiang Xue <[email protected]>
* @since 2.0
*/
class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
{
/**
* @var string|null Holds the original session module (before a custom handler is registered) so that it can be
* restored when a Session component without custom handler is used after one that has.
*/
static protected $_originalSessionModule = null;

/**
* Polyfill for ini directive session.use-strict-mode for PHP < 5.5.2.
*/
static private $_useStrictModePolyfill = false;

/**
* @var string the name of the session variable that stores the flash message data.
*/
Expand All @@ -82,6 +95,10 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
* @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods.
*/
public $handler;
/**
* @var string|null Holds the session id in case useStrictMode is enabled and the session id needs to be regenerated
*/
protected $_forceRegenerateId = null;

/**
* @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function
Expand Down Expand Up @@ -136,6 +153,11 @@ public function open()

YII_DEBUG ? session_start() : @session_start();

if ($this->getUseStrictMode() && $this->_forceRegenerateId) {
$this->regenerateID();
$this->_forceRegenerateId = null;
}

if ($this->getIsActive()) {
Yii::info('Session started', __METHOD__);
$this->updateFlashCounters();
Expand All @@ -152,6 +174,11 @@ public function open()
*/
protected function registerSessionHandler()
{
$sessionModuleName = session_module_name();
if (static::$_originalSessionModule === null) {
static::$_originalSessionModule = $sessionModuleName;
}

if ($this->handler !== null) {
if (!is_object($this->handler)) {
$this->handler = Yii::createObject($this->handler);
Expand Down Expand Up @@ -180,6 +207,12 @@ protected function registerSessionHandler()
[$this, 'gcSession']
);
}
} elseif (
$sessionModuleName !== static::$_originalSessionModule
&& static::$_originalSessionModule !== null
&& static::$_originalSessionModule !== 'user'
) {
session_module_name(static::$_originalSessionModule);
}
}

Expand All @@ -191,6 +224,8 @@ public function close()
if ($this->getIsActive()) {
YII_DEBUG ? session_write_close() : @session_write_close();
}

$this->_forceRegenerateId = null;
}

/**
Expand Down Expand Up @@ -514,6 +549,43 @@ public function setTimeout($value)
$this->unfreeze();
}

/**
* @var bool Whether strict mode is enabled or not.
* When `true` this setting prevents the session component to use an uninitialized session ID.
* Note: Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.
* Warning! Although enabling strict mode is mandatory for secure sessions, the default value of 'session.use-strict-mode' is `0`.
* @see https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode
* @since 2.0.38
*/
public function setUseStrictMode($value)
{
if (PHP_VERSION_ID < 50502) {
if ($this->getUseCustomStorage() || !$value) {
self::$_useStrictModePolyfill = $value;
} else {
throw new InvalidConfigException('Enabling `useStrictMode` on PHP < 5.5.2 is only supported with custom storage classes.');
}
} else {
$this->freeze();
ini_set('session.use_strict_mode', $value ? '1' : '0');
$this->unfreeze();
}
}

/**
* @return bool Whether strict mode is enabled or not.
* @see setUseStrictMode()
* @since 2.0.38
*/
public function getUseStrictMode()
{
if (PHP_VERSION_ID < 50502) {
return self::$_useStrictModePolyfill;
}

return (bool)ini_get('session.use_strict_mode');
}

/**
* Session open handler.
* This method should be overridden if [[useCustomStorage]] returns true.
Expand Down
14 changes: 13 additions & 1 deletion tests/framework/web/session/AbstractDbSessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@

use Yii;
use yii\db\Connection;
use yii\db\Query;
use yii\db\Migration;
use yii\db\Query;
use yii\web\DbSession;
use yiiunit\framework\console\controllers\EchoMigrateController;
use yiiunit\TestCase;
Expand All @@ -20,6 +20,8 @@
*/
abstract class AbstractDbSessionTest extends TestCase
{
use SessionTestTrait;

/**
* @return string[] the driver names that are suitable for the test (mysql, pgsql, etc)
*/
Expand Down Expand Up @@ -266,4 +268,14 @@ public function testInstantiate()
Yii::$app->set('sessionDb', null);
ini_set('session.gc_maxlifetime', $oldTimeout);
}

public function testInitUseStrictMode()
{
$this->initStrictModeTest(DbSession::className());
}

public function testUseStrictMode()
{
$this->useStrictModeTest(DbSession::className());
}
}
12 changes: 12 additions & 0 deletions tests/framework/web/session/CacheSessionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
class CacheSessionTest extends \yiiunit\TestCase
{
use SessionTestTrait;

protected function setUp()
{
parent::setUp();
Expand Down Expand Up @@ -51,4 +53,14 @@ public function testNotWrittenSessionDestroying()

$this->assertTrue($session->destroySession($session->getId()));
}

public function testInitUseStrictMode()
{
$this->initStrictModeTest(CacheSession::className());
}

public function testUseStrictMode()
{
$this->useStrictModeTest(CacheSession::className());
}
}
Loading