silverstripe-framework/src/Security/MemberAuthenticator/SessionAuthenticationHandler.php
Ingo Schommer 93b0884e19 BUG Lazy session state (fixes #8267)
Fixes regression from 3.x, where sessions where lazy started as required:
Either because an existing session identifier was sent through with the request,
or because new session data needed to be persisted as part of the request execution.

Without this lazy starting, *every* request will get a session,
which makes all those responses uncacheable by HTTP layers.

Note that 4.x also changed the $data vs. $changedData payloads:
In 3.x, they both contained key/value pairs.
In 4.x, $data contains key/value, while $changedData contains key/boolean to declare isChanged.
While this reduces duplication in the class, it also surfaced a bug which was latent in 3.x:
When an existing session is lazily resumed via start(), $data is set back to an empty array.
In 3.x, any changed data before this point was *also* retained in $changedData,
ensuring it gets merged into existing $_SESSION data.
In 4.x, this clears out data - hence the need for a more complex merge logic.

Since isset($this->data) is no longer an accurate indicator of a started session,
we introduce a separate $this->started flag.

Note that I've chosen not to make lazy an opt-in (e.g. via start($request, $lazy=false)).
We already have a distinction between lazy starting via init(), and force starting via start().
2018-07-19 13:32:04 +12:00

117 lines
3.1 KiB
PHP

<?php
namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Cookie;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Security\AuthenticationHandler;
use SilverStripe\Security\Member;
/**
* Authenticate a member pased on a session cookie
*/
class SessionAuthenticationHandler implements AuthenticationHandler
{
/**
* @var string
*/
private $sessionVariable;
/**
* Get the session variable name used to track member ID
*
* @return string
*/
public function getSessionVariable()
{
return $this->sessionVariable;
}
/**
* Set the session variable name used to track member ID
*
* @param string $sessionVariable
*/
public function setSessionVariable($sessionVariable)
{
$this->sessionVariable = $sessionVariable;
}
/**
* @param HTTPRequest $request
* @return Member
*/
public function authenticateRequest(HTTPRequest $request)
{
$session = $request->getSession();
// Sessions are only started when a session cookie is detected
if (!$session->isStarted()) {
return null;
}
// If ID is a bad ID it will be treated as if the user is not logged in, rather than throwing a
// ValidationException
$id = $session->get($this->getSessionVariable());
if (!$id) {
return null;
}
/** @var Member $member */
$member = Member::get()->byID($id);
return $member;
}
/**
* @param Member $member
* @param bool $persistent
* @param HTTPRequest $request
*/
public function logIn(Member $member, $persistent = false, HTTPRequest $request = null)
{
static::regenerateSessionId();
$request = $request ?: Controller::curr()->getRequest();
$request->getSession()->set($this->getSessionVariable(), $member->ID);
// This lets apache rules detect whether the user has logged in
// @todo make this a setting on the authentication handler
if (Member::config()->get('login_marker_cookie')) {
Cookie::set(Member::config()->get('login_marker_cookie'), 1, 0);
}
}
/**
* Regenerate the session_id.
*/
protected static function regenerateSessionId()
{
if (!Member::config()->get('session_regenerate_id')) {
return;
}
// This can be called via CLI during testing.
if (Director::is_cli()) {
return;
}
$file = '';
$line = '';
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
// There's nothing we can do about this, because it's an operating system function!
if (!headers_sent($file, $line)) {
@session_regenerate_id(true);
}
}
/**
* @param HTTPRequest $request
*/
public function logOut(HTTPRequest $request = null)
{
$request = $request ?: Controller::curr()->getRequest();
$request->getSession()->restart($request);
}
}