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

Skip to content

Commit b75cc85

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

33 files changed

+922
-86
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()
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 & 12 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) {
@@ -934,8 +927,6 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c
934927
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage',
935928
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage',
936929
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler',
937-
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy',
938-
'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy',
939930
$container->getDefinition('session')->getClass(),
940931
));
941932

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\SessionHandlerWrapper">
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: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
/**
26+
* @param string $sessionId
27+
*
28+
* @return string
29+
*/
30+
abstract protected function doRead($sessionId);
31+
32+
/**
33+
* @param string $sessionId
34+
* @param string $data
35+
*
36+
* @return bool
37+
*/
38+
abstract protected function doWrite($sessionId, $data);
39+
40+
/**
41+
* @param string $sessionId
42+
*
43+
* @return bool
44+
*/
45+
abstract protected function doDestroy($sessionId);
46+
47+
/**
48+
* {@inheritdoc}
49+
*/
50+
public function validateId($sessionId)
51+
{
52+
$this->prefetchData = $this->read($sessionId);
53+
$this->prefetchId = $sessionId;
54+
55+
return '' !== $this->prefetchData;
56+
}
57+
58+
/**
59+
* {@inheritdoc}
60+
*/
61+
public function read($sessionId)
62+
{
63+
if (null !== $this->prefetchId) {
64+
$prefetchId = $this->prefetchId;
65+
$prefetchData = $this->prefetchData;
66+
$this->prefetchId = $this->prefetchData = null;
67+
68+
if ($prefetchId === $sessionId) {
69+
return $prefetchData;
70+
}
71+
}
72+
73+
$data = $this->doRead($sessionId);
74+
$this->newSessionId = '' === $data ? $sessionId : null;
75+
76+
return $data;
77+
}
78+
79+
/**
80+
* {@inheritdoc}
81+
*/
82+
public function updateTimestamp($sessionId, $data)
83+
{
84+
return $this->write($sessionId, $data);
85+
}
86+
87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function write($sessionId, $data)
91+
{
92+
if ('' === $data) {
93+
return $this->destroy($sessionId);
94+
}
95+
$this->newSessionId = null;
96+
97+
return $this->doWrite($sessionId, $data);
98+
}
99+
100+
/**
101+
* {@inheritdoc}
102+
*/
103+
public function destroy($sessionId)
104+
{
105+
if (!headers_sent()) {
106+
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
107+
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
108+
$sessionCookieFound = false;
109+
$otherCookies = array();
110+
foreach (headers_list() as $h) {
111+
if (0 !== stripos($h, 'Set-Cookie:')) {
112+
continue;
113+
}
114+
if (11 === strpos($h, $sessionCookie, 11)) {
115+
$sessionCookieFound = true;
116+
117+
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
118+
$otherCookies[] = $h;
119+
}
120+
} else {
121+
$otherCookies[] = $h;
122+
}
123+
}
124+
if ($sessionCookieFound) {
125+
header_remove('Set-Cookie');
126+
foreach ($otherCookies as $h) {
127+
header('Set-Cookie:'.$h, false);
128+
}
129+
} else {
130+
setcookie($this->sessionName, '', 0);
131+
}
132+
}
133+
134+
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
135+
}
136+
137+
/**
138+
* Gets the session ID.
139+
*
140+
* @return string
141+
*/
142+
public function getId()
143+
{
144+
return session_id();
145+
}
146+
147+
/**
148+
* Has a session started?
149+
*
150+
* @return bool
151+
*/
152+
public function isActive()
153+
{
154+
return \PHP_SESSION_ACTIVE === session_status();
155+
}
156+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
* @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler instead.
2020
*/
21-
class MemcacheSessionHandler implements \SessionHandlerInterface
21+
class MemcacheSessionHandler extends AbstractSessionHandler
2222
{
2323
/**
2424
* @var \Memcache Memcache driver
@@ -77,23 +77,23 @@ public function close()
7777
/**
7878
* {@inheritdoc}
7979
*/
80-
public function read($sessionId)
80+
protected function doRead($sessionId)
8181
{
8282
return $this->memcache->get($this->prefix.$sessionId) ?: '';
8383
}
8484

8585
/**
8686
* {@inheritdoc}
8787
*/
88-
public function write($sessionId, $data)
88+
protected function doWrite($sessionId, $data)
8989
{
9090
return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl);
9191
}
9292

9393
/**
9494
* {@inheritdoc}
9595
*/
96-
public function destroy($sessionId)
96+
protected function doDestroy($sessionId)
9797
{
9898
$this->memcache->delete($this->prefix.$sessionId);
9999

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

0 commit comments

Comments
 (0)
0