Права на объекты, хранимые в базе данных

Виноградов С. А.
16.02.2003 г.


Введение

Рано или поздно, в любой многопользовательской базе данных возникает вопрос о разграничении прав на различные объекты, которые хранятся в этой базе. Причем разновидности этих прав могут быть самыми экзотическими.
Вариант, когда пользователю необходимо дать право на изменение только заданного атрибута, у некоторых объектов, лишь на какие-то допустимые значения, может выглядеть вполне заурядным.

Поэтому, необходимо иметь достаточно гибкую систему прав, позволяющую легко раздавать пользователям и группам самые разнообразные права на любые объекты. В том числе и на те из них, которые не являются объектами БД (например, права на запуск приложений).
При этом механизм раздачи прав должен быть максимально прост и доступен самим пользователям. Разумеется, только тем, у кого есть право на это.

Как правило, встроенный механизм безопасности SQL-сервера редко способен на такой подвиг, поэтому приходится разрабатывать систему прав самостоятельно.


Запрещено все, что не разрешено

Существует несколько принципиально различных схем раздачи и проверки прав. Первым делом, необходимо договориться о том, какая из них будет задействована.

Наиболее простым и надежным выглядит вариант, в котором запрещено все, что не разрешено явно.
Его несомненным достоинством является то, что для обеспечения безопасности не нужно прикладывать никаких усилий — ведь по умолчанию отсутствуют все права.

Также, чтобы не морочить никому голову, примем за основу, что права могут быть только разрешающего типа.
То есть то, что явно разрешено, уже нельзя запретить при помощи какого-нибудь флага и т.п.
Однажды выданное право можно отнять, только удалив факт его выдачи.


Пользователи и группы

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

Ниже приведены структуры таблиц People и Users (здесь и далее — синтаксис MS SQL):

create table [People]
(
    [Id] int not null identity primary key,
    [LastName] varchar(120) not null,
    [FirstName] varchar(120) not null,
    [MiddleName] varchar(120) not null,
    [Note] varchar(250)
)

create table [Users]
(
    [Id] int not null identity primary key,
    [Name] varchar(120) not null unique,
    [Note] varchar(250),
    [Person] int references [People]([Id])
)

Одному человеку может соответствовать как несколько пользователей, так и ни одного. Но пользователь может быть и виртуальным, не соотносящимся с каким-либо человеком.

Пользователь может включаться в несколько групп, поэтому понадобится еще две таблицы: Groups и UsersInGroups.

create table [Groups]
(
    [Id] int not null identity primary key,
    [Parent] int references [Groups]([Id]),
    [Name] varchar(120) not null unique,
    [Note] varchar(250)
)

create table [UsersInGroups]
(
    [Group] int not null references [Groups]([Id]),
    [User] int not null references [Users]([Id])
    constraint [UserInGroup] primary key ([Group], [User])
)

Так как группы могут входить одна в другую, таблица Groups сделана иерархической. Поле Parent указывает группу, в которую входит данная группа.

Если пользователь включен в какую-нибудь группу, значит, он включен и во все родительские группы.

Если группе даны права, то эти права есть у всех пользователей, входящих в эту группу.


Ресурсы

Ресурсы — это то, на что будут даваться права.

Что угодно может быть ресурсом — таблица, класс, отчет, приложение и т.п.
Ресурс может представлять как один объект (например, приложение), так и набор объектов (например, таблица с записями). Так как последний случай наиболее часто встречается в БД, то система прав в основном ориентирована именно на него.

create table [Resources]
(
    [Id] int not null identity primary key,
    [Parent] int references [Resources]([Id]),
    [Name] varchar(120) not null unique,
    [Note] varchar(250)
)

Поле Parent в таблице указывает ресурс, от которого наследован данный.

Если на ресурс даны права, значит такие же права даны и на все наследованные от него ресурсы.


Типы прав для ресурса

Для каждого ресурса надо задать типы прав.

Тип, собственно, и дает понять, какое действие разрешено этим правом. Например, тип «Insert» означает, что это — право на добавление (в пределах рассматриваемого ресурса).

create table [RightTypes]
(
    [Id] int not null identity primary key,
    [Resource] int not null references [Resources]([Id]),
    [Name] varchar(120) not null,
    [Note] varchar(250)
)

Для определенных видов ресурсов существуют стандартные типы прав, которые могут обрабатываться стандартным образом.
Например, для таблиц в БД можно определить следующие типы прав: «Select» (чтение), «Insert» (добавление), «Update» (изменение), «Delete» (удаление).
Для приложений достаточного одного типа прав — «Execute», который определяет право на запуск приложения.

Если для ресурса задан тип права, значит, такие же типы прав есть и у всех наследованных ресурсов.


Набор прав для ресурса

Для каждого ресурса должен быть определен присущий только ему набор прав. Права из этого набора и будут даваться группам либо отдельным пользователям.

Каждому праву необходимо присвоить наименование, уникальное в пределах ресурса. На это наименование и будет ориентироваться администратор при раздаче прав.

Действие, разрешаемое этим правом, определяется его типом. Например, тип «Delete» означает, что это — право на удаление (в пределах рассматриваемого ресурса).

