Merge pull request 'fix(tradein): praktika brand only under praktika account — drop kopylov mapping (brand leak)' (#1758) from fix/praktika-brand-only-praktika-user into main
All checks were successful
Deploy Trade-In / test (push) Successful in 35s
Deploy Trade-In / changes (push) Successful in 7s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 50s
Deploy Trade-In / deploy (push) Successful in 45s

Reviewed-on: #1758
This commit is contained in:
lekss361 2026-06-18 13:36:20 +00:00
commit c8f6fd1535
2 changed files with 24 additions and 8 deletions

View file

@ -50,13 +50,19 @@ class UserScope(TypedDict):
# Account → brand mapping (#657 brand-by-account) # Account → brand mapping (#657 brand-by-account)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# #
# Пилот-аккаунты агентства «Практика» получают брендирование автоматически # Брендирование «Практика» (#657 brand-by-account) применяется ТОЛЬКО под
# при логине (без ?brand=). Резолвится в /me как UserScope.brand. Остальные # аккаунтом `praktika`. Резолвится в /me как UserScope.brand, а при генерации
# юзеры → None (стандартный generic UI). Slug должен существовать в таблице # trade-in PDF — по `created_by` оценки (анти-spoofing #7, бренд не хранится).
# `brands` (см. services/brand.py) — иначе useBrand fallback на generic. # Остальные юзеры (вкл. `kopylov`) → None (стандартный generic UI/PDF). Slug
# должен существовать в таблице `brands` (см. services/brand.py) — иначе
# useBrand fallback на generic.
#
# `kopylov` ранее мапился на бренд `praktika`, но это приводило к утечке лого
# «Практики» в отчёты, созданные под `kopylov`, не тем клиентам (инцидент:
# отчёт для Брусники с лого Практики) — маппинг удалён. NB: kopylov/praktika
# остаются в роли `pilot` в roles.yaml — это РОЛЬ, не бренд, не трогаем.
_USERNAME_BRAND: dict[str, str] = { _USERNAME_BRAND: dict[str, str] = {
"praktika": "praktika", "praktika": "praktika",
"kopylov": "praktika",
} }

View file

@ -1,8 +1,8 @@
"""Tests for #657 brand-by-account — UserScope.brand resolution. """Tests for #657 brand-by-account — UserScope.brand resolution.
Pure-function tests against app.core.auth: brand slug привязывается к аккаунту Pure-function tests against app.core.auth: brand slug привязывается к аккаунту
(praktika/kopylov "praktika"), остальные юзеры None. get_user_scope (ТОЛЬКО praktika "praktika"), остальные юзеры включая kopylov None.
прокидывает brand в scope, который /me отдаёт фронту. get_user_scope прокидывает brand в scope, который /me отдаёт фронту.
Без БД и сети: читается реальный auth/roles.yaml (ancestor-walk внутри репо). Без БД и сети: читается реальный auth/roles.yaml (ancestor-walk внутри репо).
""" """
@ -29,7 +29,10 @@ from app.core.auth import get_brand_for_user, get_user_scope # noqa: E402
("username", "expected"), ("username", "expected"),
[ [
("praktika", "praktika"), ("praktika", "praktika"),
("kopylov", "praktika"), # kopylov больше НЕ брендируется как Практика (#657 brand leak fix):
# бренд резолвится по created_by при генерации PDF, поэтому утечка лого
# Практики в отчёты под kopylov исправляется тем, что kopylov → generic.
("kopylov", None),
("admin", None), ("admin", None),
("user1", None), ("user1", None),
("unknown_user", None), ("unknown_user", None),
@ -39,6 +42,13 @@ def test_get_brand_for_user(username: str, expected: str | None) -> None:
assert get_brand_for_user(username) == expected assert get_brand_for_user(username) == expected
def test_user_scope_brand_none_for_kopylov() -> None:
# Регресс-гард на #657 brand leak: kopylov-оценки рендерят generic PDF.
scope = get_user_scope("kopylov")
assert scope["brand"] is None
assert scope["username"] == "kopylov"
def test_user_scope_carries_brand_for_praktika() -> None: def test_user_scope_carries_brand_for_praktika() -> None:
scope = get_user_scope("praktika") scope = get_user_scope("praktika")
assert scope["brand"] == "praktika" assert scope["brand"] == "praktika"