Глава 4. Система пользователей и разграничение прав (ACL)

07-07-26 10:18

В этой главе подробно рассматривается устройство подсистемы пользователей, архитектура разграничения прав доступа (ACL) и механизмы безопасности в Seditio CMS. Вы узнаете, как распределяются роли пользователей, как устроена битовая маска прав доступа, как кэшируются права в базе данных, а также принципы авторизации и шифрования.


4.1. Группы пользователей и системные роли

Система управления доступом в Seditio построена на ролевой модели. Все зарегистрированные пользователи обязательно состоят в группах.

4.1.1. Системные (зарезервированные) группы пользователей

В Seditio по умолчанию присутствуют предопределенные группы с зарезервированными идентификаторами (ID), которые жестко завязаны на внутреннюю логику ядра:

  1. Guests (Гости / ID = 1): Неавторизованные посетители сайта. Группа определяет, к какому контенту и функционалу сайта есть публичный доступ.
  2. Inactive (Неактивные / ID = 2): Пользователи, прошедшие регистрацию, но ожидающие активации аккаунта (например, по ссылке из e-mail или после проверки администратором).
  3. Banned (Заблокированные / ID = 3): Пользователи, аккаунты которых временно или навсегда заблокированы.
  4. Members (Участники / ID = 4): Стандартная группа для всех успешно зарегистрированных и активированных пользователей сайта.
  5. Administrators (Администраторы / ID = 5): Члены команды с полными административными правами. Пользователи этой группы имеют доступ в панель управления (по адресу /admin или index.php?module=admin без ЧПУ) по умолчанию.
  6. Moderators (Модераторы / ID = 6): Группа пользователей с модераторскими привилегиями на форумах и в категориях страниц.

4.1.2. Концепция основной группы и дополнительных групп

У каждого пользователя в Seditio есть две плоскости принадлежности к группам:

  • Основная группа (user_maingrp): Хранится в таблице пользователей sed_users. Она определяет базовую роль пользователя на сайте, его скин по умолчанию, язык интерфейса и уровень доступа.
  • Дополнительные группы (Таблица sed_groups_users): Пользователь может состоять в неограниченном числе дополнительных групп. Принадлежность связывается через таблицу отношений sed_groups_users (колонки gru_userid и gru_groupid).

При проверке прав доступа ядро Seditio объединяет привилегии основной группы пользователя и всех дополнительных групп, в которых он состоит.


4.2. Архитектура и битовая маска ACL (Access Control List)

Вся матрица прав доступа в Seditio CMS хранится в таблице sed_auth. Запись о правах привязывается к группе пользователей (auth_groupid), области системы (auth_code — например, page, forums, admin) и конкретной опции внутри этой области (auth_option — например, категория страниц или раздел форума).

4.2.1. Байт как битовая маска прав доступа

Для минимизации размера базы данных и максимальной производительности права на конкретное действие хранятся в виде 8-битного целого числа (один байт от 0 до 255), где каждый бит отвечает за определенное разрешение:

  • Бит 0 (вес 1): Read (Чтение / R). Разрешает просмотр контента.
  • Бит 1 (вес 2): Write (Запись / W). Разрешает добавление и редактирование собственного контента.
  • Бит 2 (вес 4): Пользовательское разрешение 1 (1).
  • Бит 3 (вес 8): Пользовательское разрешение 2 (2).
  • Бит 4 (вес 16): Пользовательское разрешение 3 (3).
  • Бит 5 (вес 32): Пользовательское разрешение 4 (4).
  • Бит 6 (вес 64): Пользовательское разрешение 5 (5).
  • Бит 7 (вес 128): Admin (Администрирование / A). Разрешает модерирование, удаление чужого контента и управление настройками раздела.

Таким образом, если группа имеет полные права на раздел (Чтение + Запись + Администрирование), значение её прав в БД будет равно 1 + 2 + 128 = 131.


4.2.2. Наследование и кумулятивное сложение прав

Если пользователь состоит в нескольких группах одновременно, система не перезаписывает права одной группы правами другой, а применяет принцип кумулятивного сложения (побитового ИЛИ).

При авторизации пользователя функция sed_auth_build() запрашивает права для всех групп, в которых состоит пользователь, и собирает их в единую сетку прав с использованием побитового оператора ИЛИ (|=):

// Cumulative bitwise OR merging of user rights from multiple groups
@$authgrid[$row['auth_code']][$row['auth_option']] |= $row['auth_rights'];

Пример:

  • Пользователь состоит в группе Members, у которой права на чтение форума равны 1 (только Чтение).
  • Этот же пользователь состоит в дополнительной группе Moderators, у которой права на этот же форум равны 128 (Администрирование).
  • Итоговые права пользователя составят 1 | 128 = 129 (Чтение + Администрирование).

4.3. Процесс проверки авторизации в коде (sed_auth)

Ядро системы использует встроенные функции для построения и проверки сетки прав.

4.3.1. Сборка сетки прав при входе пользователя

