Skip to content

Commit ef7cdc2

Browse files
[HttpFoundation] Make sessions secure and lazy
1 parent 3f0a3f5 commit ef7cdc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+981
-102
lines changed

UPGRADE-3.4.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,13 @@ HttpFoundation
235235
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler`
236236
class has been deprecated and will be removed in 4.0. Use the `\SessionHandler` class instead.
237237

238-
* The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy` class has been
239-
deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly.
238+
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been
239+
deprecated and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or
240+
extend `AbstractSessionHandler` instead.
240241

241242
* The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` class has been
242243
deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly.
243244

244-
* The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` class has been
245-
deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly.
246-
247245
* `NativeSessionStorage::setSaveHandler()` now takes an instance of `\SessionHandlerInterface` as argument.
248246
Not passing it is deprecated and will throw a `TypeError` in 4.0.
249247

UPGRADE-4.0.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -542,10 +542,11 @@ HttpFoundation
542542
* The ability to check only for cacheable HTTP methods using `Request::isMethodSafe()` is
543543
not supported anymore, use `Request::isMethodCacheable()` instead.
544544

545-
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler`,
546-
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy`,
547-
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` and
548-
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` classes have been removed.
545+
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been
546+
removed. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.
547+
548+
* The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler` and
549+
`Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` classes have been removed.
549550

550551
* `NativeSessionStorage::setSaveHandler()` now requires an instance of `\SessionHandlerInterface` as argument.
551552

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: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,11 +462,14 @@ 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')
469-
->info('seconds to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed')
472+
->info('seconds to wait between 2 session metadata updates')
470473
->end()
471474
->end()
472475
->end()

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: 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\StrictSessionHandler">
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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ CHANGELOG
44
3.4.0
55
-----
66

7-
* deprecated the `NativeSessionHandler` class,
8-
* deprecated the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes,
7+
* implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new `AbstractSessionHandler`
8+
* deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
99
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
1010
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
1111
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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+
* This abstract session handler provides a generic implementation
16+
* of the PHP 7.0 SessionUpdateTimestampHandlerInterface,
17+
* enabling strict and lazy session handling.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
22+
{
23+
private $sessionName;
24+
private $prefetchId;
25+
private $prefetchData;
26+
private $newSessionId;
27+
private $igbinaryEmptyData;
28+
29+
/**
30+
* {@inheritdoc}
31+
*/
32+
public function open($savePath, $sessionName)
33+
{
34+
$this->sessionName = $sessionName;
35+
36+
return true;
37+
}
38+
39+
/**
40+
* @param string $sessionId
41+
*
42+
* @return string
43+
*/
44+
abstract protected function doRead($sessionId);
45+
46+
/**
47+
* @param string $sessionId
48+
* @param string $data
49+
*
50+
* @return bool
51+
*/
52+
abstract protected function doWrite($sessionId, $data);
53+
54+
/**
55+
* @param string $sessionId
56+
*
57+
* @return bool
58+
*/
59+
abstract protected function doDestroy($sessionId);
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function validateId($sessionId)
65+
{
66+
$this->prefetchData = $this->read($sessionId);
67+
$this->prefetchId = $sessionId;
68+
69+
return '' !== $this->prefetchData;
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function read($sessionId)
76+
{
77+
if (null !== $this->prefetchId) {
78+
$prefetchId = $this->prefetchId;
79+
$prefetchData = $this->prefetchData;
80+
$this->prefetchId = $this->prefetchData = null;
81+
82+
if ($prefetchId === $sessionId || '' === $prefetchData) {
83+
$this->newSessionId = '' === $prefetchData ? $sessionId : null;
84+
85+
return $prefetchData;
86+
}
87+
}
88+
89+
$data = $this->doRead($sessionId);
90+
$this->newSessionId = '' === $data ? $sessionId : null;
91+
if (\PHP_VERSION_ID < 70000) {
92+
$this->prefetchData = $data;
93+
}
94+
95+
return $data;
96+
}
97+
98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public function write($sessionId, $data)
102+
{
103+
if (\PHP_VERSION_ID < 70000 && $this->prefetchData) {
104+
$readData = $this->prefetchData;
105+
$this->prefetchData = null;
106+
107+
if ($readData === $data) {
108+
return $this->updateTimestamp($sessionId, $data);
109+
}
110+
}
111+
if (null === $this->igbinaryEmptyData) {
112+
// see https://github.com/igbinary/igbinary/issues/146
113+
$this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : '';
114+
}
115+
if ('' === $data || $this->igbinaryEmptyData === $data) {
116+
return $this->destroy($sessionId);
117+
}
118+
$this->newSessionId = null;
119+
120+
return $this->doWrite($sessionId, $data);
121+
}
122+
123+
/**
124+
* {@inheritdoc}
125+
*/
126+
public function destroy($sessionId)
127+
{
128+
if (\PHP_VERSION_ID < 70000) {
129+
$this->prefetchData = null;
130+
}
131+
if (!headers_sent() && ini_get('session.use_cookies')) {
132+
if (!$this->sessionName) {
133+
throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this)));
134+
}
135+
$sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
136+
$sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
137+
$sessionCookieFound = false;
138+
$otherCookies = array();
139+
foreach (headers_list() as $h) {
140+
if (0 !== stripos($h, 'Set-Cookie:')) {
141+
continue;
142+
}
143+
if (11 === strpos($h, $sessionCookie, 11)) {
144+
$sessionCookieFound = true;
145+
146+
if (11 !== strpos($h, $sessionCookieWithId, 11)) {
147+
$otherCookies[] = $h;
148+
}
149+
} else {
150+
$otherCookies[] = $h;
151+
}
152+
}
153+
if ($sessionCookieFound) {
154+
header_remove('Set-Cookie');
155+
foreach ($otherCookies as $h) {
156+
header('Set-Cookie:'.$h, false);
157+
}
158+
} else {
159+
setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly'));
160+
}
161+
}
162+
163+
return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
164+
}
165+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy