-
Notifications
You must be signed in to change notification settings - Fork 824
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEW Allow setting "samesite" attribute for individual cookies. #11632
base: 6.0
Are you sure you want to change the base?
NEW Allow setting "samesite" attribute for individual cookies. #11632
Conversation
private bool $tokenCookieSecure = true; | ||
|
||
/** | ||
* @var boolean | ||
* The SameSite value to use for authentication cookies. | ||
* If set to an empty string, the default value from Cookie.default_samesite will be used. | ||
*/ | ||
private $tokenCookieSecure = false; | ||
private string $tokenCookieSameSite = Cookie::SAMESITE_STRICT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In https://docs.silverstripe.org/en/6/developer_guides/security/secure_coding/#secure-sessions-and-cookies when talking about updating the session cookie security it says
The same treatment should be applied to the cookie responsible for remembering logins across sessions
For this reason I've chosen to do exactly that - I've updated its default secure and samesite values to match the session cookie values.
public function getTokenCookieSecure() | ||
public function getTokenCookieSecure(): bool | ||
{ | ||
if ($this->getTokenCookieSameSite() === Cookie::SAMESITE_NONE) { | ||
return true; | ||
} | ||
return $this->tokenCookieSecure; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matches how "secure" is set for session cookies
public const SAMESITE_LAX = 'Lax'; | ||
|
||
public const SAMESITE_STRICT = 'Strict'; | ||
|
||
public const SAMESITE_LAX = 'Lax'; | ||
|
||
public const SAMESITE_NONE = 'None'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the order so it's most to least secure
/** | ||
* @config | ||
* | ||
* @var bool | ||
*/ | ||
private static $report_errors = true; | ||
private static bool $report_errors = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A fair amount of unrelated strict typing being added in this PR
string $name, | ||
string|false $value, | ||
int $expiry = 90, | ||
?string $path = null, | ||
?string $domain = null, | ||
bool $secure = false, | ||
bool $httpOnly = true, | ||
string $sameSite = '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$value
can only be a string orfalse
if you trace through the calls in the actual implementation.$expiry
can only beint
.- Chose to keep the nullability of
$path
and$domain
to reduce the change of unexpected side effects and regressions given we're so close to the beta.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These types are shared with CookieJar
and Cookie_Backend
as well.
src/Control/Cookie.php
Outdated
public static function force_expiry( | ||
string $name, | ||
?string $path = null, | ||
?string $domain = null, | ||
bool $secure = false, | ||
bool $httpOnly = true, | ||
string $sameSite = '' | ||
): void { | ||
Cookie::get_inst()->forceExpiry($name, $path, $domain, $secure, $httpOnly, $sameSite); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to https://www.php.net/manual/en/function.setcookie.php#refsect1-function.setcookie-notes
Cookies must be deleted with the same parameters as they were set with.
So it seems like we need to allow setting samesite here.
* When creating the backend we want to store the existing cookies in our | ||
* "existing" array. This allows us to distinguish between cookies we received | ||
* or we set ourselves (and didn't get from the browser) | ||
* | ||
* @param array $cookies The existing cookies to load into the cookie jar. | ||
* Omit this to default to $_COOKIE | ||
* @inheritDoc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed a lot of duplicate PHPDoc from this class
src/Control/Session.php
Outdated
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_LAX; | ||
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_STRICT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and any other changes from lax to strict in this class are necessary as part of #11597
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should move this logic to a new method Session::getCookieSamesite()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's only used in one place so that seems excessive - but done anyway. Private method so we can just axe it later if we want.
$path = $this->config()->get('cookie_path') ?: Director::baseURL(); | ||
$domain = $this->config()->get('cookie_domain'); | ||
$secure = Director::is_https($request) && $this->config()->get('cookie_secure'); | ||
Cookie::force_expiry(session_name(), $path, $domain, $secure, true); | ||
$cookieParams = $this->buildCookieParams($request); | ||
Cookie::force_expiry( | ||
session_name(), | ||
$cookieParams['path'], | ||
$cookieParams['domain'], | ||
$cookieParams['secure'], | ||
true, | ||
$cookieParams['samesite'] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No sense rebuilding the params - updated this to use buildCookieParams()
@@ -220,6 +220,7 @@ class Member extends DataObject | |||
* @config | |||
* @var String If this is set, then a session cookie with the given name will be set on log-in, | |||
* and cleared on logout. | |||
* This cookie shares the same SameSite and Secure parameters as the main session cookie. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See SessionAuthenticationHandler
Based on the existing tests it doesn't look like there's a sensible way to test this new parameter is actually coming through when cookies get set. |
bc01ee8
to
9ad275d
Compare
* @param string $path The path to save the cookie on (falls back to site base) | ||
* @param string $domain The domain to make the cookie available on | ||
* @param boolean $secure Can the cookie only be sent over SSL? | ||
* @param boolean $httpOnly Prevent the cookie being accessible by JS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be deleting docblock params when they're explaining something - e.g. $expiry and $path are non obvious.
This applies other instances of deleting docblock params in this PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the @inheritdoc
immediately below the text you selected. These are duplicate PHPDocs as mentioned in #11632 (comment)
src/Control/CookieJar.php
Outdated
string $sameSite = '' | ||
): void { | ||
if ($sameSite === '') { | ||
$sameSite = Cookie::config()->get('default_samesite') ?? Cookie::SAMESITE_LAX; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should logic should be on a new method Cookie::getDefaultSameSite()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/Control/CookieJar.php
Outdated
string $sameSite = '' | ||
): bool { | ||
if ($sameSite === '') { | ||
$sameSite = Cookie::config()->get('default_samesite') ?? Cookie::SAMESITE_LAX; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should logic should be on a new method Cookie::getDefaultSameSite()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/Control/Session.php
Outdated
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_LAX; | ||
$sameSite = static::config()->get('cookie_samesite') ?? Cookie::SAMESITE_STRICT; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should move this logic to a new method Session::getCookieSamesite()
{ | ||
return $this->deviceCookieName; | ||
return $this->deviceCookieName ?? ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $this->deviceCookieName ?? ''; | |
return $this->deviceCookieName; |
Won't PHP complain about trying to access an un-initilised value?
Seems like it would be better to just give the property a default value of blank string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it only does that for properties which are explicitly typed. But I guess I can make it explicitly string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
{ | ||
return $this->tokenCookieName; | ||
return $this->tokenCookieName ?? ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return $this->tokenCookieName ?? ''; | |
return $this->tokenCookieName; |
Same as getDeviceCookieName() above
src/Control/Cookie_Backend.php
Outdated
* @param ?string $path The path to save the cookie on (falls back to site base) | ||
* @param ?string $domain The domain to make the cookie available on |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @param ?string $path The path to save the cookie on (falls back to site base) | |
* @param ?string $domain The domain to make the cookie available on | |
* @param null|string $path The path to save the cookie on (falls back to site base) | |
* @param null|string $domain The domain to make the cookie available on |
Don't think I've seen ?
in a docblock param before, should be consistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
9ad275d
to
f200906
Compare
CI failures are pre-existing and will be resolved in #11635 |
Also update secure-ness of authentication cookies to match session cookies, and finish securing session cookies.
f200906
to
33a6a13
Compare
Also update secure-ness of authentication cookies to match session cookies, and finish securing session cookies.
Issue