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
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:
commit
c8f6fd1535
2 changed files with 24 additions and 8 deletions
|
|
@ -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",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue