Глава 3. Что такое контекст
В предыдущей главе мы пришли к простой, но важной формуле:
Subject → Action → Object
Она показывает, что доступ — это отношение.
Но для сложной системы этого всё ещё недостаточно.
Одного объекта недостаточно, чтобы определить доступ к нему.
Одного субъекта тоже недостаточно.
Даже сама связь между ними не всегда однозначна.
Один и тот же пользователь может иметь разные права на один и тот же объект в зависимости от того, в рамках какой ситуации рассматривается запрос.
Эту совокупность условий и отношений будем называть контекстом.
3.1. Контекст — это не место
В обычной речи слово «контекст» часто означает окружение некоторого события.
Для архитектуры этого определения недостаточно.
Контекст доступа — это не просто место, где находится объект.
Это не обязательно:
Company
Project
Group
Tenant
Scope
Каждый из этих элементов может быть частью контекста, но ни один из них сам по себе контекстом не является.
Контекст возникает тогда, когда несколько фактов вместе определяют смысл отношения между субъектом, действием и объектом.
Например:
Alice
│
├── роль → Engineer
│
├── участник → Project A
│
└── член → Group G
Document D
│
├── принадлежит → Project A
├── опубликован → Group G
└── владелец → Bob
Каждая строка здесь описывает отдельный факт.
Но решение о доступе возникает только тогда, когда эти факты рассматриваются вместе.
3.2. Контекст отвечает на вопрос «в какой ситуации?»
Сравним два запроса.
Alice → READ → Document A
Alice → UPDATE → Document A
Субъект и объект одинаковы.
Но действия разные.
Теперь другой случай:
Alice → READ → Document A
Alice → READ → Document B
Субъект и действие одинаковы.
Но объекты разные.
Наконец, может измениться не субъект, не действие и не объект, а условия, при которых рассматривается запрос.
Например:
Alice → READ → Document A
может быть допустимо в рамках одного проекта и недопустимо в рамках другого.
Само утверждение Alice → READ → Document A не содержит информации, почему оно допустимо.
Контекст как раз и связывает отдельные факты в одну ситуацию, в которой это решение становится определённым.
3.3. Контекст состоит из фактов
Важно не превращать контекст в ещё один магический объект.
Контекст — это прежде всего набор значимых фактов.
Например:
Subject:
Alice
Action:
READ
Object:
Document A
Relevant facts:
Alice — member of — Group G
Group G — access to — Document A
Alice — role in — Project P
Document A — belongs to — Project P
Эти факты образуют контекст рассматриваемого запроса.
Другой запрос может использовать другой набор фактов.
Поэтому контекст нельзя понимать как заранее заданную папку, область или контейнер.
Он формируется относительно конкретной ситуации.
3.4. Почему одного факта недостаточно
Предположим:
Alice → Engineer
Этого недостаточно.
Нужно знать:
Engineer → какие действия?
Engineer → где действует роль?
Engineer → над какими объектами?
Теперь допустим, мы знаем:
Alice → Engineer → Project A
Это уже больше информации.
Но всё ещё может потребоваться:
Document A → опубликован в Project A
Или:
Document A → относится к Group G
Alice → member of → Group G
Каждый новый факт уточняет ситуацию.
Можно представить это как постепенное сужение множества возможных решений:
Пользователь
↓
роль
↓
область действия роли
↓
отношение к объекту
↓
свойства объекта
↓
требуемое действие
↓
решение
Контекст появляется не в одной точке.
Он формируется из фактов, которые имеют значение для этого решения.
3.5. Контекст связывает отношения
В предыдущей главе мы рассматривали отношения по отдельности.
Например:
Alice → member of → Group G
Group G → access to → Document A
Теперь можно увидеть, что эти отношения связаны.
Первое отвечает на вопрос:
К какой группе относится Alice?
Второе:
К каким объектам относится Group G?
Вместе они могут дать основание для ответа:
Может ли Alice читать Document A?
То есть контекст позволяет рассматривать не отдельные связи, а их совокупность.
Это принципиально важно.
Сложный доступ возникает не потому, что в системе существует много разрешений.
Он возникает потому, что решение зависит от большого количества взаимосвязанных фактов.
3.6. Контекст зависит от объекта
Один и тот же пользователь может находиться сразу в нескольких контекстах.
Alice может:
иметь роль Engineer в Project A;
иметь роль Reviewer в Project B;
быть участником Group G;
быть владельцем Document C.
Поэтому нельзя сказать просто:
«Контекст Alice такой-то».
Контекст всегда рассматривается относительно некоторого вопроса.
Например:
Can(Alice, READ, Document A)
и
Can(Alice, READ, Document B)
могут использовать разные части информации о Alice.
Контекст — не постоянная характеристика пользователя.
Он возникает относительно конкретного отношения доступа.
3.7. Контекст зависит и от действия
Это ещё одно важное свойство.
Для чтения объекта может быть достаточно одной связи:
Alice → member of → Group G
Для изменения может потребоваться другая:
Alice → role → Editor
Для утверждения:
Alice → role → Approver
Поэтому нельзя построить один универсальный контекст доступа и считать, что он одинаково отвечает на все вопросы.
Контекст определяется не только субъектом и объектом, но и тем, какое действие рассматривается.
3.8. Контекст может включать временные условия
Иногда отношения действуют не постоянно.
Например:
Alice → delegated access → Document A
может действовать только:
с 1 сентября
по 30 сентября.
Само отношение существует.
Но для запроса 15 сентября оно действительно, а для запроса 1 октября — уже нет.
Значит, время становится частью контекста.
То же самое относится к другим условиям:
состояние объекта;
тип операции;
организационная область;
источник запроса;
делегирование;
статус отношения.
Не каждое из них обязательно присутствует в каждой системе.
Но принцип остаётся тем же:
Контекст содержит те факты, без которых нельзя корректно интерпретировать отношение доступа.
3.9. Контекст не равен области действия
Здесь особенно легко сделать ошибку.
Допустим, у нас есть:
Project A
Project B
Можно сказать:
Роль Alice действует в Project A.
Это область действия роли.
Но сам контекст шире.
Он может включать:
Alice → роль → Engineer
Engineer → UPDATE
роль → действует → Project A
Document D → находится → Project A
Document D → опубликован → Group G
Alice → member of → Group G
Project A здесь только один из фактов.
Если заменить весь контекст одним Project A, мы потеряем остальные отношения.
Именно поэтому попытка решить сложную модель доступа одним полем scope быстро упирается в ограничения.
3.10. Контекст не является разрешением
Есть ещё одно принципиальное различие.
Контекст отвечает на вопрос:
Какие факты относятся к рассматриваемой ситуации?
Разрешение отвечает на другой вопрос:
Какое действие разрешено?
Например:
Context:
Alice — Engineer
Engineer — действует в Project A
Document D — находится в Project A
Document D — опубликован группе G
Alice — member of G
Из этого контекста правила системы могут вывести:
READ
UPDATE
Но контекст сам по себе не является ни READ, ни UPDATE.
Поэтому полезно разделять:
Факты
↓
Контекст
↓
Правила
↓
Эффективное разрешение
↓
Решение о доступе
Это разделение станет основой дальнейшей модели.
3.11. Контекст не обязан существовать как отдельная запись
Есть ещё одна тонкость.
Когда мы говорим:
«Контекст содержит такие-то факты»,
это не означает, что в базе данных должна существовать запись с названием Context.
Контекст — архитектурное понятие.
Он может быть представлен:
-
набором связанных объектов;
-
отношениями между ними;
-
атрибутами;
-
ролями;
-
публикациями;
-
состояниями;
-
производными данными.
В одной системе контекст может собираться непосредственно во время запроса.
В другой — часть его может быть рассчитана заранее.
В третьей — некоторые факты могут приходить из внешней системы.
Архитектурная идея от этого не меняется.
Нам важно не наличие сущности с названием Context, а способность системы определить какие факты имеют значение для конкретного решения.
3.12. Почему контекст становится центральным понятием
Теперь можно вернуться к исходной проблеме.
Модель:
User → Role → Permission
не исчезает.
Она становится частью более общей конструкции:
Subject
↓
Relationships
↓
Context
↓
Rules
↓
Effective Permission
↓
Access Decision
Это уже другой способ смотреть на безопасность.
Мы больше не спрашиваем:
Какие разрешения есть у Alice?
Мы спрашиваем:
Какие факты связывают Alice с данным объектом и действием, и какое решение следует из этих фактов?
В первом случае разрешение выглядит как свойство пользователя.
Во втором — как результат интерпретации ситуации.
Это гораздо ближе к тому, как устроены реальные системы.
3.13. Контекст — это часть модели объекта
Есть ещё более общий вывод.
Если доступ зависит от отношений между объектами, то контекст не может быть полностью отделён от самой модели предметной области.
Представим:
User
Group
Project
Document
и связи:
User → Group
Group → Project
Document → Project
Эти связи создавались не только ради безопасности.
Они описывают саму структуру системы.
Но в определённых правилах те же отношения становятся основаниями для доступа.
Получается:
Модель безопасности использует структуру отношений предметной области.
Это один из главных переходов от простой системы ролей к архитектуре, в которой доступ является частью общей модели данных.
3.14. Но контекст не сводится к предметной области
При этом было бы ошибкой считать, что любой факт предметной области автоматически относится к безопасности.
У объекта может быть:
название;
дата создания;
цена;
статус;
версия;
автор;
владелец;
проект;
группа.
Но только некоторые из этих фактов могут участвовать в конкретном решении.
Например, цена документа может вообще не иметь отношения к праву чтения.
А владелец — иметь.
Поэтому контекст — это не «все данные вокруг объекта».
Это значимая для данного решения часть окружающих фактов.
3.15. Формальное определение
Теперь можно дать рабочее определение.
Контекст доступа — это совокупность фактов и отношений, которые имеют значение для определения допустимости конкретного действия конкретного субъекта над конкретным объектом.
В сокращённом виде:
Context(Subject, Action, Object)
=
relevant facts and relationships
А решение можно представить как функцию:
Can(Subject, Action, Object)
=
Rules(Context(Subject, Action, Object))
Это ещё не алгоритм реализации.
Это модель мышления.
Она говорит, что решение о доступе нельзя рассматривать отдельно от ситуации, в которой оно принимается.
3.16. Контекст и границы системы
На этом этапе возникает следующий вопрос.
Если контекст состоит из множества фактов, то какие именно факты должны в него входить?
Можно ли просто взять все доступные отношения?
Очевидно, нет.
Система должна иметь способ определить границы контекста.
Например, пользователь может одновременно:
состоять в десяти группах;
участвовать в пяти проектах;
иметь несколько ролей;
владеть десятками объектов.
Но конкретный запрос касается одного объекта и одного действия.
Следовательно, контекст должен быть не только богатым, но и ограниченным.
Нужно понимать:
-
какие отношения относятся к объекту;
-
какие отношения относятся к субъекту;
-
какие из них связаны между собой;
-
какие правила применимы;
-
какие границы нельзя пересекать.
И здесь мы приходим к следующей проблеме.
Контекст может содержать несколько независимых измерений.
Он может быть одновременно связан:
с компанией;
с проектом;
с группой;
с объектом;
с субъектом;
с ролью;
с отношением;
с действием.
Попытка представить всё это одним идентификатором неизбежно теряет информацию.
Поэтому в следующей главе мы разберём, почему контекст нельзя свести к одному scope.