Эта страница ещё не переведена — показана английская версия.
Access & Roles
Роли собираются из разрешений, а не зашиваются в код. Атомарные разрешения складываются в роли. Роль ограничивает доступ к данным вплоть до строки и столбца и решает, какие интерфейсы модулей сборка вообще увидит. Это заменяет деление ключей на клиентские и серверные: учётные данные указывают только на учётную запись, а её роли определяются при каждом запросе.
Когда это нужно
- Нужны учётные данные с более узкими правами, чем «клиент» или «сервер». За ними при каждом запросе определяются роли (role), собранные из разрешений.
- Доступ к данным должен останавливаться на уровне строк и столбцов: ограничение по региону, маскирование персональных данных, подрядчики только с правом чтения.
- Сборка должна видеть только те интерфейсы, которые открывает её роль: у посетителя комнаты вызовов, чтобы выгнать игрока или закрыть комнату, просто нет.
- Кнопки в интерфейсе должны становиться неактивными ровно тогда, когда действие запрещено:
CanIпроверяет ту же политику, которую затем применит сервер. - Не нужно, если готовые пресеты (
player,room-owner,seller, …) уже подходят вашим акторам. Все модули учитывают их по умолчанию, а полный список есть на странице Основные понятия.
Кто что делает
| Актор | Что делает на этой странице |
|---|---|
operator | объявляет роли и политики, задаёт ограничения по строкам и столбцам, выдаёт роли, выпускает ключи |
match-organizer | организаторы турнира: у них ключ с составной ролью, они допускают участников к матчу, но оформлять возвраты не могут |
любой актор | перед действием проверяет CanI и видит только открытые ему интерфейсы |
Коротко
entry-validator с ограничениями по строкам и столбцам, выдаём роль и проверяем CanI перед действием[Role("entry-validator")]
public class EntryValidator
{
[Allow(Rooms.Membership.Administer)] public Permit GateEntries;
[Allow(Data.Records.Read, table: "player_profile", rows: "banned == false",
columns: "id, display_name")] public Permit SeeProfiles;
}
await PlayServ.Access.Grant(staffId, Roles.EntryValidator, Roles.MatchOrganizer);
var key = await PlayServ.Access.IssueKey(staffId); // the credential names no roles
// any actor, before attempting an operation:
if (await PlayServ.Access.CanI(Commerce.Orders.Administer)) Hud.ShowRefund();@Role('entry-validator')
export class EntryValidator {
@Allow(Rooms.membership.administer) gateEntries: Permit;
@Allow(Data.records.read, { table: 'player_profile', rows: 'banned == false',
columns: ['id', 'display_name'] }) seeProfiles: Permit;
}
await playserv.access.grant(staffId, Roles.entryValidator, Roles.matchOrganizer);
const key = await playserv.access.issueKey(staffId); // the credential names no roles
// any actor, before attempting an operation:
if (await playserv.access.canI(Commerce.orders.administer)) hud.showRefund();@role("entry-validator")
class EntryValidator:
gate_entries = allow(rooms.membership.administer)
see_profiles = allow(data.records.read, table="player_profile",
rows="banned == false", columns=["id", "display_name"])
await playserv.access.grant(staff_id, roles.ENTRY_VALIDATOR, roles.MATCH_ORGANIZER)
key = await playserv.access.issue_key(staff_id) # the credential names no roles
# any actor, before attempting an operation:
if await playserv.access.can_i(commerce.orders.administer):
hud.show_refund()Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
// declarations compile into the same pushed model — playserv push from the UE project or CI
USTRUCT(PSRole = "entry-validator")
struct FEntryValidator
{
GENERATED_BODY()
UPROPERTY(PSAllow = (Atom = "Rooms.Membership.Administer"))
FPSPermit GateEntries;
UPROPERTY(PSAllow = (Atom = "Data.Records.Read", Table = "player_profile",
Rows = "banned == false", Columns = "id, display_name"))
FPSPermit SeeProfiles;
};
// granting is an operator act; a build checks what its identity resolves to
const FPSActor Me = Client->Whoami(); // which interfaces this actor unlocks
Client->Access->CanI(TEXT("Commerce.Orders.Administer"),
TPSOnResult<bool>::CreateWeakLambda(this, [this](const TPSResult<bool>& Result)
{
if (!Result.HasValue()) { return; }
if (Result.Value()) { Hud->ShowRefund(); }
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.// the same `[Role]` / `[Allow]` declaration as the server tab, on the Unity 2021.3 runtime
var me = playserv.Whoami(); // which interfaces this actor unlocks
if (await playserv.Access.CanI(Commerce.Orders.Administer)) hud.ShowRefund();Атомарное разрешение (permission atom) — это пара из ресурса и одного из четырёх глаголов: read, write, execute, administer. Набор глаголов фиксирован. Если случай в него не укладывается, делят ресурс, а не расширяют список. Поэтому проверка чужого входа — это Rooms.Membership.Administer, а не отдельный глагол ValidateEntry. Действовать над участием другого актора в комнате — это администрирование, а войти самому — это Rooms.Membership.Write на том же ресурсе.
Модель
ACL для данных — это роль × операция × предикат строк × маска полей. Модель одна и та же, где бы её ни задали: в коде, через API или в сетке ролей в админ-панели.
Из чего строится доступ.
| Термин | Что это |
|---|---|
атомарное разрешение | одна пара ресурс × глагол. Глаголов четыре: read (получение, выборка, подписка), write (создание, изменение, удаление и действия за себя — войти, выйти), execute (вызов функции, применение способности) и administer (действия над другими: выгнать, закрыть, принудительно сменить состояние) |
роль | именованный набор атомарных разрешений. Может включать другую роль |
пресет роли | поставляется готовым, собран из атомарных разрешений и у уже задеплоенных потребителей продолжает работать без изменений. Это отправная точка, а не ограничение: проект объявляет собственные роли из тех же атомарных разрешений |
предикат строк | какие строки — булев предикат над значениями сессии |
маска полей | какие поля — объявляется отдельно для каждой роли и каждой операции (operation) |
Что стоит за учётными данными.
| Учётные данные | Что они открывают |
|---|---|
ключ игрока | его несёт сборка движка. Игрок (player) за ним появляется при входе, а сборка видит строки cl в каждой таблице операций |
ключ хоста | он есть у выделенного сервера или мастер-клиента, и его роли открывают строки mc |
загруженный код | выполняется с ролью проекта backend-service — именно её проверяет Authoritative = true у таблицы лидеров (leaderboard) |
зарегистрированный хук | ничего сверх не даёт: функция сохраняет роль, с которой её задеплоили |
Легенда меток (fn / cl / mc / adm) описана на странице Основные понятия.
Что верно для любой проверки.
| Всегда | Что это |
|---|---|
учётные данные | не содержат списка ролей: они указывают на учётную запись (identity), а роли определяются на сервере при каждом запросе. Отзыв роли сразу сбрасывает закэшированный результат и не ждёт, пока истечёт объявленный срок устаревания |
делегирование | меняет область, но не возможности. Когда вы действуете от имени игрока, меняется то, какие строки видны и кому приписывается запись. Операций, которых у актора не было, от этого не появляется |
глагол | отвечает на вопрос, какое это действие, а предикат — какие это строки. Если два случая различаются только тем, чья это строка, разница в предикате. Если отличается само действие, это другая операция и, возможно, другой глагол. Поэтому «выгнать» — это administer, а не write с широким предикатом |
владелец | всегда видит себя, что бы ни говорил предикат |
видимость | — не про безопасность: оптимизация каналов и разрешение (permission) — разные механизмы, и одно не заменяет другое |
выключенный модуль | не имеет интерфейса. Включён модуль или нет — свойство сборки, поэтому для выключенного модуля кодогенерация ничего не создаёт, и недоступный вызов даёт ошибку компиляции, а не отказ во время выполнения |
интерфейс модуля | зависит от актора, а не от стороны (см. Кто принимает решения). Сборка room-visitor видит вход, выход и чтение, сборка room-owner — вдобавок исключение игроков, закрытие и настройку комнаты, а whoami сообщает, какие интерфейсы открывает текущий актор |
Кто выдаёт роль. Выдать или отозвать роль игрока, а также объявить в проекте роль по умолчанию для нового игрока можно через операции модуля Auth & Players. Этот модуль отвечает за учётные записи, а роль определяется по той из них, на которую указывают учётные данные.
Ошибки
- Если объект скрыт предикатом, ответ —
not found, а неforbidden: иначе сам отказ сообщил бы вызывающему, что объект существует. - Если у вызывающего нет нужного права, ответ —
forbidden, когда существование объекта не секрет. В ответе сказано, чего именно не хватило, а не просто «нельзя». - Поле вне маски в ответе отсутствует, а не приходит пустым: иначе пустое значение нельзя было бы отличить от скрытого.
- Роль, которая включает саму себя напрямую или через цепочку, — ошибка конфигурации. Такое объявление отклоняется сразу, а не разбирается во время выполнения.
- Делегирование никогда не расширяет возможности: вызов, который актор не мог бы сделать от своего имени, отклоняется и тогда, когда он делает его от имени игрока.
Ограничения
Конкретные числа появятся вместе с главой об ограничениях платформы, а что происходит на каждом пределе, зафиксировано уже сейчас.
- Размер выборки по предикату строк ограничен, и модель ACL объявляет этот предел заранее, а не узнаёт о нём по ходу. Если чтение выходит за предел, в ответе будет столько строк, сколько позволяет предел, и пометка о том, что результат обрезан. Молча укороченной страницы не бывает.
- Срок устаревания вычисленного разрешения объявлен, но отзыв его не ждёт: кэш сбрасывается сразу.