При успешной авторизации или при первом обращении к странице вызывается функция sed_auth_build(), расположенная в файле system/functions.php. Она сканирует все группы пользователя, в которых он состоит (основную и дополнительные), выполняет SQL-запрос к таблице sed_auth и строит двумерный ассоциативный массив с правами:

$usr['auth'][$area][$option] = $bitwise_rights;

Сигнатура функции:

function sed_auth_build($userid, $maingrp = 0)
  • Логика гостей: Если $userid равен 0 или $maingrp равен 0, функция автоматически считает пользователя гостем и присваивает ему ID группы Guests (ID = 1).
  • Сбор всех групп: Для авторизованных пользователей в список групп добавляется $maingrp, а также все ID групп из таблицы отношений sed_groups_users по запросу к БД.
  • Групповое объединение: Выполняется единый SQL-запрос выборки прав для всех найденных групп:
SELECT auth_code, auth_option, auth_rights FROM sed_auth WHERE auth_groupid IN (...) ORDER BY auth_code ASC, auth_option ASC

В цикле права объединяются с помощью побитового ИЛИ (|=) в единый двумерный массив $authgrid:

@$authgrid[$row['auth_code']][$row['auth_option']] |= $row['auth_rights'];

Метод возвращает полученный массив $authgrid.


4.3.2. Проверка доступа в коде

Для проверки наличия прав в PHP-скриптах используется ключевая функция sed_auth() (файл system/functions.php). Она сопоставляет битовую маску запрашиваемых прав с правами текущего пользователя и возвращает булево значение (TRUE или FALSE) либо массив результатов (если запрашивается проверка нескольких масок одновременно).

Сигнатура функции:

function sed_auth($area, $option, $mask = 'RWA')

Параметры:

  • $area (string) — защищаемая область системы (например, 'page', 'forums', 'admin').
  • $option (string) — конкретный подраздел или категория (например, 'news' для категории страниц).
  • $mask (string) — строка проверяемых прав (по умолчанию 'RWA').

Внутренняя логика проверки:

  1. Функция разбивает строку маски $mask на отдельные символы-биты (через str_split()) и сопоставляет их с числовыми весами: R = 1, W = 2, 1 = 4, 2 = 8, 3 = 16, 4 = 32, 5 = 64, A = 128.
  2. Опция any: Если параметр $option передан как 'any' (проверка наличия права на что угодно в рамках области), функция проверяет всю ветку $usr['auth'][$area] на присутствие хотя бы одной записи с этим битом. Дополнительно, если запрашивается право администратора (A) и у текущего пользователя есть права суперадминистратора (admin/a), функция автоматически вернет TRUE.
  3. Обычная проверка: В остальных случаях проверяется точное пересечение побитового ИЛИ: ($usr['auth'][$area][$option] & $weight) == $weight.
  4. Возвращаемое значение: Если передана строка маски из одной буквы (например, 'R'), функция возвращает одно булево значение (TRUE или FALSE). Если передана маска из нескольких букв (например, 'RWA'), возвращается массив булевых результатов (например, [TRUE, FALSE, FALSE]), что позволяет использовать конструкцию list():
list($usr['auth_read'], $usr['auth_write'], $usr['isadmin']) = sed_auth('page', 'news', 'RWA');

4.3.3. Оптимизация производительности: кэширование прав

Для того чтобы избежать выполнения ресурсоемких SQL-запросов и побитового объединения массивов прав при каждом клике пользователя, Seditio CMS кэширует готовую сетку прав авторизованного пользователя:

  • В таблице sed_users предусмотрено поле user_auth типа TEXT.
  • При первой сборке прав результат работы sed_auth_build() сериализуется через serialize() и сохраняется в это поле.
  • При каждом последующем обращении к страницам права пользователя мгновенно загружаются из базы данных одной строкой и десериализуются: $usr['auth'] = unserialize($row['user_auth']);.
  • Сброс кэша (функция sed_auth_clear): При изменении прав групп в админке, редактировании структуры категорий или добавлении пользователей в новые группы система вызывает функцию sed_auth_clear($id) (файл system/functions.php). Если параметр $id равен 'all', кэш сбрасывается для всех пользователей системы (UPDATE sed_users SET user_auth=''), если передан числовой ID — кэш прав сбрасывается точечно только для этого пользователя.

4.4. Профиль пользователя и механизм его расширения

Каждый пользователь имеет индивидуальную карточку профиля, данные которой хранятся в таблице sed_users.

[!NOTE]

Логика вывода форм регистрации, авторизации, изменения настроек в кабинете и публичной информации о пользователях на сайте обрабатывается встроенным модулем users (подробное описание его структуры, контроллеров и TPL-шаблонов приведено в Главе 5. Встроенные модули).

4.4.1. Системная структура профиля

В состав профиля по умолчанию входят следующие ключевые поля:

  • user_id — уникальный идентификатор пользователя.
  • user_name — логин (уникальное имя для авторизации).
  • user_password и user_salt — хэш пароля и соль.
  • user_maingrp — основная группа пользователя.
  • user_email — адрес электронной почты.
  • user_avatar и user_photo — аватар и фотография.
  • user_timezone, user_lang, user_skin — региональные настройки (часовой пояс, язык, тема оформления).
  • user_regdate, user_lastlog, user_lastip — статистика активности.

