Skip to content

Commit 10df10c

Browse files
committed
feature symfony#28446 [SecurityBundle] make remember-me cookies auto-secure + inherit their default config from framework.session.cookie_* (nicolas-grekas)
This PR was merged into the 4.2-dev branch. Discussion ---------- [SecurityBundle] make remember-me cookies auto-secure + inherit their default config from framework.session.cookie_* | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#28338 | License | MIT | Doc PR | - Let's make it easier to have a good default security level, now for the remember-me cookie. Commits ------- 6ec223b [SecurityBundle] make remember-me cookies auto-secure + inherit their default config from framework.session.cookie_*
2 parents b36172c + 6ec223b commit 10df10c

File tree

17 files changed

+91
-17
lines changed

17 files changed

+91
-17
lines changed

src/Symfony/Bridge/Monolog/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ CHANGELOG
77
* added `ProcessorInterface`: an optional interface to allow autoconfiguration of Monolog processors
88
* The methods `DebugProcessor::getLogs()`, `DebugProcessor::countErrors()`, `Logger::getLogs()`
99
and `Logger::countErrors()` will have a new `$request` argument in version 5.0, not defining
10-
it is deprecated since Symfony 4.2.
10+
it is deprecated
1111

1212
4.1.0
1313
-----

src/Symfony/Bundle/SecurityBundle/CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ CHANGELOG
66

77
* Using the `security.authentication.trust_resolver.anonymous_class` and
88
`security.authentication.trust_resolver.rememberme_class` parameters to define
9-
the token classes is deprecated. To use
10-
custom tokens extend the existing `Symfony\Component\Security\Core\Authentication\Token\AnonymousToken`
9+
the token classes is deprecated. To use custom tokens extend the existing
10+
`Symfony\Component\Security\Core\Authentication\Token\AnonymousToken`.
1111
or `Symfony\Component\Security\Core\Authentication\Token\RememberMeToken`.
1212
* Added `Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass`
1313
* Added `json_login_ldap` authentication provider to use LDAP authentication with a REST API.
14+
* Made remember-me cookies inherit their default config from `framework.session.cookie_*`
15+
and added an "auto" mode to their "secure" config option to make them secure on HTTPS automatically.
1416

1517
4.1.0
1618
-----

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DependencyInjection\ChildDefinition;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Reference;
18+
use Symfony\Component\HttpFoundation\Cookie;
1819

