8000 [HttpFoundation] Make sessions secure and lazy · symfony/symfony@cd65297 · GitHub
[go: up one dir, main page]

Skip to content

Commit cd65297

Browse files
[HttpFoundation] Make sessions secure and lazy
1 parent d48bcbf commit cd65297

File tree

11 files changed

+239
-29
lines changed

11 files changed

+239
-29
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"symfony/polyfill-intl-icu": "~1.0",
3131
"symfony/polyfill-mbstring": "~1.0",
3232
"symfony/polyfill-php56": "~1.0",
33-
"symfony/polyfill-php70": "~1.0",
33+
"symfony/polyfill-php70": "~1.6",
3434
"symfony/polyfill-util": "~1.0"
3535
},
3636
"replace": {

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -908,24 +908,17 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
908908
}
909909
}
910910

911-
$container->setParameter('session.storage.options', $options);
912-
913911
// session handler (the internal callback registered with PHP session management)
914912
if (null === $config['handler_id']) {
915913
// Set the handler class to be null
916914
$container->getDefinition('session.storage.native')->replaceArgument(1, null);
917915
$container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null);
918916
} else {
919-
$handlerId = $config['handler_id'];
920-
921-
if ($config['metadata_update_threshold'] > 0) {
922-
$container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId));
923-
$handlerId = 'session.handler.write_check';
924-
}
925-
926-
$container->setAlias('session.handler', $handlerId)->setPrivate(true);
917+
$options['lazy_write'] = 1;
918+
$container->setAlias('session.handler', $config['handler_id'])->setPrivate(true);
927919
}
928920

921+
$container->setParameter('session.storage.options', $options);
929922
$container->setParameter('session.save_path', $config['save_path']);
930923