4.4.2. Расширение профиля с помощью словарей

Если стандартных полей профиля недостаточно (например, требуется добавить поля «Номер телефона», «Адрес доставки» или «Telegram ID»), администратор может расширить профиль без изменения исходного PHP-кода:

  1. В разделе «Словари» по адресу /admin/dic (или index.php?module=admin&m=dic без ЧПУ) создается новый словарь.
  2. Целевой таблицей выбирается таблица пользователей users.
  3. Создаются элементы словаря (sed_dic / sed_dic_items).
  4. Система выполняет SQL-запрос ALTER TABLE sed_users ADD user_field_name ..., физически расширяя структуру базы данных.
  5. Новое поле автоматически интегрируется в форму редактирования профиля в кабинете пользователя, в панель администрирования и становится доступно в TPL-шаблонах через теги вида {USER_FIELD_NAME}.

4.5. Механизм сессий, авторизации и безопасности

Безопасность авторизации пользователей обеспечивается комплексом мер на уровне сессий, кук и криптографического хэширования.

4.5.1. Схема авторизации и Persistent Login

В Seditio поддерживается три режима авторизации (задаются через конфигурацию $cfg['authmode'] в datas/config.php):

  1. Только куки (режим 1): Авторизация привязана исключительно к cookie-файлам клиента.
  2. Только сессии (режим 2): Авторизация привязана к PHP-сессиям пользователя.
  3. Смешанный режим (режим 3): Используются и куки, и сессии (режим по умолчанию).

При авторизации через куки (режимы 1 и 3) Persistent Login базируется на специальной куке автологина с именем $sys['site_id']:

  • Данные в куке хранятся в формате base64 и имеют структуру: user_id:_:user_secret:_:user_skin.
  • Обратите внимание: в куке хранится не пароль пользователя, а специальный секретный токен сеанса user_secret (MD5-хэш), генерируемый и сохраняемый в базе данных при успешном входе.
  • При каждом запросе ядро считывает куку, декодирует её и сопоставляет поля с базой данных:
// Decoding persistent login cookie value
  $u = base64_decode($_COOKIE[$sys['site_id']]);
  $u = explode(':_:', $u);
  $rsedition = sed_import($u[0], 'D', 'INT');  // User ID
  $rseditiop = sed_import($u[1], 'D', 'H32');  // User Secret hash
  $rseditios = sed_import($u[2], 'D', 'ALP');  // User Skin
  • Если включена проверка по IP ($cfg['ipcheck']), ядро дополнительно сверяет текущий IP-адрес запроса посетителя ($usr['ip']) со значением IP-адреса его последнего успешного входа (user_lastip), сохраненным в таблице sed_users. В случае несовпадения (например, при смене сети пользователем) авторизация по куке сбрасывается.

4.5.2. Хэширование паролей и использование Site Secret

Для защиты паролей от компрометации в Seditio используется двухфакторное хэширование с солью с помощью функции sed_hash($data, $type, $salt) (файл system/functions.php):

  • При регистрации для пользователя генерируется уникальная случайная соль (user_salt).
  • Хэш пароля генерируется функцией sed_hash() с параметром $type = 1. При этом глобальный секретный ключ сайта $cfg['site_secret'] не используется:
// Hashing password with user salt (type = 1)
  $res = hash($algorithm, hash($algorithm, $password) . $salt);
  • Глобальный уникальный ключ сайта $cfg['site_secret'] (задаваемый в datas/config.php при установке) используется функцией sed_hash() с параметром $type = 2 только для генерации уникального идентификатора сессии/токена сеанса (sessionid / user_secret):
// Hashing dynamic session identifier (type = 2)
  $res = hash($algorithm, hash($algorithm, $data) . $cfg['site_secret'] . $salt);
  • Двойное хэширование (md5 по умолчанию) и индивидуальная соль защищают пароли от взлома по радужным таблицам (Rainbow Tables).

4.5.3. Защита от брутфорса и Бан-лист

  • Аудит неудачных попыток входа: В системе ведется журнал логирования. Все неудачные попытки авторизации под любым именем записываются в лог безопасности (sec), включая IP-адрес нападающего.
  • Блокировки по IP и e-mail (Бан-лист): В панели управления по адресу /admin/banlist (или index.php?module=admin&m=banlist без ЧПУ) администратор может блокировать доступ к сайту по конкретному IP-адресу, e-mail маске (например, *@spamdomain.com) или маске подсети. Система полностью поддерживает как IPv4, так и IPv6-адреса (поле banlist_ip имеет тип VARCHAR(45)). Проверка осуществляется функцией sed_check_banlist($userip) (файл system/functions.php), которая при обращении к любой странице сверяет IP посетителя с базой, автоматически строя маски для сравнения подсетей (например, 192.168.1.* для IPv4 или 2001:db8:85a3:0:*:*:*:* для IPv6). При обнаружении совпадения выполнение скрипта немедленно прерывается.

Ratings:
(0.00)

No comments yet