Authentication/src/FuzeWorks/Authentication/Model/Sessions.php

272 lines
9.1 KiB
PHP
Executable File

<?php
/**
* FuzeWorks Authentication Plugin.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013 - 2023 i15
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author i15
* @copyright Copyright (C) 2013 - 2023, i15. (https://i15.nl)
* @license https://opensource.org/licenses/MIT MIT License
*
* @since Version 1.3.0
*
* @version Version 1.3.0
*/
namespace FuzeWorks\Authentication\Model;
use FuzeWorks\Authentication\Driver;
use FuzeWorks\Authentication\Drivers\MongoSessionsModelDriver;
use FuzeWorks\Authentication\Drivers\PdoSessionsModelDriver;
use FuzeWorks\Authentication\SessionsModelDriverInterface;
use FuzeWorks\Config;
use FuzeWorks\ConfigORM\ConfigORM;
use FuzeWorks\DatabaseEngine\iDatabaseEngine;
use FuzeWorks\Exception\FactoryException;
use FuzeWorks\Factory;
use FuzeWorks\Input;
use FuzeWorks\Logger;
use FuzeWorks\Model;
class Sessions extends Model
{
protected ?SessionsModelDriverInterface $driver;
protected ConfigORM $authCFG;
protected ConfigORM $webCFG;
protected Users $usersModel;
/**
* The current session
*
* @var Session
*/
protected Session $session;
public function __construct(iDatabaseEngine $engine, Driver $driver, ConfigORM $config, Users $usersModel)
{
parent::__construct();
// Load engine
if ($driver == Driver::PDO)
$this->driver = new PdoSessionsModelDriver($engine, $usersModel);
elseif ($driver == Driver::MONGO)
$this->driver = new MongoSessionsModelDriver($engine, $usersModel);
// Load config
$this->authCFG = $config;
$this->webCFG = Factory::getInstance("config")->getConfig("web");
// Save Users
$this->usersModel = $usersModel;
// And load the guest session
$this->session = new GuestSession();
}
/**
* @param string|null $sessionKey
* @return Session
*/
public function start(string $sessionKey = null): Session
{
// Fetch the sessionKey
if (is_null($sessionKey))
{
/** @var Input $input */
$input = Factory::getInstance("input");
$cookieName = $this->webCFG->get("cookie_prefix") . "fw_auth_token";
$sessionKey = $input->cookie($cookieName);
}
// If the sessionKey is still null, return the current GuestSession
if (is_null($sessionKey))
return $this->session;
// Otherwise, fetch the current session from the database
$session = $this->getSessionByHash($sessionKey);
if (is_null($session))
{
// Remove the session cookie
$this->_setSession("", 0);
return $this->session;
}
// Check if the date has expired
if (date('U') > $session->expiryDate)
{
Logger::log("Current session has expired. Ending session.");
$session->active = false;
if (!$this->updateSession($session))
Logger::logError("Failed to update session. Setting guest session for now.");
// And remove the session cookie
$this->_setSession("", 0);
return $this->session;
}
// If session has been deactivated, remove cookie and return guest session
if (!$session->active)
{
$this->_setSession("", 0);
return $this->session;
}
// If user has been deactivated, update session and remove cookie
if (!$session->user->active)
{
Logger::log("Current user has been deactivated. Ending session.");
$session->active = false;
if (!$this->updateSession($session))
Logger::logError("Failed to update session. Setting guest session for now.");
// And remove the session cookie
$this->_setSession("", 0);
return $this->session;
}
// Verify that email has been verified on time
if (!is_null($session->user->emailVerifyToken) && date('U') > $session->user->emailVerifyExpiry)
{
// Deactivate the user and write to database
Logger::log("User must verify email before continuing.");
$this->_setSession("", 0);
return $this->session;
}
// Extend the session if threshold has expired
if (date('U') > $session->expiryThreshold)
{
$session->expiryDate = ($session->persistent ? (int) date('U') + (3600*24*31*3) : (int) date('U') + 3600);
$session->expiryThreshold = ($session->persistent ? (int) date('U') + (3600*24) : (int) date('U') + 900);
Logger::log("Current session needs to be extended. Extending session.");
if (!$this->updateSession($session))
Logger::logError("Failed to update session. Maintaining current session.");
// Extend the session
$this->_setSession($session->sessionKey, $session->expiryDate);
}
// Log the session
$id = !is_null($session->user->primaryEmail) ? $session->user->primaryEmail : $session->user->id;
Logger::log("Current session is: '" . $id . "'");
// Set the values and return the session
$this->session = $session;
return $this->session;
}
/**
* @param string $sessionKey
* @return Session|null
*/
public function getSessionByHash(string $sessionKey): ?Session
{
$sessions = $this->driver->readSessions(["sessionKey" => $sessionKey]);
if (empty($sessions))
return null;
return $sessions[0];
}
/**
* @param string $contextKey
* @param string $contextValue
* @return Session[]
*/
public function getSessionsByContext(string $contextKey, string $contextValue): array
{
return $this->driver->readSessions(["context." . $contextKey => $contextValue]);
}
/**
* @param array $filter
* @return Session[]
*/
public function getSessions(array $filter): array
{
return $this->driver->readSessions($filter);
}
public function createSession(User $user, array $context, bool $persistent = false, bool $active = true): ?Session
{
// Generate variables
$hash = substr(base64_encode(sha1(mt_rand())), 0, 16);
$expire = ($persistent ? (int) date('U') + (3600*24*31*3) : (int) date('U') + 3600);
$threshold = ($persistent ? (int) date('U') + (3600*24) : (int) date('U') + 900);
// Create Model
$session = new Session($user, $hash, $expire, $threshold, $context, $persistent, $active);
// Write to driver and set cookie
if ($this->driver->createSession($session))
{
$this->_setSession($hash, $expire);
return $session;
}
return null;
}
public function updateSession(Session $session): bool
{
return $this->driver->updateSessions([$session]);
}
public function endSession(Session $session): bool
{
$session->active = false;
if ($this->updateSession($session))
{
$this->_setSession("", 0);
return true;
}
return false;
}
public function deleteSession(Session $session): bool
{
return $this->driver->deleteSessions([$session]);
}
public function getCurrentSession(): Session
{
return $this->session;
}
private function _setSession(string $sessionKey, int $expire)
{
// @todo Update to use FuzeWorks\Output
setcookie(
$this->webCFG->get("cookie_prefix") . "fw_auth_token",
$sessionKey,
$expire,
$this->webCFG->get("cookie_path"),
$this->webCFG->get("cookie_domain"),
$this->webCFG->get("cookie_secure"),
$this->webCFG->get("cookie_httponly")
);
}
}