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

Skip to content

Commit e240114

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

File tree

11 files changed

+235
-27
lines changed

11 files changed

+235
-27
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": {
8000

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: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
abstract protected function doWrite($sessionId, $data);
26+
abstract protected function doDestroy($sessionId);
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function validateId($sessionId)
32+
{
33+
$this->prefetchId = $sessionId;
34+
$this->prefetchData = $this->doRead($sessionId);
35+
36+
return '' !== $this->prefetchData;
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
public function read($sessionId)
43+
{
44+
if (null !== $this->prefetchId) {
45+
$prefetchId = $this->prefetchId;
46+
$prefetchData = $this->prefetchData;
47+
$this->prefetchId = $this->prefetchData = null;
48+
49+
if ($prefetchId === $sessionId) {
50+
return $prefetchData;
51+
}
52+
}
53+
54+
return $this->doRead($sessionId);
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function updateTimestamp($sessionId, $data)
61+
{
62+
return $this->write($sessionId, $data);
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function write($sessionId, $data)
69+
{
70+
if ('' === $data) {
71+
return $this->destroy($sessionId);
72+
}
73+
74+
return $this->doWrite($sessionId, $data);
75+
}
76+
77+
/**
78+
* {@inheritdoc}
79+
*/
80+
public function destroy($sessionId)
81+
{
82+
if (!headers_sent()) {
83+
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
84+
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
85+
$sessionCookieFound = false;
86+
$otherCookies = array();
87+
foreach (headers_list() as $h) {
88+
if (0 !== stripos($h, 'Set-Cookie:')) {
89+
continue;
90+
}
91+
if (11 === strpos($h, $sessionCookie, 11)) {
92+
$sessionCookieFound = true;
93+
94+
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
95+
$otherCookies[] = $h;
96+
}
97+
} else {
98+
$otherCookies[] = $h;
99+
}
100+
}
101+
if ($sessionCookieFound) {
102+
header_remove('Set-Cookie');
103+
foreach ($otherCookies as $h) {
104+
header('Set-Cookie:'.$h, false);
105+
}
106+
} else {
107+
setcookie($this->sessionName, '', 0);
108+
}
109+
}
110+
111+
return $this->doDestroy($sessionId);
112+
}
113+
}

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

Lines changed: 14 additions & 4 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
@@ -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: 6 additions & 4 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
@@ -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
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
}
@@ -273,7 +275,7 @@ public function open($savePath, $sessionName)
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