Перейти к основному содержимому
Как работает SDK

Эта страница ещё не переведена — показана английская версия.

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

Как объявлять через атрибуты в 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 объявляет этот предел заранее, а не узнаёт о нём по ходу. Если чтение выходит за предел, в ответе будет столько строк, сколько позволяет предел, и пометка о том, что результат обрезан. Молча укороченной страницы не бывает.
  • Срок устаревания вычисленного разрешения объявлен, но отзыв его не ждёт: кэш сбрасывается сразу.