Skip to content

Commit c48fbb4

Browse files
[SecurityBundle] make remember-me cookies auto-secure + inherit their default config from framework.session.cookie_*
1 parent 2879baf commit c48fbb4

File tree

10 files changed

+71
-11
lines changed

10 files changed

+71
-11
lines changed

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

+6-1
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

+29-1
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 = $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

+1
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

+5
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

+2-1
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/Cookie.php

+21-2
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/HttpKernel/DataCollector/RequestDataCollector.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public function collect(Request $request, Response $response, \Exception $except
152152
if ($request->attributes->has('_redirected') && $redirectCookie = $request->cookies->get('sf_redirect')) {
153153
$this->data['redirect'] = json_decode($redirectCookie, true);
154154

155-
$response->headers->clearCookie('sf_redirect');
155+
$response->headers->clearCookie('sf_redirect', '/', null, $request->isSecure());
156156
}
157157

158158
if ($response->isRedirect()) {
@@ -165,7 +165,8 @@ public function collect(Request $request, Response $response, \Exception $except
165165
'controller' => $this->parseController($request->attributes->get('_controller')),
166166
'status_code' => $statusCode,
167167
'status_text' => Response::$statusTexts[(int) $statusCode],
168-
))
168+
)),
169+
0, '/', null, $request->isSecure()
169170
));
170171
}
171172

src/Symfony/Component/Security/Http/RememberMe/AbstractRememberMeServices.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ protected function cancelCookie(Request $request)
271271
$this->logger->debug('Clearing remember-me cookie.', array('name' => $this->options['name']));
272272
}
273273

274-
$request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'], $this->options['secure'], $this->options['httponly'], false, $this->options['samesite'] ?? null));
274+
$request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'], $this->options['secure'] ?? $request->isSecure(), $this->options['httponly'], false, $this->options['samesite'] ?? null));
275275
}
276276

277277
/**

src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ protected function processAutoLoginCookie(array $cookieParts, Request $request)
8383
time() + $this->options['lifetime'],
8484
$this->options['path'],
8585
$this->options['domain'],
86-
$this->options['secure'],
86+
$this->options['secure'] ?? $request->isSecure(),
8787
$this->options['httponly'],
8888
false,
8989
$this->options['samesite'] ?? null
@@ -118,7 +118,7 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt
118118
time() + $this->options['lifetime'],
119119
$this->options['path'],
120120
$this->options['domain'],
121-
$this->options['secure'],
121+
$this->options['secure'] ?? $request->isSecure(),
122122
$this->options['httponly'],
123123
false,
124124
$this->options['samesite'] ?? null

src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ protected function onLoginSuccess(Request $request, Response $response, TokenInt
8080
$expires,
8181
$this->options['path'],
8282
$this->options['domain'],
83-
$this->options['secure'],
83+
$this->options['secure'] ?? $request->isSecure(),
8484
$this->options['httponly'],
8585
false,
8686
$this->options['samesite'] ?? null

0 commit comments

Comments
 (0)