супер попа (обновление)

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich
2022-04-16 17:30:06 +10:00
parent c8025acc79
commit 30fd8f0ec3
14 changed files with 623 additions and 338 deletions

View File

@@ -15,33 +15,86 @@ use exception;
*/
final class accounts_model extends core
{
/**
* Идентификатор
*/
public int $id;
/**
* Почта
*/
public string $mail;
/**
* Пароль
*/
public string $password;
/**
* Хеш
*/
public string $hash;
/**
* Время активности хеша
*/
public int $time;
/**
* Время активности хеша
*/
public array $permissions;
/**
* Конструктор
*
* @param array $vars Параметры
*/
public function __construct(array $vars = []) {
foreach ($vars as $key => $value) {
// Перебор параметров
// Запись свойства
if (property_exists($this, $key)) $this->$key = $value;
}
}
/**
* Регистрация
*
* @param string $name Входной псевдоним
* @param string $email Почта
* @param string $password Пароль (password)
* @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
* @param string $mail Почта
* @param string $password Пароль
* @param bool $authenticate Автоматическая аутентификация в случае успешной регистрации
* @param array &$errors Журнал ошибок
*
* @return array|bool Аккаунт, если удалось аутентифицироваться
* @return static|null Аккаунт
*/
public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
public static function registration(string $mail, string $password, bool $authenticate = true, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (static::account($errors)) {
if (static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
if (empty($account = static::read(['mail' => $mail]))) {
// Не удалось найти аккаунт
if (static::write($name, $email, $password, $errors)) {
if (static::write($mail, $password, $errors)) {
// Удалось зарегистрироваться
if ($authenticate) {
// Запрошена аутентификация
// Аутентификация
$account = static::authentication($mail, $password, true, $errors);
}
return $account;
}
} else {
@@ -51,7 +104,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -59,41 +112,44 @@ final class accounts_model extends core
];
}
return [];
return null;
}
/**
* Аутентификация
*
* @param string $login Входной псевдоним
* @param string $password Пароль (password)
* @param string $mail Почта
* @param string $password Пароль
* @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
* @return static|null Аккаунт
*/
public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
public static function authentication(string $mail, string $password, bool $remember = false, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (static::account($errors)) {
if (static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Запись ошибки
throw new exception('Уже аутентифицирован');
}
if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
if (empty($account = static::read(['mail' => $mail]))) {
// Не удалось найти аккаунт
throw new exception('Не удалось найти аккаунт');
}
if (password_verify($password, $account['password'])) {
if (password_verify($password, $account->password)) {
// Совпадают хеши паролей
// Инициализация идентификатора сессии
session_id($account['id']);
session_id((string) $account->id);
// Инициализация названия сессии
session_name('id');
@@ -105,10 +161,10 @@ final class accounts_model extends core
$time = time() + ($remember ? 604800 : 86400);
// Инициализация хеша
$hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
$hash = static::hash((int) $account->id, crypt($account->password, time() . $account->id), $time, $errors)['hash'];
// Инициализация cookies
setcookie("hash", $hash, $time, path: '/', secure: true);
setcookie('hash', $hash, $time, path: '/', secure: true);
return $account;
} else {
@@ -118,7 +174,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -126,7 +182,7 @@ final class accounts_model extends core
];
}
return [];
return null;
}
/**
@@ -138,8 +194,11 @@ final class accounts_model extends core
*/
public static function deauthentication(array &$errors = []): bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if ($account = static::account($errors)) {
if ($account = static::init(errors: $errors)) {
// Аутентифицирован пользователь
// Инициализация запроса
@@ -147,7 +206,7 @@ final class accounts_model extends core
// Параметры запроса
$params = [
":id" => $account['id'],
":id" => $account->id,
];
// Отправка запроса
@@ -157,8 +216,8 @@ final class accounts_model extends core
$request->fetch(pdo::FETCH_ASSOC);
// Деинициализация cookies
setcookie("id", '', 0, path: '/', secure: true);
setcookie("hash", '', 0, path: '/', secure: true);
setcookie('id', '', 0, path: '/', secure: true);
setcookie('hash', '', 0, path: '/', secure: true);
return true;
} else {
@@ -169,7 +228,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -181,21 +240,30 @@ final class accounts_model extends core
}
/**
* Прочитать данные аккаунта, если пользователь аутентифицирован
*
* Можно использовать как проверку на аутентифицированность
* Инициализация
*
* @param int|null $account Аккаунт (идентификатор)
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
*
* @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
* @return static|null Аккаунт
*/
public static function account(array &$errors = []): array
public static function init(?int $account = null, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
if (isset($account)) {
// Получен идентификатор аккаунта
if (empty($account = static::read(['id' => $account]))) {
// Не найден аккаунт
// Генерация ошибки
throw new exception('Не найден пользователь');
}
} else if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
// Найдены cookie с данными аккаунта (подразумевается, что он аутентифицирован)
if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
// Совпадает переданный хеш с тем, что хранится в базе данных
@@ -206,34 +274,28 @@ final class accounts_model extends core
throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
// Параметры запроса
$params = [
":id" => $_COOKIE['id'],
":hash" => $_COOKIE['hash'],
];
// Отправка запроса
$request->execute($params);
// Генерация ответа
if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
// Не найдена связка идентификатора с хешем
if (empty($account = static::read([
'id' => $_COOKIE['id'],
'hash' => $_COOKIE['hash']
]))) {
// Не найден аккаунт или связка аккаунта с хешем
// Генерация ошибки
throw new exception('Не найден пользотватель или время аутентификации истекло');
throw new exception('Не найден пользователь или время аутентификации истекло');
}
} else {
// Не найдены параметры для поиска аккаунта
// Чтение разрешений
$account['permissions'] = static::permissions((int) $account['id'], $errors);
return $account;
return null;
}
// Чтение разрешений
$account->permissions = static::permissions((int) $account->id, $errors);
return $account;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -241,11 +303,12 @@ final class accounts_model extends core
];
}
return [];
return null;
}
/**
* Прочитать разрешения аккаунта
* Прочитать разрешения из базы данных
*
* @param int $id Идентификатор аккаунта
* @param array &$errors Журнал ошибок
@@ -254,6 +317,9 @@ final class accounts_model extends core
*/
public static function permissions(int $id, array &$errors = []): array
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
@@ -280,7 +346,7 @@ final class accounts_model extends core
return $response;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -302,14 +368,17 @@ final class accounts_model extends core
*/
public static function access(string $permission, int|null $id = null, array &$errors = []): ?bool
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация аккаунта
$account = isset($id) ? self::read(['id' => $id], $errors) : self::account($errors);
return isset($account['permissions'][$permission]) ? (bool) $account['permissions'][$permission] : null;
return isset($account->permissions[$permission]) ? (bool) $account->permissions[$permission] : null;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -321,114 +390,57 @@ final class accounts_model extends core
}
/**
* Запись пользователя в базу данных
* Запись в базу данных
*
* @param string|null $name Имя
* @param string|null $email Почта
* @param string|null $password Пароль
* @param string $mail Почта
* @param string $password Пароль
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт (если не найден, то пустой массив)
* @return static|null Аккаунт
*/
public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
public static function write(string $mail, string $password, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация параметров запроса
$params = [];
if (isset($name)) {
try {
// Проверка параметра
if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
// Запись в буфер параметров запроса
$params[':name'] = $name;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($email)) {
try {
// Проверка параметра
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Запись в буфер параметров запроса
$params[':email'] = $email;
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
if (isset($password)) {
try {
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
];
goto end;
}
}
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
try {
if (isset($name)) {
// Передано имя аккаунта
// Проверка параметра
if (filter_var($mail, FILTER_VALIDATE_mail) === false) throw new exception('Не удалось распознать почту');
if (iconv_strlen($mail) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
if (iconv_strlen($mail) > 60) throw new exception('Длина почты должна быть не более 80 символов');
// Чтение аккаунта
$account = static::read(['name' => $name]);
} else if (isset($email)) {
// Передана почта аккаунта
// Запись в буфер параметров запроса
$params[':mail'] = $mail;
// Чтение аккаунта
$account = static::read(['email' => $email]);
} else {
// Не передано ни имя, ни почта
// Проверка параметра
if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
throw new exception('Не переданны данные для полноценной регистрации аккаунта');
}
// Запись в буфер параметров запроса
$params[':password'] = password_hash($password, PASSWORD_BCRYPT);
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($mail) ? ', ' : '') . (isset($mail) ? '`mail`' : '') . ((isset($name) || isset($mail)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($mail) ? ', ' : '') . (isset($mail) ? ':mail' : '') . ((isset($name) || isset($mail)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
// Отправка запроса
$request->execute($params);
// Генерация ответа
$request->fetch(pdo::FETCH_ASSOC);
// Чтение аккаунта
$account = static::read(['mail' => $mail]);
// Инициализация запроса
$request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
// Инициализация параметров
$params = [
':id' => $account['id']
':id' => $account->id
];
// Отправка запроса
@@ -438,7 +450,7 @@ final class accounts_model extends core
$request->fetch(pdo::FETCH_ASSOC);
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -447,7 +459,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -458,52 +470,58 @@ final class accounts_model extends core
// Конец выполнения
end:
return isset($account) && $account ? $account : [];
return $account ?? [];
}
/**
* Чтение пользователя из базы данных
* Чтение из базы данных
*
* @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
* @param array $expression Выражение поиска ('поле' => 'значение')
* @param array &$errors Журнал ошибок
*
* @return array Аккаунт, если найден
* @return static|null Аккаунт
*/
public static function read(array $search, array &$errors = []): array
public static function read(array $expression, array &$errors = []): ?static
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
// Инициализация данных для поиска
$field = array_keys($search)[0] ?? null;
$value = $search[$field] ?? null;
// Инициализация выражения поиска
$where = 'WHERE ';
if (empty($field)) {
// Получено пустое значение поля
// Инициализация параметров запроса
$params = [];
// Запись ошибки
throw new exception('Пустое значение поля для поиска');
foreach ($expression as $parameter => $value) {
// Перебор выражения поиска
// Запись в строку запроса
$where .= "`$parameter` = :$parameter &&";
// Запись параметров запроса
$params[":$parameter"] = $value;
}
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
// Очистка или реинициализация выражения поиска
$where = empty($expression) ? '' : trim(trim($where, '&&'));
// Параметры запроса
$params = [
":field" => $value,
];
// Инициализация запроса
$request = static::$db->prepare("SELECT * FROM `accounts` $where LIMIT 1");
// Отправка запроса
$request->execute($params);
// Генерация ответа
if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
if ($account = new static($request->fetch(pdo::FETCH_ASSOC))) {
// Найден аккаунт
try {
if ($permissions = static::permissions((int) $account['id'], $errors)) {
if ($permissions = static::permissions((int) $account->id, $errors)) {
// Найдены разрешения
// Запись в буфер данных аккаунта
$account['permissions'] = $permissions;
$account->permissions = $permissions;
} else {
// Не найдены разрешения
@@ -511,7 +529,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
@@ -524,7 +542,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][]= [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
@@ -532,7 +550,7 @@ final class accounts_model extends core
];
}
return isset($account) && $account ? $account : [];
return $account ?? null;
}
/**
@@ -547,6 +565,9 @@ final class accounts_model extends core
*/
public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
{
// Инициализация журнала ошибок
$errors['account'] ?? $errors['account'] = [];
try {
if (isset($hash, $time)) {
// Переданы хеш и его время хранения
@@ -608,7 +629,7 @@ final class accounts_model extends core
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[]= [
$errors['account'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),