mirror of
https://github.com/nextcloud/server.git
synced 2024-12-29 00:18:42 +00:00
9100b8757e
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
232 lines
5.2 KiB
PHP
232 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
namespace OC\Session;
|
|
|
|
use OC\Authentication\Token\IProvider;
|
|
use OCP\Authentication\Exceptions\InvalidTokenException;
|
|
use OCP\ILogger;
|
|
use OCP\Session\Exceptions\SessionNotAvailableException;
|
|
use Psr\Log\LoggerInterface;
|
|
use function call_user_func_array;
|
|
use function microtime;
|
|
|
|
/**
|
|
* Class Internal
|
|
*
|
|
* wrap php's internal session handling into the Session interface
|
|
*
|
|
* @package OC\Session
|
|
*/
|
|
class Internal extends Session {
|
|
/**
|
|
* @param string $name
|
|
* @throws \Exception
|
|
*/
|
|
public function __construct(
|
|
string $name,
|
|
private ?LoggerInterface $logger,
|
|
) {
|
|
set_error_handler([$this, 'trapError']);
|
|
$this->invoke('session_name', [$name]);
|
|
$this->invoke('session_cache_limiter', ['']);
|
|
try {
|
|
$this->startSession();
|
|
} catch (\Exception $e) {
|
|
setcookie($this->invoke('session_name'), '', -1, \OC::$WEBROOT ?: '/');
|
|
}
|
|
restore_error_handler();
|
|
if (!isset($_SESSION)) {
|
|
throw new \Exception('Failed to start session');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
* @param integer $value
|
|
*/
|
|
public function set(string $key, $value) {
|
|
$reopened = $this->reopen();
|
|
$_SESSION[$key] = $value;
|
|
if ($reopened) {
|
|
$this->close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
* @return mixed
|
|
*/
|
|
public function get(string $key) {
|
|
if (!$this->exists($key)) {
|
|
return null;
|
|
}
|
|
return $_SESSION[$key];
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
* @return bool
|
|
*/
|
|
public function exists(string $key): bool {
|
|
return isset($_SESSION[$key]);
|
|
}
|
|
|
|
/**
|
|
* @param string $key
|
|
*/
|
|
public function remove(string $key) {
|
|
if (isset($_SESSION[$key])) {
|
|
unset($_SESSION[$key]);
|
|
}
|
|
}
|
|
|
|
public function clear() {
|
|
$this->reopen();
|
|
$this->invoke('session_unset');
|
|
$this->regenerateId();
|
|
$this->invoke('session_write_close');
|
|
$this->startSession(true);
|
|
$_SESSION = [];
|
|
}
|
|
|
|
public function close() {
|
|
$this->invoke('session_write_close');
|
|
parent::close();
|
|
}
|
|
|
|
/**
|
|
* Wrapper around session_regenerate_id
|
|
*
|
|
* @param bool $deleteOldSession Whether to delete the old associated session file or not.
|
|
* @param bool $updateToken Whether to update the associated auth token
|
|
* @return void
|
|
*/
|
|
public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {
|
|
$this->reopen();
|
|
$oldId = null;
|
|
|
|
if ($updateToken) {
|
|
// Get the old id to update the token
|
|
try {
|
|
$oldId = $this->getId();
|
|
} catch (SessionNotAvailableException $e) {
|
|
// We can't update a token if there is no previous id
|
|
$updateToken = false;
|
|
}
|
|
}
|
|
|
|
try {
|
|
@session_regenerate_id($deleteOldSession);
|
|
} catch (\Error $e) {
|
|
$this->trapError($e->getCode(), $e->getMessage());
|
|
}
|
|
|
|
if ($updateToken) {
|
|
// Get the new id to update the token
|
|
$newId = $this->getId();
|
|
|
|
/** @var IProvider $tokenProvider */
|
|
$tokenProvider = \OCP\Server::get(IProvider::class);
|
|
|
|
try {
|
|
$tokenProvider->renewSessionToken($oldId, $newId);
|
|
} catch (InvalidTokenException $e) {
|
|
// Just ignore
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wrapper around session_id
|
|
*
|
|
* @return string
|
|
* @throws SessionNotAvailableException
|
|
* @since 9.1.0
|
|
*/
|
|
public function getId(): string {
|
|
$id = $this->invoke('session_id', [], true);
|
|
if ($id === '') {
|
|
throw new SessionNotAvailableException();
|
|
}
|
|
return $id;
|
|
}
|
|
|
|
/**
|
|
* @throws \Exception
|
|
*/
|
|
public function reopen(): bool {
|
|
if ($this->sessionClosed) {
|
|
$this->startSession(false, false);
|
|
$this->sessionClosed = false;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param int $errorNumber
|
|
* @param string $errorString
|
|
* @throws \ErrorException
|
|
*/
|
|
public function trapError(int $errorNumber, string $errorString) {
|
|
if ($errorNumber & E_ERROR) {
|
|
throw new \ErrorException($errorString);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $functionName the full session_* function name
|
|
* @param array $parameters
|
|
* @param bool $silence whether to suppress warnings
|
|
* @throws \ErrorException via trapError
|
|
* @return mixed
|
|
*/
|
|
private function invoke(string $functionName, array $parameters = [], bool $silence = false) {
|
|
try {
|
|
$timeBefore = microtime(true);
|
|
if ($silence) {
|
|
$result = @call_user_func_array($functionName, $parameters);
|
|
} else {
|
|
$result = call_user_func_array($functionName, $parameters);
|
|
}
|
|
$timeAfter = microtime(true);
|
|
$timeSpent = $timeAfter - $timeBefore;
|
|
if ($timeSpent > 0.1) {
|
|
$logLevel = match (true) {
|
|
$timeSpent > 25 => ILogger::ERROR,
|
|
$timeSpent > 10 => ILogger::WARN,
|
|
$timeSpent > 0.5 => ILogger::INFO,
|
|
default => ILogger::DEBUG,
|
|
};
|
|
$this->logger?->log(
|
|
$logLevel,
|
|
"Slow session operation $functionName detected",
|
|
[
|
|
'parameters' => $parameters,
|
|
'timeSpent' => $timeSpent,
|
|
],
|
|
);
|
|
}
|
|
return $result;
|
|
} catch (\Error $e) {
|
|
$this->trapError($e->getCode(), $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function startSession(bool $silence = false, bool $readAndClose = true) {
|
|
$sessionParams = ['cookie_samesite' => 'Lax'];
|
|
if (\OC::hasSessionRelaxedExpiry()) {
|
|
$sessionParams['read_and_close'] = $readAndClose;
|
|
}
|
|
$this->invoke('session_start', [$sessionParams], $silence);
|
|
}
|
|
}
|