<?php

/**
 * @defgroup session Session
 * Implements session concerns such as the session manager, session objects, etc.
 */

/**
 * @file classes/session/Session.php
 *
 * Copyright (c) 2014-2021 Simon Fraser University
 * Copyright (c) 2000-2021 John Willinsky
 * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
 *
 * @class Session
 *
 * @ingroup session
 *
 * @see SessionDAO
 *
 * @brief Maintains user state information from one request to the next.
 */

namespace PKP\session;

use APP\facades\Repo;
use PKP\config\Config;
use PKP\user\User;

class Session extends \PKP\core\DataObject
{
    /** @var User User object associated with this session */
    public $user;


    /**
     * Get a session variable's value.
     *
     * @param string $key
     */
    public function getSessionVar($key)
    {
        return $_SESSION[$key] ?? null;
    }

    /**
     * Get a session variable's value.
     *
     * @param string $key
     */
    public function setSessionVar($key, $value)
    {
        $_SESSION[$key] = $value;
        return $value;
    }

    /**
     * Unset (delete) a session variable.
     *
     * @param string $key
     */
    public function unsetSessionVar($key)
    {
        if (isset($_SESSION[$key])) {
            unset($_SESSION[$key]);
        }
    }

    //
    // Get/set methods
    //

    /**
     * Get user ID (0 if anonymous user).
     *
     * @return int
     */
    public function getUserId()
    {
        return $this->getData('userId');
    }

    /**
     * Set user ID.
     *
     * @param ?int $userId
     */
    public function setUserId($userId)
    {
        if (!isset($userId) || empty($userId)) {
            $this->user = null;
            $userId = null;
        } elseif ($userId != $this->getData('userId')) {
            $this->user = Repo::user()->get($userId);
            if (!isset($this->user)) {
                $userId = null;
            }
        }
        $this->setData('userId', $userId);
    }

    /**
     * Get IP address.
     *
     * @return string
     */
    public function getIpAddress()
    {
        return $this->getData('ipAddress');
    }

    /**
     * Set IP address.
     *
     * @param string $ipAddress
     */
    public function setIpAddress($ipAddress)
    {
        $this->setData('ipAddress', $ipAddress);
    }

    /**
     * Get user agent.
     *
     * @return string
     */
    public function getUserAgent()
    {
        return $this->getData('userAgent');
    }

    /**
     * Set user agent.
     *
     * @param string $userAgent
     */
    public function setUserAgent($userAgent)
    {
        $this->setData('userAgent', $userAgent);
    }

    /**
     * Get time (in seconds) since session was created.
     *
     * @return int
     */
    public function getSecondsCreated()
    {
        return $this->getData('created');
    }

    /**
     * Set time (in seconds) since session was created.
     *
     * @param int $created
     */
    public function setSecondsCreated($created)
    {
        $this->setData('created', $created);
    }

    /**
     * Get time (in seconds) since session was last used.
     *
     * @return int
     */
    public function getSecondsLastUsed()
    {
        return $this->getData('lastUsed');
    }

    /**
     * Set time (in seconds) since session was last used.
     *
     * @param int $lastUsed
     */
    public function setSecondsLastUsed($lastUsed)
    {
        $this->setData('lastUsed', $lastUsed);
    }

    /**
     * Check if session is to be saved across browser sessions.
     *
     * @return bool
     */
    public function getRemember()
    {
        return $this->getData('remember');
    }

    /**
     * Set whether session is to be saved across browser sessions.
     *
     * @param bool $remember
     */
    public function setRemember($remember)
    {
        $this->setData('remember', $remember);
    }

    /**
     * Get all session parameters.
     *
     * @return array
     */
    public function getSessionData()
    {
        return $this->getData('data');
    }

    /**
     * Set session parameters.
     *
     * @param string $data
     */
    public function setSessionData($data)
    {
        $this->setData('data', $data);
    }

    /**
     * Get the domain with which the session is registered
     *
     * @return string
     */
    public function getDomain()
    {
        return $this->getData('domain');
    }

    /**
     * Set the domain with which the session is registered
     *
     * @param string $data
     */
    public function setDomain($data)
    {
        $this->setData('domain', $data);
    }

    /**
     * Get user associated with this session (null if anonymous user).
     *
     * @return User
     */
    public function &getUser()
    {
        return $this->user;
    }

    /**
     * Get a usable CSRF token (generating if necessary).
     *
     * @return string
     */
    public function getCSRFToken()
    {
        $csrf = $this->getSessionVar('csrf');
        if (!is_array($csrf) || time() > $csrf['timestamp'] + (60 * 60)) { // 1 hour token expiry
            // Generate random data
            if (function_exists('openssl_random_pseudo_bytes')) {
                $data = openssl_random_pseudo_bytes(128);
            } elseif (function_exists('random_bytes')) {
                $data = random_bytes(128);
            } else {
                $data = sha1(random_int(0, PHP_INT_MAX));
            }

            // Hash the data
            $token = null;
            $salt = Config::getVar('security', 'salt');
            $algos = hash_algos();
            foreach (['sha256', 'sha1', 'md5'] as $algo) {
                if (in_array($algo, $algos)) {
                    $token = hash_hmac($algo, $data, $salt);
                }
            }
            if (!$token) {
                $token = md5($data . $salt);
            }

            $csrf = $this->setSessionVar('csrf', [
                'timestamp' => time(),
                'token' => $token,
            ]);
        } else {
            // Extend timeout of CSRF token
            $csrf['timestamp'] = time();
            $this->setSessionVar('csrf', $csrf);
        }
        return $csrf['token'];
    }
}

if (!PKP_STRICT_MODE) {
    class_alias('\PKP\session\Session', '\Session');
}
