123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- <?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 OCP\ISession;
- use OCP\Security\ICrypto;
- use OCP\Session\Exceptions\SessionNotAvailableException;
- use function json_decode;
- use function OCP\Log\logger;
- /**
- * Class CryptoSessionData
- *
- * @package OC\Session
- * @template-implements \ArrayAccess<string,mixed>
- */
- class CryptoSessionData implements \ArrayAccess, ISession {
- /** @var ISession */
- protected $session;
- /** @var \OCP\Security\ICrypto */
- protected $crypto;
- /** @var string */
- protected $passphrase;
- /** @var array */
- protected $sessionValues;
- /** @var bool */
- protected $isModified = false;
- public const encryptedSessionName = 'encrypted_session_data';
- /**
- * @param ISession $session
- * @param ICrypto $crypto
- * @param string $passphrase
- */
- public function __construct(ISession $session,
- ICrypto $crypto,
- string $passphrase) {
- $this->crypto = $crypto;
- $this->session = $session;
- $this->passphrase = $passphrase;
- $this->initializeSession();
- }
- /**
- * Close session if class gets destructed
- */
- public function __destruct() {
- try {
- $this->close();
- } catch (SessionNotAvailableException $e) {
- // This exception can occur if session is already closed
- // So it is safe to ignore it and let the garbage collector to proceed
- }
- }
- protected function initializeSession() {
- $encryptedSessionData = $this->session->get(self::encryptedSessionName) ?: '';
- if ($encryptedSessionData === '') {
- // Nothing to decrypt
- $this->sessionValues = [];
- } else {
- try {
- $this->sessionValues = json_decode(
- $this->crypto->decrypt($encryptedSessionData, $this->passphrase),
- true,
- 512,
- JSON_THROW_ON_ERROR,
- );
- } catch (\Exception $e) {
- logger('core')->critical('Could not decrypt or decode encrypted session data', [
- 'exception' => $e,
- ]);
- $this->sessionValues = [];
- $this->regenerateId(true, false);
- }
- }
- }
- /**
- * Set a value in the session
- *
- * @param string $key
- * @param mixed $value
- */
- public function set(string $key, $value) {
- if ($this->get($key) === $value) {
- // Do not write the session if the value hasn't changed to avoid reopening
- return;
- }
- $reopened = $this->reopen();
- $this->sessionValues[$key] = $value;
- $this->isModified = true;
- if ($reopened) {
- $this->close();
- }
- }
- /**
- * Get a value from the session
- *
- * @param string $key
- * @return string|null Either the value or null
- */
- public function get(string $key) {
- if (isset($this->sessionValues[$key])) {
- return $this->sessionValues[$key];
- }
- return null;
- }
- /**
- * Check if a named key exists in the session
- *
- * @param string $key
- * @return bool
- */
- public function exists(string $key): bool {
- return isset($this->sessionValues[$key]);
- }
- /**
- * Remove a $key/$value pair from the session
- *
- * @param string $key
- */
- public function remove(string $key) {
- $reopened = $this->reopen();
- $this->isModified = true;
- unset($this->sessionValues[$key]);
- if ($reopened) {
- $this->close();
- }
- }
- /**
- * Reset and recreate the session
- */
- public function clear() {
- $reopened = $this->reopen();
- $requesttoken = $this->get('requesttoken');
- $this->sessionValues = [];
- if ($requesttoken !== null) {
- $this->set('requesttoken', $requesttoken);
- }
- $this->isModified = true;
- $this->session->clear();
- if ($reopened) {
- $this->close();
- }
- }
- public function reopen(): bool {
- $reopened = $this->session->reopen();
- if ($reopened) {
- $this->initializeSession();
- }
- return $reopened;
- }
- /**
- * Wrapper around session_regenerate_id
- *
- * @param bool $deleteOldSession Whether to delete the old associated session file or not.
- * @param bool $updateToken Wheater to update the associated auth token
- * @return void
- */
- public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {
- $this->session->regenerateId($deleteOldSession, $updateToken);
- }
- /**
- * Wrapper around session_id
- *
- * @return string
- * @throws SessionNotAvailableException
- * @since 9.1.0
- */
- public function getId(): string {
- return $this->session->getId();
- }
- /**
- * Close the session and release the lock, also writes all changed data in batch
- */
- public function close() {
- if ($this->isModified) {
- $encryptedValue = $this->crypto->encrypt(json_encode($this->sessionValues), $this->passphrase);
- $this->session->set(self::encryptedSessionName, $encryptedValue);
- $this->isModified = false;
- }
- $this->session->close();
- }
- /**
- * @param mixed $offset
- * @return bool
- */
- public function offsetExists($offset): bool {
- return $this->exists($offset);
- }
- /**
- * @param mixed $offset
- * @return mixed
- */
- #[\ReturnTypeWillChange]
- public function offsetGet($offset) {
- return $this->get($offset);
- }
- /**
- * @param mixed $offset
- * @param mixed $value
- */
- public function offsetSet($offset, $value): void {
- $this->set($offset, $value);
- }
- /**
- * @param mixed $offset
- */
- public function offsetUnset($offset): void {
- $this->remove($offset);
- }
- }
|