Глава 20. Проверка доступа к объекту
20.1. От модели к конкретному запросу
До этого момента мы рассматривали модель доступа как систему фактов, отношений и правил.
Теперь появляется конкретный запрос:
User A
↓
READ
↓
Object X
Система должна ответить:
ALLOW
или:
DENY
Но этот ответ не возникает непосредственно из одной записи о пользователе.
К моменту проверки уже должны существовать необходимые производные факты.
В общем случае цепочка выглядит так:
Subject
↓
Relevant facts
↓
Context
↓
Access grounds
↓
Rules
↓
Effective permission
↓
Object authorization
И только после этого можно перейти к данным объекта.
20.2. Эффективного разрешения недостаточно
Предположим, пользователь имеет:
READ
Это ещё не означает, что он может читать любой объект.
Нужно знать область действия этого права.
Например:
READ in Project A
не означает:
READ everywhere
Поэтому проверка должна сопоставлять две стороны:
permission side
и:
object side
Одна сторона говорит:
Какие права есть у субъекта в данной области?
Другая:
В каких областях и с какими правами объект доступен?
Только их пересечение позволяет принять решение.
20.3. Публикация находится на стороне объекта
Публикация объекта — это не разрешение пользователя.
Она сообщает:
В какой области объект опубликован и какие права допускает эта публикация?
Например:
Object X
↓ published in
Project P
Это ещё не означает:
User A → READ → Object X
Нужно дополнительно установить, имеет ли пользователь эффективное разрешение в Project P.
Таким образом:
Object publication
+
Subject effective permission
↓
Object authorization
20.4. Владелец — отдельное основание
Владелец объекта представляет особый случай.
В текущей модели Guardian право владельца проверяется независимо от effective_user_permission.
Концептуально:
OWNER scope
↓
owner_id = current user
↓
permission mask
↓
required action
Это важно.
Владелец не обязан иметь отдельную роль только для того, чтобы получить собственные права на объект.
Тем самым сохраняется различие:
Creator ≠ Owner ≠ Effective permission
Создатель может передать владение.
После этого право должно следовать за владельцем, а не за created_by.
20.5. Обычная ветка проверки
Для обычного доступа текущая модель Guardian использует две проекции:
object_scope
+
effective_user_permission
Связь выполняется по:
company_id
user_id
scope_type
scope_id
а затем проверяется пересечение масок.
Упрощённо:
User permission
&
Object publication ceiling
&
Required permission
!= 0
То есть:
(eup.permission_mask
&
os.permission_mask
&
required_mask) != 0
Это конкретное правило Guardian, а не универсальная формула всех систем доступа.
20.6. Упрощённый Guardian-псевдокод
Текущую проверку можно представить так:
function hasObjectPermission(objectId, requiredMask):
identity = currentIdentity()
if exists OWNER scope where
scope.objectId == objectId
and scope.companyId == identity.companyId
and scope.scopeType == OWNER
and scope.scopeId == identity.userId
and (scope.permissionMask & requiredMask) != 0:
return ALLOW
if exists objectScope where
objectScope.objectId == objectId
and objectScope.scopeType != OWNER
and exists effectiveUserPermission where
effectiveUserPermission.companyId
== objectScope.companyId
and effectiveUserPermission.userId
== identity.userId
and effectiveUserPermission.scopeType
== objectScope.scopeType
and effectiveUserPermission.scopeId
== objectScope.scopeId
and (
effectiveUserPermission.permissionMask
&
objectScope.permissionMask
&
requiredMask
) != 0:
return ALLOW
return DENY
Это не буквальная копия SQL.
Это сокращённое представление его архитектурной логики.
20.7. Где здесь группа
На этом месте легко сделать неправильный вывод.
Можно предположить:
hasObjectPermission
↓
effective_user_group
↓
check membership
Но текущая модель Guardian устроена иначе.
effective_user_group используется раньше.
Он участвует в построении эффективных разрешений, в частности в обработке Scope Barrier и расширении прав по иерархии.
После построения:
effective_user_permission
runtime-проверка объекта использует уже этот результат.
Поэтому:
effective_user_group
не является непосредственным runtime-фильтром в has_object_permission().
20.8. Почему это разделение важно
Если выполнять всю логику отношений во время каждого запроса, система должна каждый раз заново определять:
к каким группам относится пользователь;
какие проекты связаны с группами;
какие роли действуют;
какие публикации существуют;
какие ограничения применимы.
Это дорого и усложняет проверку.
В Guardian большая часть этой работы происходит заранее.
Поэтому запрос видит уже подготовленные:
effective_user_permission
object_scope
а не весь граф исходных отношений.
20.9. Объектная авторизация ещё не является доступом к данным
Даже если:
has_object_permission(...) = ALLOW
это не означает:
можно читать любые физические строки,
относящиеся к объекту
Логический объект и физические данные могут иметь разные границы.
Один объект может быть представлен:
table A
table B
table C
или несколькими строками одной таблицы.
Поэтому нужна ещё одна граница:
Object authorization
↓
Data enforcement
20.10. Главный вывод
Проверка доступа к объекту — это не поиск пользователя в ACL.
Это сопоставление:
Subject effective permission
×
Object security scope
×
Requested action
В Guardian:
effective_user_permission
+
object_scope
↓
has_object_permission()
При этом владелец проверяется отдельной веткой.
Но даже положительное решение на уровне объекта не отменяет последующую защиту физических данных.