понедельник, 8 декабря 2008 г.

еще немного о деревьях в sql-2 (права доступа)

Конечно, какое же дерево без разделения прав доступа!
Похоже это будет на права доступа в ОС Netware, т.е. элементы списка права доступа наследуются "на лету" с родительских объектов, соответственно, никаких тормозов при изменении прав доступа, но расчет эффективных прав будет требовать немного больше затрат. Реализованы также права доступа для "Владельца" объекта (смотри поле "Owner" в таблице "Objects").

Чтобы было понятнее, надо упомянуть, что в таблице "Objects" есть объекты с "предопределенными" ID:
пользователь "Системный администратор", ID=-100
псевдопользователь "Владелец объекта", ID=-102
тип связи "Группа-Пользователь", ID=-300
Нам понадобятся таблицы "Objects" и "Rights" с предыдущего поста и две хранимые процедуры:
CREATE TABLE "Objects" (
ID            TIDNOTNULL NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"ClassID"     TIDNOTNULL NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"ChildCount"  TIDNOTNULL DEFAULT  0 NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"Creator"     TIDNOTNULL DEFAULT -100 NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"Owner"       TIDNOTNULL DEFAULT -100 NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"mUser"       TIDNOTNULL DEFAULT -100 NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"OrderPos"    TIDNOTNULL DEFAULT 0 NOT NULL /* TIDNOTNULL = INTEGER NOT NULL */,
"mTime"       TCURTIMESTAMP DEFAULT current_timestamp NOT NULL /* TCURTIMESTAMP = TIMESTAMP DEFAULT current_timestamp */,
"cTime"       TCURTIMESTAMP DEFAULT current_timestamp NOT NULL /* TCURTIMESTAMP = TIMESTAMP DEFAULT current_timestamp */,
"ParentID"    TID /* TID = INTEGER */,
"State"       TID /* TID = INTEGER */,
"Name"        TSTR50 /* TSTR50 = VARCHAR(50) */,
"Desc"        TSTR200 /* TSTR200 = VARCHAR(200) */,
GUID          TGUID /* TGUID = CHAR(38) */
);

CREATE TABLE "Rights" (
"ObjID"        TID NOT NULL /* TID = INTEGER */,
"UserID"       TID NOT NULL /* TID = INTEGER */,
"Permissions"  INTEGER NOT NULL
)

вспомогательная процедура pdm_GetACL

CREATE PROCEDURE "pdm_GetACL" (
"AObjID" integer,
"AUserID" integer,
"AGroupID" integer)
returns (
"ObjID" integer,
"TokenID" integer,
"Permissions" integer)
as
declare variable "tmpID" integer;
declare variable "OwnerID" integer;
begin
/*
Если AUserID и AGroupID равны null, то возвращаем полный ACL объекта

Если AGroupID is NULL, то возвращаем в иерархии родителей
первый ACE непосредственно для пользователя.

Если AGroupID is NOT NULL, то возвращаем первый ACE для указанной группы пользователя
в иерархии родителей объекта
Если встречается ACE непосредственно для пользователя, то выходим
ничего не возвращая, тем самым отсекаем ACE для групп, расположенные "дальше",
чем ACE для пользователя
*/
"ObjID" = "AObjID";
/*Если AUserID и AGroupID равны null, то возвращаем полный ACL объекта*/
if (("AUserID" is null) and ("AGroupID" is null)) then begin
for select "UserID", "Permissions" from "Rights"
where "ObjID"=:"AObjID"
into :"TokenID", :"Permissions" do suspend;
exit;
end

"Permissions" = 0;
select "Owner" from "Objects" where id=:"AObjID" into :"OwnerID";

while ("ObjID" is not null) do begin
--ищем ACE для пользователя
for select "UserID", "Permissions" from "Rights"
where "ObjID"=:"ObjID" and "UserID" = :"AUserID"
into :"TokenID", :"Permissions" do begin
if ("AGroupID" is null) then suspend;
exit;
end
--ищем ACE для владельца объекта
if ("AUserID" = "OwnerID") then
for select "UserID", "Permissions" from "Rights"
where "ObjID"=:"ObjID" and "UserID" = -102
into :"TokenID", :"Permissions" do begin
if ("AGroupID" is null) then suspend;
exit;
end
--ищем ACE для группы
if ("AGroupID" is not null) then
for select "UserID", "Permissions" from "Rights"
where "ObjID"=:"ObjID" and "UserID" = :"AGroupID"
into :"TokenID", :"Permissions" do begin
suspend;
exit;
end
-- получение следующего родителя
"tmpID" = "ObjID";
"ObjID" = NULL;
select "ParentID" from "Objects" where id = :"tmpID" into :"ObjID";
end
end


Основная процедура, возвращает эффективные права доступа к заданному объекту для заданного пользователя
CREATE PROCEDURE "pdm_GetObjPermissions" (
"AObjID" tid,
"AUserID" tid)
returns (
"Permissions" integer)
as
declare variable "tmpObjID" tid;
declare variable "tmpUserID" tid;
declare variable "tmpPerm" integer;
begin
/*
ACL - access control list (список прав доступа)
ACE - access control entity (элемент списка прав доступа),
это одна строка в таблице "Rights"(ObjID, UserID, Permissions)
В поле Permissions хранится битовое представление разрешений
(acRead=1;acWrite=2;acDelete=4;acReadChilds=8;acWriteChilds=16;acChangePermissons=32);
Правила расчета эффективных прав доступа на объект:
Если у объекта есть ACE непосредственно для пользователя, то возвращается
значение Permissions этого ACE.
Иначе права доступа наследуются следующим образом:
Ищется ближайший ACE для пользователя в родительской иерархии.
Для каждой группы, в которую входит пользователь, ищется ближайшее ACE
вверх по родительской иерархии, но ниже ACE для пользователя.
Т.е. если у какого-либо родителя существует ACE для пользователя, то такой
ACE имеет более высокий приоритет и "отсекает" все ACE для групп, расположенные
выше и на одинаковом уровне с ним.
Общее правило: ближе расположенный ACE для одного и того же пользователя или группы
имеет приоритет.
Найденные ACE для всех групп пользователя побитового суммируются друг с другом
и с ранее найденным ACE для пользователя.

*/
"Permissions" = 0;
/*Проверяем ACE непосредственно для пользователя*/
for select "ObjID", "Permissions"
from "pdm_GetACL"(:"AObjID",:"AUserID",NULL) into :"tmpObjID", :"Permissions" do
if ("tmpObjID" = "AObjID") then exit;
/*Проверяем ACE для групп пользователя*/
for select "ParentID" from "Links"
where "ChildID"=:"AUserID" and "LinkTypeID"=-300 into :"tmpUserID" do begin
for select "Permissions" from "pdm_GetACL"(:"AObjID",
:"AUserID", :"tmpUserID") into :"tmpPerm" do
"Permissions" = bin_or("Permissions", "tmpPerm");
end
end

И для наглядности, картинка:

Комментариев нет: