All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Successful in 1m56s
CI / frontend-tests (pull_request) Successful in 2m14s
CI Trade-In / backend-tests (pull_request) Successful in 3m1s
CI / openapi-codegen-check (pull_request) Successful in 2m15s
CI / backend-tests (pull_request) Successful in 15m48s
Две связанные вещи, обе — по решению владельца продукта. 1. auth/** в paths-фильтры обоих CI (ci.yml, ci-tradein.yml). auth/roles.yaml — общий RBAC-конфиг двух стеков, но лежит в корне репы и не попадал НИ В ОДИН фильтр: правка ролей/пользователей не запускала ни backend-, ни tradein-сьют. Так 2026-07-30 в main уехал красный test_get_role_known_users (user2 переведён в expired, тест ждал pilot) — обнаружен только вручную и починен в PR #2587. Теперь правка roles.yaml гоняет оба гейта. 2. «Поиск домов» (/trade-in/sale-share) — ТЕСТОВЫЙ продукт, доступ только у админа. Раньше он был закрыт от клиентских ролей (employee/manager/pilot), но оставался открыт внутренней роли analyst. «Только у админа» включает и внутренние роли → analyst добавлен в deny по sale-share. Асимметрия с «Кэшем» намеренная и запиннена тестом: Кэш — не продукт, а диагностика кэшей/скраперов, т.е. ровно тот инструмент, ради которого роль analyst заведена; ему он оставлен. Замеры после правки (реальный is_path_allowed поверх roles.yaml): роль | Поиск домов | Кэш | ядро продукта admin | True | True | True analyst | False | True | True pilot | False | False| True Тест test_yaml_roles_deliberately_outside_client_deny переписан: пиннит ОБЕ стороны асимметрии, а не только «analyst видит всё». Набор внутренних путей разрезан на _SALE_SHARE_PATHS / _CACHE_TOOL_PATHS с assert'ом, что разрез покрывает исходный набор целиком — иначе новый путь добавят и забудут отнести к продукту, оставив analyst непроверенным. Заодно поправлены устаревшие комментарии «Доступ: pilot + admin» в Caddyfile (vanity-редирект gendsgn.ru/sale-share) и в докстринге самой страницы. Тесты: 77 passed (tradein rbac/auth_session/auth_api) + 24 passed (site-finder). tsc --noEmit + next build — зелёные. YAML обоих workflow провалидирован.
141 lines
8.9 KiB
YAML
141 lines
8.9 KiB
YAML
# RBAC roles + user → role mapping.
|
||
#
|
||
# Single source of truth for both main backend (backend/app/core/auth.py) and
|
||
# tradein backend (tradein-mvp/backend/app/core/auth.py). Mounted into both
|
||
# containers at /app/auth/roles.yaml via docker-compose bind-mount.
|
||
#
|
||
# Glob semantics (fnmatch-based, see app.core.auth.is_path_allowed):
|
||
# - "/**" → matches any path (admin scope).
|
||
# - "/foo/**" → matches /foo, /foo/, /foo/bar, /foo/bar/baz/...
|
||
# - "/foo" → matches exactly /foo.
|
||
#
|
||
# A path is allowed iff (1) it matches one of `paths` AND (2) it does NOT match
|
||
# any pattern in `deny`. `deny` overrides `paths` (deny-by-default within
|
||
# match scope).
|
||
#
|
||
# Username list MUST match exactly the entries in caddy/users.caddy.snippet
|
||
# — Caddy basic_auth sets X-Authenticated-User from {http.auth.user.id}, and
|
||
# unknown users hit /me with 403 ("user not in roles config").
|
||
#
|
||
# Last updated: 2026-05-26 (pilot scope narrowed to /trade-in/** only — decision
|
||
# 2026-05-26: pilot аккаунты пилот-программы видят ТОЛЬКО раздел Trade-In;
|
||
# Analytics / Site Finder / Concept / landing — admin-only).
|
||
|
||
roles:
|
||
admin:
|
||
paths:
|
||
- "/**"
|
||
deny: []
|
||
pilot:
|
||
# Pilot имеет доступ ТОЛЬКО к разделу Trade-In (оценка вторички).
|
||
# Landing (/), analytics, site-finder, concept, остальной /api/v1/* — закрыты.
|
||
# Backend middleware всё равно пропускает known users на все non-admin paths
|
||
# (path-level enforcement делает frontend RouteGuard через `allowed_paths`),
|
||
# но для UX/security принципов keep deny список explicit.
|
||
paths:
|
||
- "/trade-in/**"
|
||
- "/trade-in/api/v1/**"
|
||
deny:
|
||
- "/admin/**"
|
||
- "/api/v1/admin/**"
|
||
- "/trade-in/api/v1/admin/**"
|
||
# Внутренние разделы, закрытые от клиентских аккаунтов (решение владельца
|
||
# продукта 2026-07-31): «Доля в продаже» — аналитика рынка, «Кэш» —
|
||
# состояние кэшей/скраперов. Зеркало deny-списка DB-ролей employee/manager
|
||
# (tradein-mvp/backend/app/services/auth_session.py: DB_ROLE_PATHS).
|
||
#
|
||
# Зачем копия здесь, если клиенты ходят session-cookie'ой: снаружи легаси
|
||
# trusted-header ветка НЕДОСТИЖИМА — с #2558 Caddy срезает входящий
|
||
# X-Authenticated-User на всём /trade-in/* (`header_up
|
||
# -X-Authenticated-User` в handle /trade-in/api/* и в @tradein), так что
|
||
# ни один клиентский аккаунт по ней не ходит. Паттерны нужны для другого:
|
||
# 1) ВНУТРИСЕТЕВОЙ dual-mode трафик — запросы изнутри gendesign_shared с
|
||
# валидным X-Internal-Auth-Secret; ими ходят QA-смоуки вида
|
||
# `docker exec tradein-backend curl localhost:8000
|
||
# -H 'X-Authenticated-User: ...'` — они резолвятся именно через
|
||
# roles.yaml, и без этих строк смоук показал бы 200 там, где
|
||
# реальный клиент получает 403;
|
||
# 2) чтобы legacy-pilot не расходился с DB-employee, если dual-режим
|
||
# когда-нибудь снова окажется на периметре (откат #2558 / новый
|
||
# фронт-прокси) — тогда расхождение молча откроет разделы.
|
||
# НЕ удалять как «мёртвые»: они мёртвые только пока Caddy режет заголовок.
|
||
#
|
||
# Страницы + их API вместе: deny гейтит пункт меню (Topbar через /me),
|
||
# саму страницу (RouteGuard) и серверные ручки (rbac_guard).
|
||
#
|
||
# cache-stats закрыт ГЛОБОМ, а не точным путём, намеренно: точный паттерн
|
||
# обходится трейлинг-слэшем ('…/cache-stats/' не равен '…/cache-stats' →
|
||
# allowed), и защита повисала бы на Starlette redirect_slashes, а не на
|
||
# RBAC. '<prefix>/**' → '^<prefix>(?:/.*)?$': сам путь + слэш + подпути,
|
||
# но НЕ соседи по префиксу ('…/cache-statistics' не матчится).
|
||
- "/trade-in/sale-share/**"
|
||
- "/trade-in/cache/**"
|
||
- "/trade-in/api/v1/buildings/**"
|
||
- "/trade-in/api/v1/trade-in/cache-stats/**"
|
||
analyst:
|
||
# #962 (EPIC18, ТЗ §19): analyst видит ВСЁ (deals, insights, exports,
|
||
# site-finder, analytics, concept) КРОМЕ admin/data-management.
|
||
# paths "/**" = доступ ко всему; deny = админ-управление (scraper-триггеры,
|
||
# sync-таски, data-management, user/role management).
|
||
# Backend hard-gate: app/main.py rbac_guard уже блокирует /api/v1/admin/*
|
||
# для любого role != "admin" → analyst авто-403 на admin-API без доп. кода.
|
||
# deny ниже драйвит фронтовый RouteGuard (deny_paths из /me) для UI-gating
|
||
# /admin/** страниц.
|
||
# Клиентский deny 2026-07-31 (см. pilot выше) распространён на analyst
|
||
# ЧАСТИЧНО — асимметрия намеренная, не недосмотр:
|
||
# «Поиск домов» (/trade-in/sale-share + /api/v1/buildings/**) — ЗАКРЫТ.
|
||
# Решение владельца продукта 2026-07-31: это ТЕСТОВЫЙ продукт, доступ
|
||
# только у admin. «Только у админа» = включая внутренние роли, поэтому
|
||
# analyst тоже в deny.
|
||
# «Кэш» (/trade-in/cache + cache-stats) — ОСТАВЛЕН открытым: это не
|
||
# продукт, а диагностика состояния кэшей/скраперов, т.е. ровно тот
|
||
# рабочий инструмент, ради которого роль analyst и заведена
|
||
# («видит ВСЁ кроме admin-управления», см. выше).
|
||
# Обе стороны этой асимметрии запиннены тестом
|
||
# tradein-mvp/backend/tests/test_rbac.py::test_yaml_roles_deliberately_outside_client_deny
|
||
# — если решение поменяется, тест упадёт и заставит обновить и его, и этот
|
||
# комментарий, а не тихо разойтись с реальностью.
|
||
paths:
|
||
- "/**"
|
||
deny:
|
||
- "/admin/**"
|
||
- "/api/v1/admin/**"
|
||
- "/trade-in/api/v1/admin/**"
|
||
- "/trade-in/sale-share/**"
|
||
- "/trade-in/api/v1/buildings/**"
|
||
expired:
|
||
# Пробный доступ закончился — нет доступа ни к чему. Аккаунт остаётся в
|
||
# caddy/users.caddy.snippet (basic_auth), чтобы дойти до фронта и увидеть
|
||
# сообщение; /me отдаёт role=expired → RouteGuard рендерит trial-экран.
|
||
paths: []
|
||
deny:
|
||
- "/**"
|
||
|
||
# NB: реальные analyst-логины ДОЛЖНЫ быть добавлены и в
|
||
# caddy/users.caddy.snippet (Caddy basic_auth) силами devops — иначе Caddy не
|
||
# пропустит юзера и не выставит X-Authenticated-User. Здесь только role-mapping;
|
||
# Caddy НЕ редактируем из этого таска.
|
||
users:
|
||
admin: admin
|
||
kopylov: pilot
|
||
user1: pilot
|
||
user2: expired # «Брусника» — доступ закрыт 2026-07-30 (решение владельца продукта;
|
||
# ранее: восстановлен 2026-07-13, trial-expire 2026-07-09)
|
||
user3: pilot
|
||
user4: pilot
|
||
user5: pilot
|
||
user6: pilot
|
||
user7: pilot
|
||
user8: pilot
|
||
user9: pilot
|
||
user10: pilot
|
||
praktika: pilot # ГК «Практика» — доступ восстановлен 2026-07-27 (решение владельца
|
||
# продукта; ранее expired с 2026-06-27). Безлимитная квота оценок
|
||
# выдана через account_quota_overrides.unlimited (migration 191),
|
||
# не через код — см. app.services.account_quota.is_unlimited.
|
||
admintest: admin # temp QA 2026-05-26
|
||
pilottest: pilot # temp QA 2026-05-26
|
||
analysttest: analyst # temp QA 2026-06-07 (#962)
|
||
expiredtest: expired # temp QA 2026-07-27 — role=expired regression coverage для
|
||
# test_rbac.py (praktika перестал быть expired-фикстурой
|
||
# после восстановления доступа)
|