931924
if (\PHP_VERSION_ID < 70000) {

src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@
5252
<argument>%session.save_path%</argument>
5353
</service>
5454

55-
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" />
55+
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler">
56+
<deprecated>The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use the `session.lazy_write` ini setting instead.</deprecated>
57+
</service>
5658

5759
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
5860
<tag name="kernel.event_subscriber" />
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
13+
14+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*/
17+
abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
18+
{
19+
protected $sessionName;
20+
21+
private $prefetchId;
22+
private $prefetchData;
23+
24+
abstract protected function doRead($sessionId);
25+
26+
abstract protected function doWrite($sessionId, $data);
27+
28+
abstract protected function doDestroy($sessionId);
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function validateId($sessionId)
34+
{
35+
$this->prefetchId = $sessionId;
36+
$this->prefetchData = $this->doRead($sessionId);
37+
38+
return '' !== $this->prefetchData;
39+
}
40+
41+
/**
42+
* {@inheritdoc}
43+
*/
44+
public function read($sessionId)
45+
{
46+
if (null !== $this->prefetchId) {
47+
$prefetchId = $this->prefetchId;
48+
$prefetchData = $this->prefetchData;
49+
$this->prefetchId = $this->prefetchData = null;
50+
51+
if ($prefetchId === $sessionId) {
52+
return $prefetchData;
53+
}
54+
}
55+
56+
return $this->doRead($sessionId);
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function updateTimestamp($sessionId, $data)
63+
{
64+
return $this->write($sessionId, $data);
65+
}
66+
67+
/**
68+
* {@inheritdoc}
69+
*/
70+
public function write($sessionId, $data)
71+
{
72+
if ('' === $data) {
73+
return $this->destroy($sessionId);
74+
}
75+
76+
return $this->doWrite($sessionId, $data);
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function destroy($sessionId)
83+
{
84+
if (!headers_sent()) {
85+
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
86+
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
87+
$sessionCookieFound = false;
88+
$otherCookies = array();
89+
foreach (headers_list() as $h) {
90+
if (0 !== stripos($h, 'Set-Cookie:')) {
91+
continue;
92+
}
93+
if (11 === strpos($h, $sessionCookie, 11)) {
94+
$sessionCookieFound = true;
95+
96+
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
97+
$otherCookies[] = $h;
98+
}
99+
} else {
100+
$otherCookies[] = $h;
101+
}
102+
}
103+
if ($sessionCookieFound) {
104+
header_remove('Set-Cookie');
105+
foreach ($otherCookies as $h) {
106+
header('Set-Cookie:'.$h, false);
107+
}
108+
} else {
109+
setcookie($this->sessionName, '', 0);
110+
}
111+
}
112+
113+
return $this->doDestroy($sessionId);
114+
}
115+
}

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* @author Drak <drak@zikula.org>
2121
*/
22-
class MemcachedSessionHandler implements \SessionHandlerInterface
22+
class MemcachedSessionHandler extends AbstractSessionHandler
2323
{
2424
/**
2525
* @var \Memcached Memcached driver
@@ -39,7 +39,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface
3939
/**
4040
* List of available options:
4141
* * prefix: The prefix to use for the memcached keys in order to avoid collision
42-
* * expiretime: The time to live in seconds
42+
* * expiretime: The time to live in seconds.
4343
*
4444
* @param \Memcached $memcached A \Memcached instance
4545
* @param array $options An associative array of Memcached options
@@ -65,6 +65,8 @@ public function __construct(\Memcached $memcached, array $options = array())
6565
*/
6666
public function open($savePath, $sessionName)
6767
{
68+
$this->sessionName = $sessionName;
69+
6870
return true;
6971
}
7072

@@ -79,23 +81,31 @@ public function close()
7981
/**
8082
* {@inheritdoc}
8183
*/
82-
public function read($sessionId)
84+
protected function doRead($sessionId)
8385
{
8486
return $this->memcached->get($this->prefix.$sessionId) ?: '';
8587
}
8688

8789
/**
8890
* {@inheritdoc}
8991
*/
90-
public function write($sessionId, $data)
92+
public function updateTimestamp($sessionId, $data)
93+
{
94+
return $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl);
95+
}
96+
97+
/**
98+
* {@inheritdoc}
99+
*/
100+
protected function doWrite($sessionId, $data)
91101
{
92102
return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl);
93103
}
94104

95105
/**
96106
* {@inheritdoc}
97107
*/
98-
public function destroy($sessionId)
108+
protected function doDestroy($sessionId)
99109
{
100110
$result = $this->memcached->delete($this->prefix.$sessionId);
101111

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* @see https://packagist.org/packages/mongodb/mongodb
2020
* @see http://php.net/manual/en/set.mongodb.php
2121
*/
22-
class MongoDbSessionHandler implements \SessionHandlerInterface
22+
class MongoDbSessionHandler extends AbstractSessionHandler
2323
{
2424
/**
2525
* @var \Mongo|\MongoClient|\MongoDB\Client
@@ -43,7 +43,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface
4343
* * id_field: The field name for storing the session id [default: _id]
4444
* * data_field: The field name for storing the session data [default: data]
4545
* * time_field: The field name for storing the timestamp [default: time]
46-
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
46+
* * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
4747
*
4848
* It is strongly recommended to put an index on the `expiry_field` for
4949
* garbage-collection. Alternatively it's possible to automatically expire
@@ -97,6 +97,8 @@ public function __construct($mongo, array $options)
9797
*/
9898
public function open($savePath, $sessionName)
9999
{
100+
$this->sessionName = $sessionName;
101+
100102
return true;
101103
}
102104

@@ -111,7 +113,7 @@ public function close()
111113
/**
112114
* {@inheritdoc}
113115
*/
114-
public function destroy($sessionId)
116+
protected function doDestroy($sessionId)
115117
{
116118
$methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove';
117119

@@ -139,7 +141,7 @@ public function gc($maxlifetime)
139141
/**
140142
* {@inheritdoc}
141143
*/
142-
public function write($sessionId, $data)
144+
protected function doWrite($sessionId, $data)
143145
{
144146
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
145147

@@ -171,7 +173,7 @@ public function write($sessionId, $data)
171173
/**
172174
* {@inheritdoc}
173175
*/
174-
public function read($sessionId)
176+
protected function doRead($sessionId)
175177
{
176178
$dbData = $this->getCollection()->findOne(array(
177179
$this->options['id_field'] => $sessionId,

src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php

Lines changed: 10 additions & 6 deletions
< 10000 td data-grid-cell-id="diff-fb1dcfa62129a037c11aebcf43264282a5c8e2da5d9409aa8f4f3281659b3dd1-272-274-0" data-selected="false" role="gridcell" style="background-color:var(--bgColor-accent-muted, var(--color-accent-subtle));flex-grow:1" tabindex="-1" valign="top" class="focusable-grid-cell diff-hunk-cell left-side" colSpan="4">
@@ -273,7 +275,7 @@ public function open($savePath, $sessionName)
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
* @author Michael Williams <michael.williams@funsational.com>
3939
* @author Tobias Schultze <http://tobion.de>
4040
*/
41-
class PdoSessionHandler implements \SessionHandlerInterface
41+
class PdoSessionHandler extends AbstractSessionHandler
4242
{
4343
/**
4444
* No locking is done. This means sessions are prone to loss of data due to
@@ -260,6 +260,8 @@ public function isSessionExpired()
260260
*/
261261
public function open($savePath, $sessionName)
262262
{
263+
$this->sessionName = $sessionName;
264+
263265
if (null === $this->pdo) {
264266
$this->connect($this->dsn ?: $savePath);
265267
}
273275
public function read($sessionId)
274276
{
275277
try {
276-
return $this->doRead($sessionId);
278+
return parent::read($sessionId);
277279
} catch (\PDOException $e) {
278280
$this->rollback();
279281

@@ -296,7 +298,7 @@ public function gc($maxlifetime)
296298
/**
297299
* {@inheritdoc}
298300
*/
299-
public function destroy($sessionId)
301+
protected function doDestroy($sessionId)
300302
{
301303
// delete the record associated with this id
302304
$sql = "DELETE FROM $this->table WHERE $this->idCol = :id";
@@ -317,7 +319,7 @@ public function destroy($sessionId)
317319
/**
318320
* {@inheritdoc}
319321
*/
320-
public function write($sessionId, $data)
322+
protected function doWrite($sessionId, $data)
321323
{
322324
$maxlifetime = (int) ini_get('session.gc_maxlifetime');
323325

@@ -491,7 +493,7 @@ private function rollback()
491493
*
492494
* @return string The session data
493495
*/
494-
private function doRead($sessionId)
496+
protected function doRead($sessionId)
495497
{
496498
$this->sessionExpired = false;
497499

@@ -517,7 +519,9 @@ private function doRead($sessionId)
517519
return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0];
518520
}
519521

520-
if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
522+
if (\PHP_VERSION_ID < 70000 && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) {
523+
// Before PHP 7.0, session fixation was possible so locking could be needed even when creating a session.
524+
// Starting with 7.0, secure random ids are generated so not concurrency is possible, thus this code path can be removed.
521525
// Exclusive-reading of non-existent rows does not block, so we need to do an insert to block
522526
// until other connections to the session are committed.
523527
try {

0 commit comments

Comments
 (0)
0