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

Skip to content

Commit aa5e67b

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

26 files changed

+788
-34
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/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated,
78
* Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore,
89
but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead
910
* Always register a minimalist logger that writes in `stderr`

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,10 @@ private function addSessionSection(ArrayNodeDefinition $rootNode)
462462
->scalarNode('gc_divisor')->end()
463463
->scalarNode('gc_probability')->defaultValue(1)->end()
464464
->scalarNode('gc_maxlifetime')->end()
465-
->booleanNode('use_strict_mode')->end()
465+
->booleanNode('use_strict_mode')
466+
->defaultTrue()
< F438 /td>467+
->setDeprecated('The "%path%.%node%" option is enabled by default and deprecated since Symfony 3.4. It will be always enabled in 4.0.')
468+
->end()
466469
->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end()
467470
->integerNode('metadata_update_threshold')
468471
->defaultValue('0')

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

Lines changed: 3 additions & 10 deletions
10000
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: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,17 @@
4848
<argument type="service" id="session.storage.metadata_bag" />
4949
</service>
5050

51-
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
52-
<argument>%session.save_path%</argument>
51+
<service id="session.handler.native_file" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\SessionHandlerProxy">
52+
<argument type="service">
53+
<service class="Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler">
54+
<argument>%session.save_path%</argument>
55+
</service>
56+
</argument>
5357
</service>
5458

55-
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" />
59+
<service id="session.handler.write_check" class="Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler">
60+
<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>
61+
</service>
5662

5763
<service id="session_listener" class="Symfony\Component\HttpKernel\EventListener\SessionListener">
5864
<tag name="kernel.event_subscriber" />

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ protected static function getBundleDefaultConfig()
301301
'gc_probability' => 1,
302302
'save_path' => '%kernel.cache_dir%/sessions',
303303
'metadata_update_threshold' => '0',
304+
'use_strict_mode' => true,
304305
),
305306
'request' => array(
306307
'enabled' => false,

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ CHANGELOG
44
3.4.0
55
-----
66

7+
* implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new `AbstractSessionHandler`
8+
* deprecated `WriteCheckSessionHandler`
79
* deprecated the `NativeSessionHandler` class,
810
* deprecated the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes,
911
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
private $newSessionId;
24+
25+
abstract protected function doRead($sessionId);
26+
27+
abstract protected function doWrite($sessionId, $data);
28+
29+
abstract protected function doDestroy($sessionId);
30+
31+
/**
32+
* {@inheritdoc}
33+
*/
34+
public function validateId($sessionId)
35+
{
36+
$this->prefetchData = $this->read($sessionId);
37+
$this->prefetchId = $sessionId;
38+
39+
return '' !== $this->prefetchData;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function read($sessionId)
46+
{
47+
if (null !== $this->prefetchId) {
48+
$prefetchId = $this->prefetchId;
49+
$prefetchData = $this->prefetchData;
50+
$this->prefetchId = $this->prefetchData = null;
51+
52+
if ($prefetchId === $sessionId) {
53+
return $prefetchData;
54+
}
55+
}
56+
57+
$data = $this->doRead($sessionId);
58+
$this->newSessionId = '' === $data ? $sessionId : null;
59+
60+
return $data;
61+
}
62+
63+
/**
64+
* {@inheritdoc}
65+
*/
66+
public function updateTimestamp($sessionId, $data)
67+
{
68+
return $this->write($sessionId, $data);
69+
}
70+
71+
/**
72+
* {@inheritdoc}
73+
*/
74+
public function write($sessionId, $data)
75+
{
76+
if ('' === $data) {
77+
return $this->destroy($sessionId);
78+
}
79+
$this->newSessionId = null;
80+
81+
return $this->doWrite($sessionId, $data);
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*/
87+
public function destroy($sessionId)
88+
{
89+
if (!headers_sent()) {
90+
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
91+
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
92+
$sessionCookieFound = false;
93+
$otherCookies = array();
94+
foreach (headers_list() as $h) {
95+
if (0 !== stripos($h, 'Set-Cookie:')) {
96+
continue;
97+
}
98+
if (11 === strpos($h, $sessionCookie, 11)) {
99+
$sessionCookieFound = true;
100+
101+
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
102+
$otherCookies[] = $h;
103+
}
104+
} else {
105+
$otherCookies[] = $h;
106+
}
107+
}
108+
if ($sessionCookieFound) {
109+
header_remove('Set-Cookie');
110+
foreach ($otherCookies as $h) {
111+
header('Set-Cookie:'.$h, false);
112+
}
113+
} else {
114+
setcookie($this->sessionName, '', 0);
115+
}
116+
}
117+
118+
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
119+
}
120+
}

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: 34 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,34 @@ public function write($sessionId, $data)
171173
/**
172174
* {@inheritdoc}
173< 10000 code>175
*/
174-
public function read($sessionId)
176+
public function updateTimestamp($sessionId, $data)
177+
{
178+
$expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime'));
179+
180+
if ($this->mongo instanceof \MongoDB\Client) {
181+
$methodName = 'updateOne';
182+
$options = array();
183+
} else {
184+
$methodName = 'update';
185+
$options = array('multiple' => false);
186+
}
187+
188+
$this->getCollection()->$methodName(
189+
array($this->options['id_field'] => $sessionId),
190+
array('$set' => array(
191+
$this->options['time_field'] => $this->createDateTime(),
192+
$this->options['expiry_field'] => $expiry,
193+
)),
194+
$options
195+
);
196+
197+
return true;
198+
}
199+
200+
/**
201+
* {@inheritdoc}
202+
*/
203+
protected function doRead($sessionId)
175204
{
176205
$dbData = $this->getCollection()->findOne(array(
177206
$this->options['id_field'] => $sessionId,

0 commit comments

Comments
 (0)
0