%PDF- %PDF-
| Direktori : /www/varak.net/dmarc.varak.net/classes/Database/Common/ |
| Current File : /www/varak.net/dmarc.varak.net/classes/Database/Common/CommonUserMapper.php |
<?php
/**
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
* Copyright (C) 2023-2025 Aleksey Andreev (liuch)
*
* Available at:
* https://github.com/liuch/dmarc-srg
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*
* =========================
*
* This file contains the CommonUserMapper class
*
* @category API
* @package DmarcSrg
* @author Aleksey Andreev (liuch)
* @license https://www.gnu.org/licenses/gpl-3.0.html GNU/GPLv3
*/
namespace Liuch\DmarcSrg\Database\Common;
use Liuch\DmarcSrg\DateTime;
use Liuch\DmarcSrg\Database\UserMapperInterface;
use Liuch\DmarcSrg\Exception\DatabaseFatalException;
use Liuch\DmarcSrg\Exception\DatabaseNotFoundException;
/**
* Universal implementation of UserMapper class
*/
class CommonUserMapper implements UserMapperInterface
{
/** @var \Liuch\DmarcSrg\Database\DatabaseConnector */
private $connector = null;
/**
* The constructor
*
* @param \Liuch\DmarcSrg\Database\DatabaseConnector $connector DatabaseConnector instance of the current database
*/
public function __construct(object $connector)
{
$this->connector = $connector;
}
/**
* Return true if the user exists or false otherwise.
*
* @param array $data Array with user data to search
*
* @return bool
*/
public function exists(array &$data): bool
{
try {
$st = $this->connector->dbh()->prepare(
'SELECT id FROM ' . $this->connector->tablePrefix('users') . ' WHERE ' . $this->sqlCondition($data)
);
$this->sqlBindValues($st, $data, 1);
$st->execute();
$res = $st->fetch(\PDO::FETCH_NUM);
$st->closeCursor();
if (!$res) {
return false;
}
$data['id'] = intval($res[0]);
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to verify the user existence', -1, $e);
}
return true;
}
/**
* Fetch the user data from the database by its id or name
*
* @param array $data User data to update
*
* @return void
*/
public function fetch(array &$data): void
{
try {
$st = $this->connector->dbh()->prepare(
'SELECT id, name, level, enabled, IF(password IS NULL OR password = \'\', FALSE, TRUE),'
. ' email, "key", session, created_time, updated_time FROM '
. $this->connector->tablePrefix('users') . ' WHERE ' . $this->sqlCondition($data)
);
$this->sqlBindValues($st, $data, 1);
$st->execute();
$res = $st->fetch(\PDO::FETCH_NUM);
$st->closeCursor();
if (!$res) {
throw new DatabaseNotFoundException('User not found');
}
$data['id'] = intval($res[0]);
$data['name'] = $res[1];
$data['level'] = intval($res[2]);
$data['enabled'] = boolval($res[3]);
$data['password'] = boolval($res[4]);
$data['email'] = $res[5];
$data['key'] = $res[6];
$data['session'] = intval($res[7]);
$data['created_time'] = new DateTime($res[8]);
$data['updated_time'] = new DateTime($res[9]);
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to fetch the user data', -1, $e);
}
}
/**
* Saves user data to the database (updates or inserts an record)
*
* @param array $data User data
*
* @return void
*/
public function save(array &$data): void
{
$db = $this->connector->dbh();
$data['updated_time'] = new DateTime();
$enabled = $data['enabled'] ?? false;
if ($this->exists($data)) {
try {
$id = $data['id'];
$db->beginTransaction();
$u_tn = $this->connector->tablePrefix('users');
$extra = '';
if (!$enabled) {
$st = $db->prepare("SELECT enabled FROM {$u_tn} WHERE id = ?");
$st->bindValue(1, $id, \PDO::PARAM_INT);
$st->execute();
$res = $st->fetch(\PDO::FETCH_NUM);
$st->closeCursor();
if ($res && boolval($res[0])) {
// The user got deactivated. Reset its active sessions.
$extra = ', session = session + 1';
}
}
$st = $db->prepare(
'UPDATE ' . $u_tn
. ' SET level = ?, enabled = ?, email = ?, "key" = ?, updated_time = ?'
. $extra . ' WHERE id = ?'
);
$st->bindValue(1, $data['level'], \PDO::PARAM_INT);
$st->bindValue(2, $enabled, \PDO::PARAM_BOOL);
if (empty($data['email'])) {
$st->bindValue(3, null, \PDO::PARAM_NULL);
} else {
$st->bindValue(3, $data['email'], \PDO::PARAM_STR);
}
if (empty($data['key'])) {
$st->bindValue(4, null, \PDO::PARAM_NULL);
} else {
$st->bindValue(4, $data['key'], \PDO::PARAM_STR);
}
$st->bindValue(5, $data['updated_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
$st->bindValue(6, $id, \PDO::PARAM_INT);
$st->execute();
$st->closeCursor();
$db->commit();
} catch (\PDOException $e) {
$db->rollBack();
throw new DatabaseFatalException('Failed to update the user data', -1, $e);
} catch (\Exception $e) {
$db->rollBack();
throw $e;
}
} else {
try {
$data['created_time'] = $data['updated_time'];
if (!is_null($data['email'])) {
$ss1 = ', email';
$ss2 = ', ?';
} else {
$ss1 = '';
$ss2 = '';
}
if (!is_null($data['key'])) {
$ss1 .= ', "key"';
$ss2 .= ', ?';
}
$st = $db->prepare(
'INSERT INTO ' . $this->connector->tablePrefix('users')
. ' (name, level, enabled' . $ss1 . ', session, created_time, updated_time)'
. ' VALUES (?, ?, ?' . $ss2 . ', ?, ?, ?)'
);
$idx = 0;
$st->bindValue(++$idx, $data['name'], \PDO::PARAM_STR);
$st->bindValue(++$idx, $data['level'], \PDO::PARAM_INT);
$st->bindValue(++$idx, $enabled, \PDO::PARAM_BOOL);
if (!is_null($data['email'])) {
$st->bindValue(++$idx, $data['email'], \PDO::PARAM_STR);
}
if (!is_null($data['key'])) {
$st->bindValue(++$idx, $data['key'], \PDO::PARAM_STR);
}
$st->bindValue(++$idx, 0, \PDO::PARAM_INT);
$st->bindValue(++$idx, $data['created_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
$st->bindValue(++$idx, $data['updated_time']->format('Y-m-d H:i:s'), \PDO::PARAM_STR);
$st->execute();
$st->closeCursor();
$data['id'] = intval($db->lastInsertId());
$data['enabled'] = $enabled;
$data['session'] = 0;
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to insert the user data', -1, $e);
}
}
}
/**
* Deletes the user from the database
*
* @param array $data User data
*
* @return void
*/
public function delete(array &$data): void
{
$db = $this->connector->dbh();
$db->beginTransaction();
try {
$id = $data['id'];
$st = $db->prepare('DELETE FROM ' . $this->connector->tablePrefix('userdomains')
. ' WHERE user_id = ?');
$st->bindValue(1, $id, \PDO::PARAM_INT);
$st->execute();
$st->closeCursor();
$st = $db->prepare('DELETE FROM ' . $this->connector->tablePrefix('system') . ' WHERE user_id = ?');
$st->bindValue(1, $id, \PDO::PARAM_INT);
$st->execute();
$st->closeCursor();
$st = $db->prepare('DELETE FROM ' . $this->connector->tablePrefix('users') . ' WHERE id = ?');
$st->bindValue(1, $id, \PDO::PARAM_INT);
$st->execute();
$st->closeCursor();
$db->commit();
} catch (\PDOException $e) {
$db->rollBack();
throw new DatabaseFatalException('Failed to delete the user', -1, $e);
} catch (\Exception $e) {
$db->rollBack();
throw $e;
}
}
/**
* Returns a list of users data from the database
*
* @return array
*/
public function list(): array
{
$list = [];
try {
$st = $this->connector->dbh()->query(
'SELECT id, name, level, enabled, email, "key", created_time, updated_time, '
. '(SELECT COUNT(*) FROM ' . $this->connector->tablePrefix('userdomains')
. ' WHERE user_id = id) AS domains FROM ' . $this->connector->tablePrefix('users')
);
while ($row = $st->fetch(\PDO::FETCH_NUM)) {
$list [] = [
'id' => intval($row[0]),
'name' => $row[1],
'level' => intval($row[2]),
'enabled' => boolval($row[3]),
'email' => $row[4],
'key' => $row[5],
'created_time' => new DateTime($row[6]),
'updated_time' => new DateTime($row[7]),
'domains' => intval($row[8])
];
}
$st->closeCursor();
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to get the domain list', -1, $e);
}
return $list;
}
/**
* Returns the user's password hash
*
* @param array $data User data
*
* @return string
*/
public function getPasswordHash(array &$data): string
{
$hash = '';
try {
$st = $this->connector->dbh()->prepare(
'SELECT password FROM ' . $this->connector->tablePrefix('users')
. ' WHERE ' . $this->sqlCondition($data)
);
$this->sqlBindValues($st, $data, 1);
$st->execute();
$res = $st->fetch(\PDO::FETCH_NUM);
$st->closeCursor();
if (!$res) {
throw new DatabaseNotFoundException('User not found');
}
$hash = strval($res[0]);
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to get user data', -1, $e);
}
return $hash;
}
/**
* Replaces the user's password hash with the passed one
*
* @param array $data User data
* @param string $hash Password hash to save
*/
public function savePasswordHash(array &$data, string $hash): void
{
try {
$st = $this->connector->dbh()->prepare(
'UPDATE ' . $this->connector->tablePrefix('users')
. ' SET password = ?, session = session + 1 WHERE ' . $this->sqlCondition($data)
);
$st->bindValue(1, $hash, \PDO::PARAM_STR);
$this->sqlBindValues($st, $data, 2);
$st->execute();
$st->closeCursor();
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to save user data', -1, $e);
}
}
/**
* Updates the user's key string
*
* @param array $data User data
* @param string $key User key string to set
*
* @return void
*/
public function setUserKey(array &$data, string $key): void
{
try {
$st = $this->connector->dbh()->prepare(
'UPDATE ' . $this->connector->tablePrefix('users')
. ' SET "key" = ? WHERE ' . $this->sqlCondition($data)
);
$st->bindValue(1, $key, \PDO::PARAM_STR);
$this->sqlBindValues($st, $data, 2);
$st->execute();
$st->closeCursor();
$data['key'] = $key;
} catch (\PDOException $e) {
throw new DatabaseFatalException('Failed to update user key', -1, $e);
}
}
/**
* Returns a condition string for a WHERE statement based on existing user data
*
* @param array $data User data
*
* @return string
*/
private function sqlCondition(array &$data): string
{
if (isset($data['id'])) {
return 'id = ?';
}
return 'name = ?';
}
/**
* Binds values for SQL queries based on existing user data
*
* @param \PDOStatement $st PDO statement to bind to
* @param array $data User data
* @param int $pos Bind position
*
* @return void
*/
private function sqlBindValues($st, array &$data, int $pos): void
{
if (isset($data['id'])) {
$st->bindValue($pos, $data['id'], \PDO::PARAM_INT);
} else {
$st->bindValue($pos, $data['name'], \PDO::PARAM_STR);
}
}
}