1920
class RememberMeFactory implements SecurityFactoryInterface
2021
{
@@ -140,7 +141,11 @@ public function addConfiguration(NodeDefinition $node)
140141
;
141142

142143
foreach ($this->options as $name => $value) {
143-
if (\is_bool($value)) {
144+
if ('secure' === $name) {
145+
$builder->enumNode($name)->values(array(true, false, 'auto'))->defaultValue('auto' === $value ? null : $value);
146+
} elseif ('samesite' === $name) {
147+
$builder->enumNode($name)->values(array(null, Cookie::SAMESITE_LAX, Cookie::SAMESITE_STRICT))->defaultValue($value);
148+
} elseif (\is_bool($value)) {
144149
$builder->booleanNode($name)->defaultValue($value);
145150
} else {
146151
$builder->scalarNode($name)->defaultValue($value);

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection;
1313

14+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
1415
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
1516
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
1617
use Symfony\Bundle\SecurityBundle\SecurityUserValueResolver;
@@ -22,6 +23,7 @@
2223
use Symfony\Component\DependencyInjection\ChildDefinition;
2324
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
2425
use Symfony\Component\DependencyInjection\ContainerBuilder;
26+
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
2527
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
2628
use Symfony\Component\DependencyInjection\Parameter;
2729
use Symfony\Component\DependencyInjection\Reference;
@@ -37,7 +39,7 @@
3739
* @author Fabien Potencier <fabien@symfony.com>
3840
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
3941
*/
40-
class SecurityExtension extends Extension
42+
class SecurityExtension extends Extension implements PrependExtensionInterface
4143
{
4244
private $requestMatchers = array();
4345
private $expressions = array();
@@ -54,6 +56,32 @@ public function __construct()
5456
}
5557
}
5658

59+
public function prepend(ContainerBuilder $container)
60+
{
61+
$rememberMeSecureDefault = false;
62+
$rememberMeSameSiteDefault = null;
63+
64+
if (!isset($container->getExtensions()['framework'])) {
65+
return;
66+
}
67+
foreach ($container->getExtensionConfig('framework') as $config) {
68+
if (isset($config['session'])) {
69+
$rememberMeSecureDefault = $config['session']['cookie_secure'] ?? $rememberMeSecureDefault;
70+
$rememberMeSameSiteDefault = array_key_exists('cookie_samesite', $config['session']) ? $config['session']['cookie_samesite'] : $rememberMeSameSiteDefault;
71+
}
72+
}
73+
foreach ($this->listenerPositions as $position) {
74+
foreach ($this->factories[$position] as $factory) {
75+
if ($factory instanceof RememberMeFactory) {
76+
\Closure::bind(function () use ($rememberMeSecureDefault, $rememberMeSameSiteDefault) {
77+
$this->options['secure'] = $rememberMeSecureDefault;
78+
$this->options['samesite'] = $rememberMeSameSiteDefault;
79+
}, $factory, $factory)();
80+
}
81+
}
82+
}
83+
}
84+
5785
public function load(array $configs, ContainerBuilder $container)
5886
{
5987
if (!array_filter($configs)) {

src/Symfony/Bundle/SecurityBundle/Tests/Functional/LogoutTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public function testSessionLessRememberMeLogout()
2626
$cookieJar->expire(session_name());
2727

2828
$this->assertNotNull($cookieJar->get('REMEMBERME'));
29+
$this->assertSame('lax', $cookieJar->get('REMEMBERME')->getSameSite());
2930

3031
$client->request('GET', '/logout');
3132

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/RememberMeLogout/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
imports:
22
- { resource: ./../config/framework.yml }
33

4+
framework:
5+
session:
6+
cookie_secure: auto
7+
cookie_samesite: lax
8+
49
security:
510
encoders:
611
Symfony\Component\Security\Core\User\User: plaintext

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
},
2929
"require-dev": {
3030
"symfony/asset": "~3.4|~4.0",
31-
"symfony/browser-kit": "~3.4|~4.0",
31+
"symfony/browser-kit": "~4.2",
3232
"symfony/console": "~3.4|~4.0",
3333
"symfony/css-selector": "~3.4|~4.0",
3434
"symfony/dom-crawler": "~3.4|~4.0",
@@ -48,6 +48,7 @@
4848
"twig/twig": "~1.34|~2.4"
4949
},
5050
"conflict": {
51+
"symfony/browser-kit": "<4.2",
5152
"symfony/var-dumper": "<3.4",
5253
"symfony/event-dispatcher": "<3.4",
5354
"symfony/framework-bundle": "<4.2",

src/Symfony/Component/BrowserKit/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ CHANGELOG
55
-----
66

77
* The method `Client::submit()` will have a new `$serverParameters` argument
8-
in version 5.0, not defining it is deprecated since version 4.2
8+
in version 5.0, not defining it is deprecated
9+
* Added ability to read the "samesite" attribute of cookies using `Cookie::getSameSite()`
910

1011
3.4.0
1112
-----

src/Symfony/Component/BrowserKit/Cookie.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Cookie
4040
protected $secure;
4141
protected $httponly;
4242
protected $rawValue;
43+
private $samesite;
4344

4445
/**
4546
* Sets a cookie.
@@ -52,8 +53,9 @@ class Cookie
5253
* @param bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client
5354
* @param bool $httponly The cookie httponly flag
5455
* @param bool $encodedValue Whether the value is encoded or not
56+
* @param string|null $samesite The cookie samesite attribute
5557
*/
56-
public function __construct(string $name, ?string $value, string $expires = null, string $path = null, string $domain = '', bool $secure = false, bool $httponly = true, bool $encodedValue = false)
58+
public function __construct(string $name, ?string $value, string $expires = null, string $path = null, string $domain = '', bool $secure = false, bool $httponly = true, bool $encodedValue = false, string $samesite = null)
5759
{
5860
if ($encodedValue) {
5961
$this->value = urldecode($value);
@@ -67,6 +69,7 @@ public function __construct(string $name, ?string $value, string $expires = null
6769
$this->domain = $domain;
6870
$this->secure = $secure;
6971
$this->httponly = $httponly;
72+
$this->samesite = $samesite;
7073

7174
if (null !== $expires) {
7275
$timestampAsDateTime = \DateTime::createFromFormat('U', $expires);
@@ -106,6 +109,10 @@ public function __toString()
106109
$cookie .= '; httponly';
107110
}
108111

112+
if (null !== $this->samesite) {
113+
$str .= '; samesite='.$this->samesite;
114+
}
115+
109116
return $cookie;
110117
}
111118

@@ -138,6 +145,7 @@ public static function fromString($cookie, $url = null)
138145
'secure' => false,
139146
'httponly' => false,
140147
'passedRawValue' => true,
148+
'samesite' => null,
141149
);
142150

143151
if (null !== $url) {
@@ -186,7 +194,8 @@ public static function fromString($cookie, $url = null)
186194
$values['domain'],
187195
$values['secure'],
188196
$values['httponly'],
189-
$values['passedRawValue']
197+
$values['passedRawValue'],
198+
$values['samesite']
190199
);
191200
}
192201

@@ -298,4 +307,14 @@ public function isExpired()
298307
{
299308
return null !== $this->expires && 0 != $this->expires && $this->expires < time();
300309
}
310+
311+
/**
312+
* Gets the samesite attribute of the cookie.
313+
*
314+
* @return string|null The cookie samesite attribute
315+
*/
316+
public function getSameSite(): ?string
317+
{
318+
return $this->samesite;
319+
}
301320
}

src/Symfony/Component/BrowserKit/Tests/CookieTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,13 @@ public function testConstructException()
202202
{
203203
$cookie = new Cookie('foo', 'bar', 'string');
204204
}
205+
206+
public function testSameSite()
207+
{
208+
$cookie = new Cookie('foo', 'bar');
209+
$this->assertNull($cookie->getSameSite());
210+
211+
$cookie = new Cookie('foo', 'bar', 0, '/', 'foo.com', false, true, false, 'lax');
212+
$this->assertSame('lax', $cookie->getSameSite());
213+
}
205214
}

0 commit comments

Comments
 (0)