test(tradein): прод-конфигурация ролей в приёмке #3316 + компромисс фолбэка
Some checks failed
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 5m8s
Some checks failed
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 5m8s
Review PR #3331: приёмка «роли не изменились» гонялась с ПУСТЫМ реестром, а в проде строка в БД есть у 12 из 13 юзеров и DB-роль ИНАЯ (kopylov: manager при YAML pilot, user1: employee при YAML pilot). Добавлены два кейса именно этой конфигурации: * YAML pilot + реестр employee → employee, и scope не поехал: allow/deny DB_ROLE_PATHS['employee'] сверяются со списками роли pilot из roles.yaml целиком — дрейф ЛЮБОГО из двух списков теперь красный тест, а не тихо потерянный/выданный раздел в проде; * YAML pilot + реестр manager → manager, и лишних путей на tradein-периметре нет: manager отличается от employee ровно префиксом /api/v1/team/** (вне /trade-in/**), deny-списки совпадают. Докстринг `_registry_role`: зафиксирован компромисс — при недоступном реестре фолбэк временно возвращает авторитетность roles.yaml, то есть состояние, которое фикс и лечит. Сегодня безопасно (прод-коллизий имён нет, новые закрыты 409-гвардом create_employee); появится коллизия — ветку менять на fail-closed.
This commit is contained in:
parent
4feb61c006
commit
cde95aa9f6
2 changed files with 55 additions and 0 deletions
|
|
@ -180,6 +180,15 @@ def _registry_role(username: str) -> str | None:
|
|||
пустая, либо реестр вообще недоступен. Во всех трёх случаях решение
|
||||
остаётся за roles.yaml — падение БД не имеет права выключить legacy-вход.
|
||||
|
||||
⚠️ Осознанный компромисс (#3316 review): последняя ветка — недоступный
|
||||
реестр — на время сбоя ВОЗВРАЩАЕТ авторитетность roles.yaml, то есть ровно
|
||||
то состояние, которое этот фикс и лечит. Сегодня это безопасно: коллизий
|
||||
имён между реестром и YAML на проде нет, а новые закрыты 409-гвардом в
|
||||
`team.create_employee`. Если коллизия всё же появится (ручной INSERT в
|
||||
реестр, расширение roles.yaml) — сбой БД станет окном эскалации, и тогда
|
||||
эту ветку надо менять на fail-closed (отказ вместо YAML-роли), а не
|
||||
дописывать проверки у вызывающих.
|
||||
|
||||
Имя таблицы берётся из фиксированного словаря `identity_schema()`, значение
|
||||
едет bind-параметром: снаружи в SQL не попадает ничего.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -129,6 +129,52 @@ def test_legacy_yaml_users_keep_their_roles(registry: Callable[..., Any]) -> Non
|
|||
assert {username: auth.get_role(username) for username in users} == users
|
||||
|
||||
|
||||
def _yaml_pilot() -> str:
|
||||
for username, role in _yaml_users().items():
|
||||
if role == "pilot":
|
||||
return username
|
||||
pytest.skip("в auth/roles.yaml нет ни одного pilot — тест неприменим")
|
||||
|
||||
|
||||
def test_prod_config_pilot_in_yaml_employee_in_registry(registry: Callable[..., Any]) -> None:
|
||||
"""Прод-конфигурация 12 из 13 аккаунтов: строка в реестре ЕСТЬ и роль там иная.
|
||||
|
||||
Реестр главнее (`employee`), а объём прав от этого не меняется: scope
|
||||
DB-роли `employee` обязан совпадать с вчерашним YAML-scope роли `pilot`.
|
||||
Списки сверяются целиком — дрейф ЛЮБОГО из двух ловится здесь, а не
|
||||
тихой потерей/выдачей раздела в проде.
|
||||
"""
|
||||
from app.services.auth_session import DB_ROLE_PATHS
|
||||
|
||||
registry("employee")
|
||||
assert auth.get_role(_yaml_pilot()) == "employee"
|
||||
|
||||
pilot = auth._load_roles_config()["roles"]["pilot"]
|
||||
allow, deny = DB_ROLE_PATHS["employee"]
|
||||
assert sorted(allow) == sorted(pilot["paths"])
|
||||
assert sorted(deny) == sorted(pilot["deny"] or [])
|
||||
|
||||
|
||||
def test_prod_config_pilot_in_yaml_manager_in_registry(registry: Callable[..., Any]) -> None:
|
||||
"""Конфигурация kopylov: YAML pilot + реестр manager → manager.
|
||||
|
||||
Лишних путей на tradein-периметре это не даёт: manager отличается от
|
||||
employee ровно одним префиксом `/api/v1/team/**` (дашборд «Команды»,
|
||||
ВНЕ `/trade-in/**`), а deny-списки совпадают.
|
||||
"""
|
||||
from app.services.auth_session import DB_ROLE_PATHS
|
||||
|
||||
registry("manager")
|
||||
assert auth.get_role(_yaml_pilot()) == "manager"
|
||||
|
||||
emp_allow, emp_deny = DB_ROLE_PATHS["employee"]
|
||||
mgr_allow, mgr_deny = DB_ROLE_PATHS["manager"]
|
||||
extra = set(mgr_allow) - set(emp_allow)
|
||||
assert extra == {"/api/v1/team/**"}
|
||||
assert not any(p.startswith("/trade-in") for p in extra)
|
||||
assert sorted(mgr_deny) == sorted(emp_deny)
|
||||
|
||||
|
||||
def test_registry_failure_falls_back_to_yaml(registry: Callable[..., Any]) -> None:
|
||||
"""Реестр недоступен → legacy-вход продолжает работать по YAML."""
|
||||
registry(_MISSING)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue