Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public function run(): JobResult
$result = new JobResult();
$result->setStatus(JobResult::STATUS_OK);

$num_destroyed_sessions = ilSession::_destroyExpiredSessions();
$num_destroyed_sessions = ilSession::destroyExpiredSessions();
ilSessionStatistics::aggregateRaw(time());
$result->setMessage('Number of destroyed sessions: ' . $num_destroyed_sessions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public function resetStatus(): void

public function migrateAccount(ilAuthSession $session): bool
{
if (!$session->isAuthenticated()) {
if (!$session->isFullyAuthenticated()) {
$this->logger->warning('Desired user account is not authenticated');
return false;
}
Expand Down Expand Up @@ -285,7 +285,7 @@ protected function handleAuthenticationSuccess(ilAuthProviderInterface $provider
$user->update();

$this->logger->info('Successfully authenticated: ' . ilObjUser::_lookupLogin($this->getStatus()->getAuthenticatedUserId()));
$this->getAuthSession()->setAuthenticated(true, $this->getStatus()->getAuthenticatedUserId());
$this->getAuthSession()->onLoginSuccess($this->getStatus()->getAuthenticatedUserId());

ilInitialisation::initUserAccount();

Expand Down
134 changes: 107 additions & 27 deletions components/ILIAS/Authentication/classes/class.ilAuthSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

declare(strict_types=1);

use ILIAS\Authentication\Domain\SessionRotationPolicy;
use ILIAS\Authentication\Domain\SubmittedSessionId;

class ilAuthSession
{
private const string SESSION_AUTH_AUTHENTICATED = '_authsession_authenticated';
Expand All @@ -38,12 +41,9 @@ private function __construct(ilLogger $logger)
$this->logger = $logger;
}

public static function getInstance(ilLogger $logger): ilAuthSession
public static function getInstance(ilLogger $logger): self
{
if (self::$instance) {
return self::$instance;
}
return self::$instance = new self($logger);
return self::$instance ??= new self($logger);
}

protected function getLogger(): ilLogger
Expand All @@ -54,7 +54,7 @@ protected function getLogger(): ilLogger
/**
* Start auth session
*/
public function init(): bool
public function init(SubmittedSessionId $submitted_session_id): bool
{
if (session_status() === PHP_SESSION_ACTIVE) {
$this->getLogger()->error(__METHOD__ . ' called with active session.');
Expand All @@ -65,9 +65,9 @@ public function init(): bool
session_start();

$this->setId(session_id());
$this->rejectForeignSessionIdIfUnknown($submitted_session_id);

$user_id = (int) (ilSession::get(self::SESSION_AUTH_USER_ID) ?? ANONYMOUS_USER_ID);

if ($user_id) {
$this->getLogger()->debug('Resuming old session for user: ' . $user_id);
$this->setUserId($user_id);
Expand All @@ -81,6 +81,7 @@ public function init(): bool
$this->expired = false;
$this->authenticated = false;
}

return true;
}

Expand All @@ -92,28 +93,55 @@ public function isValid(): bool
return !$this->isExpired() && $this->isAuthenticated();
}

/**
* Regenerate id
*/
public function regenerateId(): void
{
$old_session_id = session_id();
session_regenerate_id(true);
$this->setId(session_id());
$this->getLogger()->info('Session regenerate id: [' . substr($old_session_id, 0, 5) . '] -> [' . substr($this->getId(), 0, 5) . ']');
$this->applySessionRotation(SessionRotationPolicy::Rotate);
}

/**
* Logout user => stop session
* End the current auth context and establish an anonymous session.
* Callers that must keep the session id use ensureAnonymousContext().
*/
public function logout(): void
{
$this->getLogger()->debug('Logout called for: ' . $this->getUserId());
session_regenerate_id(true);

$this->clearSessionPayload();

session_destroy();
session_start();

$this->setAuthenticated(ANONYMOUS_USER_ID);
$this->applySessionRotation(SessionRotationPolicy::Rotate);
}

/**
* Prepare the login screen: anonymous auth state in the current session (no destroy, no rotate).
*/
public function ensureAnonymousContext(): void
{
$this->getLogger()->debug(
'Ensure anonymous context for: ' . $this->getUserId()
);
$this->establishAnonymousAuthState();
}

$this->init();
$this->setAuthenticated(true, ANONYMOUS_USER_ID);
/**
* Establish an authenticated session after successful login.
*/
public function onLoginSuccess(int $user_id): void
{
$this->setAuthenticated($user_id);
$this->applySessionRotation(SessionRotationPolicy::Rotate);
}

/**
* Transition an expired privileged session to anonymous.
*/
public function onSessionExpired(): void
{
$this->establishAnonymousAuthState();
$this->applySessionRotation(SessionRotationPolicy::Rotate);
}

/**
Expand All @@ -124,19 +152,13 @@ public function isAuthenticated(): bool
return $this->authenticated || $this->user_id === ANONYMOUS_USER_ID;
}

/**
* Set authenticated
*/
public function setAuthenticated(bool $a_status, int $a_user_id): void
private function setAuthenticated(int $a_user_id): void
{
$this->authenticated = $a_status;
$this->authenticated = true;
$this->user_id = $a_user_id;
ilSession::set(self::SESSION_AUTH_AUTHENTICATED, $a_status);
ilSession::set(self::SESSION_AUTH_AUTHENTICATED, true);
ilSession::set(self::SESSION_AUTH_USER_ID, $a_user_id);
$this->setExpired(false);
if ($a_status) {
$this->regenerateId();
}
}

public function isFullyAuthenticated(): bool
Expand Down Expand Up @@ -196,6 +218,7 @@ protected function validateExpiration(): bool
$this->setExpired(true);
return false;
}

return true;
}

Expand All @@ -214,4 +237,61 @@ public function getId(): string
{
return $this->id;
}

/**
* Anonymous auth flags. A switch away from a logged-in account drops that account's session values.
* An already anonymous session keeps its payload, so the login page can preserve its id and messages.
*/
private function establishAnonymousAuthState(): void
{
if ($this->holdsLoggedInAccount()) {
$this->clearSessionPayload();
}

$this->setAuthenticated(ANONYMOUS_USER_ID);
}

private function holdsLoggedInAccount(): bool
{
return $this->user_id > 0 && $this->user_id !== ANONYMOUS_USER_ID;
}

private function clearSessionPayload(): void
{
ilSession::purge();
}

private function rejectForeignSessionIdIfUnknown(SubmittedSessionId $submitted_session_id): void
{
if (defined('IL_PHPUNIT_TEST')) {
return;
}

$submitted_id = $submitted_session_id->get();
if ($submitted_id === '' || $submitted_id !== session_id()) {
return;
}

if (!ilSession::exists($submitted_id)) {
$this->applySessionRotation(SessionRotationPolicy::RejectForeign);
}
}

private function applySessionRotation(SessionRotationPolicy $policy): void
{
$old_session_id = session_id();

session_regenerate_id(
$policy === SessionRotationPolicy::Rotate
);

$this->setId(session_id());

$label = $policy === SessionRotationPolicy::RejectForeign
? 'Session reject foreign id'
: 'Session regenerate id';
$this->getLogger()->info(
$label . ': [' . substr($old_session_id, 0, 5) . '] -> [' . substr($this->getId(), 0, 5) . ']'
);
}
}
53 changes: 42 additions & 11 deletions components/ILIAS/Authentication/classes/class.ilSession.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ilSession
* @param string session id
* @return string session data
*/
public static function _getData(string $a_session_id): string
public static function getData(string $a_session_id): string
{
if (!$a_session_id) {
// fix for php #70520
Expand Down Expand Up @@ -89,7 +89,7 @@ public static function lookupExpireTime(string $a_session_id): int
return 0;
}

public static function _writeData(string $a_session_id, string $a_data): bool
public static function writeData(string $a_session_id, string $a_data): bool
{
global $DIC;

Expand Down Expand Up @@ -123,7 +123,7 @@ public static function _writeData(string $a_session_id, string $a_data): bool
$fields['remote_addr'] = [ilDBConstants::T_TEXT, $_SERVER['REMOTE_ADDR'] ?? ''];
}

if (self::_exists($a_session_id)) {
if (self::exists($a_session_id)) {
// note that we do this only when inserting the new record
// updating may get us other contexts for the same session, especially ilContextWAC, which we do not want
if (class_exists('ilContext') && ilContext::isSessionMainContext()) {
Expand Down Expand Up @@ -194,15 +194,15 @@ public static function _writeData(string $a_session_id, string $a_data): bool
$r = new \Random\Randomizer();
if ($r->getInt(0, 50) === 2) {
// get time _before_ destroying expired sessions
self::_destroyExpiredSessions();
self::destroyExpiredSessions();
ilSessionStatistics::aggregateRaw($now);
}
}

return true;
}

public static function _exists(string $a_session_id): bool
public static function exists(string $a_session_id): bool
{
if (!$a_session_id) {
return false;
Expand All @@ -224,7 +224,7 @@ public static function _exists(string $a_session_id): bool
* @param int|null $a_closing_context closing context
* @param int|bool $a_expired_at expired at timestamp
*/
public static function _destroy($a_session_id, ?int $a_closing_context = null, $a_expired_at = null): bool
public static function destroy($a_session_id, ?int $a_closing_context = null, $a_expired_at = null): bool
{
global $DIC;

Expand Down Expand Up @@ -283,7 +283,7 @@ public static function _destroy($a_session_id, ?int $a_closing_context = null, $
*
* @param int user id
*/
public static function _destroyByUserId(int $a_user_id): bool
public static function destroyByUserId(int $a_user_id): bool
{
global $DIC;

Expand All @@ -300,7 +300,7 @@ public static function _destroyByUserId(int $a_user_id): bool
* Destroy expired sessions
* @return int The number of deleted sessions on success
*/
public static function _destroyExpiredSessions(): int
public static function destroyExpiredSessions(): int
{
global $DIC;

Expand All @@ -313,7 +313,7 @@ public static function _destroyExpiredSessions(): int
$ids[$row['session_id']] = (int) $row['expires'];
}
if ($ids !== []) {
self::_destroy($ids, self::SESSION_CLOSE_EXPIRE, true);
self::destroy($ids, self::SESSION_CLOSE_EXPIRE, true);
}

return count($ids);
Expand All @@ -325,7 +325,7 @@ public static function _destroyExpiredSessions(): int
* @param string session id
* @return string new session id
*/
public static function _duplicate(string $a_session_id): string
public static function duplicate(string $a_session_id): string
{
global $DIC;

Expand All @@ -345,7 +345,7 @@ public static function _duplicate(string $a_session_id): string
$res = $ilDB->query($query);

if ($row = $ilDB->fetchObject($res)) {
self::_writeData($new_session, $row->data);
self::writeData($new_session, $row->data);
return $new_session;
}
//TODO check if throwing an excpetion might be a better choice
Expand All @@ -360,6 +360,32 @@ public static function getExpireValue(): int
return time() + self::getIdleValue();
}

/**
* Move the idle deadline of an existing session without rewriting its payload.
* Used when PHP calls updateTimestamp because the session data did not change.
*/
public static function updateTimestamp(string $session_id): bool
{
if ($session_id === '' || self::isWebAccessWithoutSessionEnabled()) {
return true;
}

global $DIC;

$affected_rows = $DIC['ilDB']->update(
'usr_session',
[
'expires' => [ilDBConstants::T_INTEGER, self::getExpireValue()],
'ctime' => [ilDBConstants::T_INTEGER, time()],
],
[
'session_id' => [ilDBConstants::T_TEXT, $session_id],
]
);

return $affected_rows > 0;
}

/**
* Returns the idle time in seconds
*/
Expand Down Expand Up @@ -408,6 +434,11 @@ public static function clear(string $a_var): void
}
}

public static function purge(): void
{
$_SESSION = [];
}

/**
* The names of all variables currently held in the session.
*
Expand Down
Loading
Loading