Overwatch Tournaments нужны функциональные cookie, чтобы сохранять вход и язык интерфейса. Аналитические cookie необязательны и только показывают, как используются страницы, — подробнее в политике конфиденциальности.

Перейти к содержимому

Воркспейсы и права

Воркспейс — корень аренды в OWT: сообщество со своим сайтом, своими турнирами, своими участниками и своими правами. Почти каждая доменная строка несёт workspace_id — напрямую либо через турнир или запись участника. Эта статья объясняет, как хост превращается в воркспейс, как воркспейс попадает в запрос к API, что видно из чужого воркспейса и как устроены роли, права и скоупы ключей.

Хост определяет сообщество

Есть ровно два взаимоисключающих способа привязать хост к воркспейсу.

Поддомен платформенной зоны. Метка длиной 1–63 символа из a-z, 0-9 и дефиса, без дефиса в начале и в конце, не из списка зарезервированных: www, api, auth, admin, app, assets, static, cdn, mail, ws. Многосегментная метка не принимается, апекс зоны — это сама платформа, а не арендатор.

Собственный домен. Полное доменное имя минимум из двух меток, уникальное и заведомо не под платформенной зоной. Домен обслуживается только после подтверждения владения записью DNS TXT: до этого он не резолвится ни во что — политика отказа по умолчанию.

Резолв доступен публично и без аутентификации:

GET /api/v1/workspaces/by-host?host=example.org HTTP/1.1
{"workspace_id": 7, "slug": "example"}

Ответ null означает «хост не назван, некорректен или не принадлежит ни одному воркспейсу» — в том числе если собственный домен добавлен, но ещё не подтверждён. Сайт использует тот же вызов: хост, похожий на арендаторский, но не резолвящийся, отдаёт 404, а временный сбой резолва — 503 с заголовком Retry-After, чтобы живой арендатор не исчезал из-за одной неудачной попытки.

Маршруты API не привязаны к хосту. Сайт сообщества на поддомене или на собственном домене отвечает по тем же путям /api/..., что и https://owt.craazzzyyfoxx.me.

У воркспейса есть ещё два независимых флага, которые легко перепутать. is_active выключает воркспейс, а is_hidden убирает его только из публичного каталога и из чужих списков — прямой доступ по slug, поддомену или подтверждённому домену при этом сохраняется. Отдельная ось — уровень доверия verification_status (unverified, verified, trusted), который меняет только суперпользователь: он управляет доступом к тяжёлым вычислениям и попаданием в публичный каталог.

Воркспейс в запросе

Хост определяет сообщество для сайта, но сам запрос к API называет воркспейс явно — параметром workspace_id. Доменные чтения без него отвечают 400: отдать строки всех арендаторов сразу было бы утечкой между сообществами, поэтому отсутствие скоупа считается ошибкой, а не «читай всё».

GET /api/v1/tournaments?workspace_id=7 HTTP/1.1
Authorization: Bearer <токен или API-ключ>

Одно послабление шлюз делает сам: если предъявленная учётка привязана ровно к одному воркспейсу — а API-ключ привязан всегда, — и workspace_id в запросе не передан, шлюз подставит идентификатор из самой учётки. Явно переданное значение всегда важнее. Если у учётки воркспейсов несколько, шлюз не угадывает и оставляет 400 в силе.

Подстановка не расширяет права: она называет воркспейс, который учётка и так держит, а проверку прав в нём всё равно выполняет доменный воркер.

Что видно из чужого воркспейса

Границу держат три разных механизма, и снаружи они выглядят по-разному:

  • Списки и коллекции фильтруются по workspace_id. Чужие строки не «запрещены» — их просто нет в ответе.
  • Операции над объектом (правки, админские действия) сначала определяют воркспейс самого объекта, а потом требуют нужное право именно в нём. Нет права — 403 с текстом вида Permission denied for workspace 7: tournament.update required. Объекта не существует — 404.
  • Скрытые турниры отвечают 404 «не найдено» всем, кроме инсайдеров: суперпользователя, участника воркспейса-организатора и аккаунтов из списка предпросмотра. Это же правило действует на подписки WebSocket, см. Realtime.

Публичные чтения платформы (профиль игрока, справочники, карточка турнира по идентификатору) остаются публичными: аренда ограничивает доменные выборки и любые записи, а не превращает открытые данные в закрытые.

Участники

workspace_member — это якорь, за который цепляется всё локальное для сообщества: ростеры, заявки, драфты, достижения и ранги. Строка уникальна парой (workspace_id, player_id) и намеренно бедна: в ней нет ни auth_user_id, ни колонки роли.

Так получается потому, что идентичность разложена на слои:

СлойЧто этоКлючевое свойство
auth.userАккаунт: вход, сессии, ролиМожет не существовать вовсе
players.userИгрок: BattleTag, историяauth_user_id уникален и может быть NULL — это теневой игрок из лога или таблицы, он не логинится
public.workspace_memberИгрок внутри сообществаУникален парой воркспейс + игрок; хранит локальный display_name
balancer.member_rankРанги участникаСлой поверх участника, не поверх аккаунта

Из этого следуют два практических факта. Во-первых, заявка, ростер и ранг ссылаются на участника, а не на аккаунт, поэтому организатор может внести игрока, у которого аккаунта нет. Во-вторых, роль участника лежит не здесь, а в ролях аккаунта, привязанных к воркспейсу, — и участник без аккаунта роли не имеет в принципе. Когда аккаунт впервые появляется в воркспейсе и ролей в нём ещё нет, ему автоматически выдаётся системная роль member; уже имеющиеся роли эта выдача не трогает и никогда не понижает.

Роли и права

Права — это плоский каталог грантов вида resource.action (tournament.update, registration.approve), плюс единственный подстановочный элемент admin.*. Каталог — и есть список допустимого: права, которого в нём нет, выдать нельзя. Поверх каталога — шесть системных ролей воркспейса:

РольЧто даёт
owneradmin.* — всё в этом воркспейсе
adminВсё, кроме управления ролями и правами и кроме удаления воркспейса и его участников
refereeЧтения уровня member плюс match.result и registration.update / .approve / .reject / .check_in — результаты и допуск, ничего структурного
hostЧтения уровня member плюс полный цикл custom_game — это и есть право проводить миксы
memberТолько *.read по доменным ресурсам сообщества
playerНичего

Два гранта в этом каталоге уже, чем можно подумать по имени ресурса, — и именно на этом разделении держится роль referee:

ПравоЧто покрываетЧего не покрывает
match.resultРезультат встречи: поля home_score, away_score, status, closeness, started_at, ended_at, current_map_index в общем PATCH плюс эндпоинты результата — проставить и переоткрыть, результаты карт, правка отдельной игры, результаты и аннулирование игр FFA, живые правки пик-бана (сброс, действие, отправка, переоткрытие, выбор начинающего)Всё, что относится к самой встрече
match.updateСаму встречу: название, стадию, команды, раунд, формат серии, время начала, обмен слотами в сетке, конфигурацию пик-бана, форму отчёта, число игр FFAПоля результата выше
registration.rolesРоли заявки — включая answers.roles — её ранги и закрепление (pin, clear_pin), а также применение автозаполнения ранговОстальную заявку, за неё отвечает registration.update

PATCH, смешивающий обе стороны, требует обоих грантов, а PATCH заявки с ключом roles без права registration.roles отклоняется целиком, а не молча урезается.

Отдельно стоят способности, разрешённые всем по умолчанию: account.avatar, account.rename, account.social, custom_game.self_join, registration.self_register, workspace.self_create. Они существуют не чтобы их выдавать, а чтобы их можно было отобрать точечно.

Отбирает их запрещающий слой: персональная запись «этому аккаунту запрещено resource.action», глобальная или привязанная к одному воркспейсу. Запрет бьёт любой грант, включая суперпользователя, но совпадает только точно: он никогда не подстановочный, поэтому запрет tournament.update не трогает tournament.delete.

Порядок вычисления права в воркспейсе:

  1. Запрет на эту пару resource.action — сразу нет.
  2. Суперпользователь или глобальная роль admin — да.
  3. Глобальный грант (с учётом подстановок) — да.
  4. Роль owner или admin в этом воркспейсе — да, кроме ресурсов role и permission и кроме workspace.delete и workspace_member.delete.
  5. Грант, выданный в этом воркспейсе (с учётом подстановок) — да.
  6. Иначе нет.

Права, которые нужны интегратору

Имена прав — это ровно то, что будет проверено на эндпоинте, и ровно то, что вы перечисляете в скоупах ключа.

ПравоЗачем оно интегратору
tournament.read, stage.read, match.read, standing.read, team.read, player.readЧтение турнирной картины сообщества
tournament.create, tournament.updateЗаведение и правка турниров
match.update, match.resultПравка встречи и внесение её результата
registration.readЧтение заявок
registration.approve, registration.reject, registration.check_inДопуск, отказ, чек-ин
registration.rolesСмена ролей, рангов и закрепления в заявке
registration_form.read, registration_form.updateФорма заявки
team.createСоздание команд, в том числе результатом задачи балансировщика
balancer.read, balancer.createЗадачи балансировки
custom_game.create, custom_game.update, custom_game.deleteПроведение миксов
log.create, log.readЗагрузка и чтение разбора логов
analytics.readАналитика сообщества
stream.update, rank.update, subscription.updateРучной перезапрос статусов трансляций, рангов и подписок
admin.*Всё сразу; выдавайте только когда перечислить нужное действительно невозможно

Скоупы ключа — это те же права

У API-ключа нет собственного словаря разрешений: скоуп ключа — это имя права из того же каталога. Когда ключ предъявляется, его скоупы пересекаются с реальными правами владельца в том единственном воркспейсе, к которому ключ привязан, и результат подставляется как обычный набор прав. Поэтому эндпоинты проверяют ключ той же проверкой, что и сессию, без отдельной ветки.

Следствия, на которые стоит рассчитывать при выдаче ключей:

  • Ключ никогда не шире владельца: сняли у владельца право — ключ теряет его в тот же момент.
  • Ключ видит ровно один воркспейс, и глобальных прав в его наборе нет никогда.
  • Ключ без скоупов не может ничего, включая аутентификацию сокета.
  • admin.* — единственная подстановка; формы вроде tournament.* не существует.
  • Неизвестное имя скоупа отклоняется при создании ключа, а если право позже убрали из каталога — ключ просто теряет эту способность, а не ломается целиком.

Выпуск, перевыпуск и отзыв ключей делаются только сессией, не другим ключом; подробности и квоты — в статье Аутентификация и ключи API.

См. также