Глава 4. Почему контекст нельзя свести к одному scope
В предыдущей главе мы определили контекст как совокупность фактов и отношений, значимых для конкретного решения о доступе.
Но возникает естественный вопрос:
А нельзя ли упростить эту модель и описывать контекст одним
scope?
Во многих системах именно так и пытаются сделать.
У пользователя есть scope.
У объекта есть scope.
У разрешения есть scope.
А дальше система проверяет, совпадают ли они.
Такой подход действительно работает для простых моделей.
Проблема начинается тогда, когда одно и то же действие зависит сразу от нескольких независимых отношений.
4.1. Что такое scope
В этой книге слово scope используется в архитектурном смысле — как область действия правила, отношения или разрешения.
Это значение не следует смешивать со scope в OAuth 2.0 (Open Authorization 2.0) и связанных с ним механизмах OpenID Connect (OIDC).
Там scope обозначает набор запрашиваемых или предоставляемых полномочий, например:
read:documents
или:
write:users.
Это другой уровень модели.
В OAuth 2.0 scope описывает, какие полномочия предоставлены клиенту или субъекту.
В рассматриваемой здесь модели scope описывает, где или в каких границах действует некоторое правило или отношение.
Поэтому scope в этой главе — не OAuth 2.0 scope из токена.
И проблема не в том, что scope — плохое понятие.
Проблема возникает тогда, когда область действия пытаются использовать как замену всему контексту.
4.2. Scope отвечает только на один вопрос
Scope отвечает примерно на вопрос:
Где действует это правило?
Например:
Permission: READ
Scope: Project P
означает, что некоторое разрешение действует в пределах P.
Но остаются другие вопросы:
-
кто выполняет действие;
-
над каким объектом;
-
какое именно действие выполняется;
-
какое отношение связывает субъекта с этой областью;
-
есть ли дополнительные ограничения.
Scope отвечает только на часть этих вопросов.
4.3. Объект и scope — не одно и то же
Пусть правило действует в некоторой области S.
Это ещё не означает, что конкретный объект D автоматически доступен субъекту.
Нужно установить отношение объекта к этой области.
Условно:
Subject
↓
Scope S
↓
Object D
Само существование S не отвечает на вопрос:
Can(Subject, Action, D).
Нужно ещё знать, как D связан с S и какие правила действуют для этого действия.
4.4. Один объект может участвовать в нескольких областях
Объект может одновременно находиться в нескольких структурах.
Например, один объект может быть связан:
-
с организацией;
-
с проектом;
-
с группой;
-
с другим объектом;
-
с субъектом-владельцем.
Если каждую такую границу назвать scope, получится несколько областей.
Object D
├── Scope A
├── Scope B
└── Scope C
Уже этого достаточно, чтобы увидеть проблему модели «один объект — один scope».
Объект может иметь несколько независимых областей действия.
4.5. Разные отношения могут иметь разные области
Пусть субъект имеет одну роль в области A и другую роль в области B.
При этом один и тот же объект связан с обеими областями.
Тогда решение зависит не только от самого scope, но и от того, какое отношение действует в этом scope.
Условно:
Subject
├── relation R1 ──→ Scope A
└── relation R2 ──→ Scope B
Object
├── relation ─────→ Scope A
└── relation ─────→ Scope B
Нельзя заменить эту структуру одним значением:
scope = X.
Потому что исчезает информация о характере отношений.
4.6. Scope не определяет субъекта
Одна и та же область может содержать множество субъектов.
Но разные субъекты могут иметь разные отношения с этой областью.
Например:
Alice → role R1 → S
Bob → role R2 → S
Carol → member → S
Если просто записать:
scope = S
мы ничего не узнали о том, что именно разрешено Alice, Bob и Carol.
Следовательно:
Scope ≠ Subject
И scope нельзя использовать как замену отношения субъекта с областью.
4.7. Scope не определяет объект
Та же область может содержать множество объектов:
S
├── D1
├── D2
├── D3
└── D4
Но доступ к D1 и D2 может различаться.
Например, D1 может быть опубликован для всех участников области, а D2 — только для владельца.
Поэтому:
Scope ≠ Object
Scope задаёт границу, но не описывает все свойства конкретного объекта внутри неё.
4.8. Scope не определяет действие
Даже если субъект имеет доступ к объекту в определённой области, остаётся вопрос:
Что именно ему разрешено делать?
Например:
READ
может быть разрешён, а:
UPDATE
— нет.
Поэтому одного scope недостаточно и здесь:
Scope
+
Action
дают больше информации, чем один scope.
4.9. Контекст имеет несколько независимых измерений
Для одного решения могут одновременно иметь значение:
-
субъект;
-
объект;
-
действие;
-
область действия;
-
роль;
-
членство;
-
владение;
-
публикация;
-
состояние объекта;
-
другие ограничения.
Это разные измерения ситуации.
Условно:
Context
│
┌────────────┼────────────┐
↓ ↓ ↓
Subject Object Action
│ │ │
└─────┬──────┴──────┬─────┘
↓ ↓
Relations Scope
Ни одно из этих измерений не обязано быть главным.
Их значение определяется конкретным правилом.
4.10. Нельзя просто сделать составной scope
Можно попытаться решить проблему иначе:
scope =
Company A /
Project P /
Group G
На первый взгляд проблема исчезает.
Но на самом деле мы просто упаковали несколько измерений в одну строку.
Сразу возникают новые вопросы:
-
что произойдёт при изменении одного элемента;
-
обязаны ли все элементы существовать;
-
независимы ли они;
-
как описать альтернативные отношения;
-
как представить несколько областей одновременно;
-
как учитывать свойства, которые вообще не являются областями.
Составной scope становится контейнером для контекста.
Но от этого он контекстом не становится.
4.11. Не все элементы контекста являются областями
Некоторые факты вообще невозможно естественно представить как scope.
Например:
Object.state = REVIEW
или:
Subject owns Object
или:
Action = APPROVE
Это не области действия.
Это состояние объекта, отношение и действие.
Поэтому модель:
Context = Scope
изначально ограничивает возможное содержание контекста.
4.12. Контекст зависит от конкретного действия
Предположим, нужно определить:
Can(Alice, READ, D)
и затем:
Can(Alice, APPROVE, D).
Набор значимых фактов может различаться.
Для чтения может иметь значение публикация объекта.
Для утверждения может дополнительно иметь значение состояние объекта и роль субъекта.
Следовательно:
Context(Alice, READ, D)
не обязан совпадать с:
Context(Alice, APPROVE, D)
Это одна из причин, почему контекст нельзя превратить в постоянное свойство объекта или пользователя.
4.13. Контекст зависит не только от субъекта
Нельзя сказать:
У Alice такой-то контекст.
Контекст возникает для конкретного решения.
У Alice может быть совершенно разная ситуация при работе с разными объектами:
Alice → READ → D1
Alice → READ → D2
Даже если субъект и действие одинаковы, контекст может отличаться.
Потому что отличаются объекты и отношения вокруг них.
4.14. Контекст зависит не только от объекта
Обратное тоже верно.
Для одного объекта:
Alice → READ → D
Bob → READ → D
контексты могут различаться.
Объект один.
Действие одно.
Но отношения субъектов с объектом и окружающей моделью разные.
Поэтому контекст нельзя определить только по объекту.
4.15. Что тогда представляет собой контекст
Контекст — это не одно поле и не один идентификатор.
Это релевантная часть ситуации, в которой принимается конкретное решение.
Можно записать:
Context(Subject, Action, Object) = Relevant Facts and Relations
А само решение:
Can(Subject, Action, Object) = Rules(Context(Subject, Action, Object))
В такой модели scope остаётся полезным понятием.
Если некоторое правило действует только в определённой области, эта область является частью контекста.
Но она не исчерпывает его.
Поэтому:
Scope ⊂ Context
там, где scope присутствует как элемент модели.
И:
Context ≠ Scope.
4.16. Главный вывод
Scope — полезный способ описать границу действия отношения, правила или разрешения.
Но он отвечает только на один вопрос:
Где действует правило?
Контекст должен отвечать на более широкий вопрос:
В какой ситуации это правило должно применяться к данному субъекту, действию и объекту?
Поэтому контекст может включать:
-
субъекта;
-
объект;
-
действие;
-
отношения;
-
области действия;
-
состояние;
-
владение;
-
публикацию;
-
другие релевантные факты.
Именно поэтому нельзя построить универсальную модель доступа, просто присвоив каждому объекту и пользователю по одному scope.
Нам нужно сначала определить объект, затем разобраться, какие факты и отношения связывают его с остальной системой, и только после этого определить, какие из этих отношений становятся основаниями доступа.
С этого начинается следующая часть модели.