Аутентификация и разграничение прав доступа
Каждый, кто пытался реализовать полноценное разграничение прав доступа в программе, использующей в качестве СУБД Interbase, Firebird или Yaffil, сталкивался с ограничениями как самого стандарта SQL в этой области, так и конкретной реализации в одном из вышеупомянутых серверов.
Перечислим наиболее существенные из них:
- Учетные записи пользователей хранятся отдельно от базы данных. Список пользователей един для всех баз данных, расположенных на конкретном сервере. Данное ограничение существенно усложняет перенос базы данных между серверами или развертывание распределенных баз данных.
- Для учетной записи пользователя невозможно задать ряд необходимых параметров, как то:
- Состояние учетной записи: активна/не активна;
- Дата и время истечения срока действия учетной записи;
- Дата и время истечения срока действия пароля учетной записи;
- Необходимость сменить пароль при очередном входе в систему;
- Временной интервал, в течение которого допускается вход в систему.
- Невозможно определить список групп пользователей и задать членство в группах для конкретной учетной записи.
- Невозможно запретить множественные одновременные подключения под одним аккаунтом.
- Невозможно разграничить доступ к данным на уровне строк таблицы (результирующей выборки).
В настоящей статье, на примере кода платформы Гедымин, рассматриваются подходы по реализации подсистемы безопасности, избавленной от вышеупомянутых ограничений.
Учетные записи и группы пользователей
Учетные записи системы хранятся непосредственно в базе данных, в таблице с именем GD_USER:
| Поле | Тип данных | Описание |
| ID | INTEGER | Первичный ключ записи. |
| NAME | VARCHAR(20) NOT NULL | Имя учетной записи. |
| PASSW | VARCHAR(20) NOT NULL | Пароль учетной записи. |
| INGROUP | INTEGER, DEFAULT 1 | Битовая маска групп, в которые входит данная учетная запись. |
| FULLNAME | VARCHAR(180) NOT NULL | Полное имя пользователя. |
| DESCRIPTION | VARCHAR(180) | Произвольный комментарий. |
| IBNAME | VARCHAR(20) NOT NULL | Имя пользователя Interbase. |
| IBPASSWORD | VARCHAR(20) NOT NULL | Пароль пользователя Interbase. |
| LOCKEDOUT | INTEGER, DEFAULT 0 | Запись заблокирована. |
| MUSTCHANGE | INTEGER, DEFAULT 0 | Пользователь обязан изменить пароль при следующем входе в систему. |
| CANTCHANGEPASSW | INTEGER, DEFAULT 1 | Пользователь не может менять пароль. |
| PASSWNEVEREXP | INTEGER, DEFAULT 1 | Срок действия пароля никогда не истекает. |
| EXPDATE | DATE | Дата истечения срока действия пароля. |
| WORKSTART | TIME | Начало временно интервала, в котором пользователю разрешен вход в систему. |
| WORKEND | TIME | Окончание временного интервала, в котором пользователю разрешен вход в систему. |
| … |
Выше, приведен список только основных полей из таблицы GD_USER.
При создании новой учетной записи в Гедымине, автоматически создается учетная запись сервера Interbase. Имя учетной записи сервера и ее пароль имеют длину по 8 символов, генерируются случайным образом и сохраняются в полях IBNAME, IBPASSW. Зарезервированной является учетная запись Администратор, которой сопоставлена учетная запись SYSDBA на сервере базы данных.
При переносе базы данных на новый сервер необходимо войти в систему под учетной записью Администратор и вызвать команду "Пересоздать всех пользователей", которая создаст учетные записи Interbase для пользователей Гедымина.
Список групп пользователей хранится в таблице GD_USERGROUP:
| Поле | Тип данных | Описание |
| ID | INTEGER | Уникальный идентификатор группы. Целое число в диапазоне 1-32. |
| NAME | VARCHAR(60) NOT NULL | Наименование группы пользователей. |
| DESCRIPTION | VARCHAR(180) | Описание группы пользователей. |
| DISABLED | INTEGER | Группа отключена. Пользователи, входящие в отключенную группу не смогут войти в систему. |
| … |
Максимальное количество групп пользователей — 32, по числу бит в целом числе. Обратите внимание, каким образом задается членство конкретного пользователя в группах. Поле INGROUP таблицы GD_USER содержит битовую маску, где бит с номером N (нумерация начинается с 1) отвечает за принадлежность пользователя к группе с идентификатором ID=N. Почему была выбрана именно такая схема, станет ясно позднее, а пока рассмотрим процесс входа пользователя в систему.
Аутентификация пользователя
В базе данных существует роль Administrator, которая обладает полными правами на любой объект базы данных. Создаваемые учетные записи пользователей Interbase получают право на использование данной роли. Таким образом, исключается необходимость назначать права на каждый объект базы данных при создании новой учетной записи. Кроме этого, на сервере должна присутствовать учетная запись STARTUSER с паролем startuser. Эта учетная запись обладает единственным правом на выполнение хранимой процедуры GD_P_SEC_LOGINUSER, которая, в свою очередь, обладает правами на чтение из таблиц GD_USER и GD_USERGROUP. На вход процедуры передается учетная запись системы Гедымин и введенный пользователем пароль. Если проверка прошла успешно, то процедура возвращает имя учетной записи и пароль для подключения к серверу Interbase.
Таким образом, алгоритм подключения к базе данных выглядит следующим образом:
- Запускается модуль gedemin.exe.
- Осуществляется попытка подключения под учетной записью STARTUSER и паролем startuser. Если произошла ошибка, то:
- Если база данных не существует, то предлагаем пользователю указать расположение файла базы данных;
- Если отсутствует учетная запись STARTUSER, то пытаемся создать ее, при необходимости, запросив у пользователя пароль для учетной записи SYSDBA.
- Если подключение под STARTUSER прошло успешно, то запрашиваем у пользователя имя учетной записи в системе Гедымин и пароль.
- Вызываем процедуру GD_P_SEC_LOGINUSER, передаем ей введенные параметры. Если введены корректные значения, сроки действия учетной записи и пароля не истекли, учетная запись не отключена и т.п., то процедура возвращает учетную запись сервера Interbase и ее пароль.
- Переподключаемся к базе данных, используя полученную учетную запись, ее пароль и роль Administrator.
Дескрипторы безопасности
Разграничение прав доступа на уровне записи осуществляется с помощью т.н. дескрипторов безопасности. Каждый дескриптор — это целочисленное поле, которое содержит битовую маску групп пользователей, обладающих определенным правом доступа. Система поддерживает три уровня доступа, каждому из которых соответствует предустановленное имя дескриптора безопасности:
- AVIEW — набор групп, обладающих правом на просмотр записи;
- ACHAG — набор групп, обладающих правом на просмотр и изменение записи;
- AFULL — набор групп, обладающих максимальными правами на запись.
В таблице может присутствовать любая комбинация из вышеуказанных полей-дескрипторов.
При автоматическом формировании SQL запроса на извлечение данных Гедымин подставляет следующее условие:
BIN_AND ( Z.AVIEW, :IG ) <> 0, где:
Z — алиас главной таблицы бизнес-объекта;
IG — параметр, содержащий битовую маску групп, в которые входит текущий пользователь.
Описанный выше вариант разграничения прав на уровне записи не является реляционным, в строгом смысле этого слова. Однако, это единственное решение, приемлемое с точки зрения производительности. И действительно, альтернативой могло бы быть создание таблицы содержащей: идентификатор записи, для которой установлены права, ссылку на группу пользователей или конкретного пользователи и описание прав доступа. Предположим, что мы имеем дело с базой данных предприятия, где необходимо обеспечить разграничение прав на уровне документов и объектов справочников. Пусть первых в базе данных находится D записей, а вторых — R. Активно используется N групп пользователей. Тогда, в первом приближении, количество записей в вышеупомянутой таблице можно оценить как: (D + R) * N. Реальный документооборот крупного предприятия может составлять миллионы документов. Добавление JOIN на такую таблицу в запрос извлечения данных приведет к существенному увеличению времени его выполнения. Да и на диске, такая таблица со всеми необходимыми индексами займет места больше, чем отдельные поля дескрипторов безопасности.
Платой за нереляционность такого решения является особая обработка операции удаления группы пользователей из списка: необходимо пройтись по всем таблицам, содержащим дескрипторы безопасности, и снять бит, принадлежавший удаленной группе. К счастью, такую операцию приходится проделывать весьма редко.
26.02.2006