Но бывает право на удаление любых объектов, а бывает — только тех, которые соответствуют заданным условиям. Чтобы не создавать отдельный тип права для каждого условия и не выносить это условие в код проверки прав, можно держать его непосредственно в таблице прав.
Поэтому, для ресурса может быть определено несколько прав одного типа, отличающихся только внутренними условиями (и именами, естественно).

create table [Rights]
(
    [Id] int not null identity primary key,
    [Resource] int not null references [Resources]([Id]),
    [Type] int not null references [RightTypes]([Id]),
    [Name] varchar(120) not null,
    [Note] varchar(250),
    [BeginCondition] varchar(2000),
    [BeginMessage] varchar(250),
    [EndCondition] varchar(2000),
    [EndMessage] varchar(250)
)

Если для проверки прав на добавление и удаление объекта достаточно только одного условия, то для проверки права на изменение существующего объекта необходимо иметь уже два условия — для проверки состояния объекта до изменения и после него.

Поле BeginCondition содержит условие для проверки состояния объекта до изменения (и до удаления).
Также, его можно использовать в качестве дополнительного условия к запросу для ограничения права на чтение.
Поле BeginMessage содержит описание условия BeginCondition. Это описание можно выдавать пользователю при невыполнении соответствующего условия.

К примеру, BeginCondition =

select top 1 0
from Objects
inner join Employees on ( Employees.Division = Objects.Division )
inner join Users on ( Users.Employee = Employees.Id )
where Users.Name = user_name()
and Objects.Id = @Id

где @Id — идентификатор объекта, для которого проверяются условия.
В этом случае BeginMessage = «Разрешено изменение только объектов своего подразделения».

Поле EndCondition содержит условие для проверки состояния объекта после изменения (и после добавления).
Поле EndMessage содержит описание условия EndCondition. Это описание можно выдавать пользователю при невыполнении соответствующего условия.

Для ресурсов, которые не соответствуют таблицам БД, характерно отсутствие таких условий.
Если условие не задано, то при проверке прав оно и не проверяется.

Если для ресурса определено какое-нибудь право, то такое же право должно существовать и для всех наследованных от него ресурсов. Для реализации этого потребуется специальный механизм дублирования и синхронизации прав в наследуемых ресурсах.


Раздача прав

Механизм раздачи прав будет заключаться в сопоставлении права и пользователя (либо группы пользователей).
Для этого потребуются две таблицы-связки, содержащие выданные права:

create table [UsersRights]
(
    [User] int not null references [Users]([Id]),
    [Right] int not null references [Rights]([Id])
    constraint [UserRight] primary key ([User], [Right])
)

create table [GroupsRights]
(
    [Group] int not null references [Groups]([Id]),
    [Right] int not null references [Rights]([Id])
    constraint [GroupRight] primary key ([Group], [Right])
)

Для того чтобы у кого-нибудь отнять право, достаточно удалить соответствующую запись из таблицы-связки.


Проверка прав

Проверка прав происходит довольно просто.

При попытке пользователя выполнить какое-нибудь действие с определенным ресурсом проверяется наличие прав соответствующего типа у этого пользователя (либо у группы, в которую он входит).

Если таких прав не обнаружено, то действие отменяется с выдачей стандартного сообщения об отсутствии прав на это действие.

Если на ресурс пользователю дано несколько прав одного типа, то ищется право без условия — тогда считается, что действие разрешено. В случае, когда нет прав без условий, проверяются по очереди все условия, пока не найдется то, которое будет выполнено.
Проверка условий производится следующим образом — выполняется строка запроса из соответствующего условия (до или после действия). Если запрос возвращает значение, то считается, что условие выполнено. Иначе переходим к проверке следующего условия.
Если не найдется ни одного права, условие которого будет выполнено, то будет выдано сообщение, соответствующего последнему невыполненному условию.

Для стандартного права на чтение (из таблицы) условие должно содержать не строку запроса на проверку объекта, а строку дополнительного условия, которое, будучи присоединено к условию where запроса на выборку ограничит видимость данных из таблицы.
Если пользователю дано несколько таких прав, то ищется право без условия — тогда к запросу на выборку не надо присоединять никаких дополнительных условий. В случае, когда нет прав без условий, все условия соединяются друг с другом через or, а затем прицепляются к запросу на выборку данных из таблицы.

Права можно дополнительно проверять в интерфейсе и блокировать/разблокировать соответствующие командные кнопки до совершения пользователем каких-либо действий.


Заключение

Разумеется, показанная система прав не претендует на полноту и универсальность. Однако, она позволяет легко решать вопросы с раздачей достаточно сложных вариантов прав и, что немаловажно, при этом дает возможность использовать тот же механизм для раздачи прав на произвольные объекты, которые не являются объектами базы данных.

При необходимости, описанный механизм может быть использован для проверки прав на отдельные поля (или группы полей).
Для этого достаточно, например, расширить стандартные типы прав для таблиц БД.

В варианте с объектно-реляционной БД проверка стандартных типов прав может быть легко вынесена в базовые классы для соответствующих видов ресурсов.
К примеру, проверку прав на чтение, добавление, изменение и удаление объектов можно определить в базовом классе для всех объектов, хранимых в БД.



© 2003 Сергей Виноградов
Hosted by uCoz