Fix OIDC subject collisions by introducing canonical provider-plus-subject principals
- Add canonical_principal(provider, sub) => 'oidc:v1:<provider>:<sub>' - Preserve provider identity across HTTP sessions and WebSockets - Apply principals throughout: game creation/joining/seating, WebSocket views/actions, Redis engine state, match history, leaderboard focus, ratings, /api/me, OpenAPI, and telemetry - Widen identity columns to 1024 chars with schema-only aerich migration (5_20261024221522_canonical_principals.py) - Document provider names as stable security identifiers - Add regression tests: same subject at different providers cannot share games, private hands, join codes, moves, history, or ratings; distinct seat and rating ownership verified - All platform, integration, game, and frontend tests pass; mypy clean
This commit is contained in:
+13
-13
@@ -21,7 +21,7 @@ from tavolo.platform.models import Match, MatchPlayer, PlayerRating
|
||||
from tavolo.platform.stats import save_match_result
|
||||
from tavolo.scopa import engine as scopa_rules
|
||||
from tavolo.scopone import engine as rules
|
||||
from tests.helpers import async_test, oidc_user
|
||||
from tests.helpers import async_test, make_principal, oidc_user
|
||||
|
||||
PLAYERS = ("alice", "bob", "carol", "dave")
|
||||
|
||||
@@ -32,12 +32,12 @@ async def _finished_session(target_score: int = 1) -> GameSession:
|
||||
id="stats-scope-1",
|
||||
game_type=engine.id,
|
||||
join_code="SS0001",
|
||||
creator_sub="alice",
|
||||
players=[Seat(user_sub="alice", display_name="Alice")],
|
||||
creator_sub=make_principal("alice"),
|
||||
players=[Seat(user_sub=make_principal("alice"), display_name="Alice")],
|
||||
)
|
||||
engine.create(session, {"target_score": target_score})
|
||||
for name in PLAYERS[1:]:
|
||||
engine.join(session, name, name.capitalize())
|
||||
engine.join(session, make_principal(name), name.capitalize())
|
||||
moves = 0
|
||||
while not engine.is_finished(session) and moves < 200000:
|
||||
state = session.state
|
||||
@@ -160,11 +160,11 @@ async def _finished_briscola_session() -> GameSession:
|
||||
id="stats-briscola-1",
|
||||
game_type=engine.id,
|
||||
join_code="BS0001",
|
||||
creator_sub="alice",
|
||||
players=[Seat(user_sub="alice", display_name="Alice")],
|
||||
creator_sub=make_principal("alice"),
|
||||
players=[Seat(user_sub=make_principal("alice"), display_name="Alice")],
|
||||
)
|
||||
engine.create(session, {})
|
||||
engine.join(session, "bob", "Bob")
|
||||
engine.join(session, make_principal("bob"), "Bob")
|
||||
moves = 0
|
||||
while not engine.is_finished(session) and moves < 200000:
|
||||
state = session.state
|
||||
@@ -246,15 +246,15 @@ async def _finished_chiamata_session() -> GameSession:
|
||||
id="stats-chiamata-1",
|
||||
game_type=engine.id,
|
||||
join_code="BC0001",
|
||||
creator_sub="alice",
|
||||
creator_sub=make_principal("alice"),
|
||||
players=[
|
||||
Seat(user_sub=name, display_name=name.capitalize())
|
||||
Seat(user_sub=make_principal(name), display_name=name.capitalize())
|
||||
for name in ("alice", "bob", "carol", "dave", "erin")
|
||||
],
|
||||
)
|
||||
engine.create(session, {})
|
||||
for name in ("bob", "carol", "dave", "erin"):
|
||||
engine.join(session, name, name.capitalize())
|
||||
engine.join(session, make_principal(name), name.capitalize())
|
||||
moves = 0
|
||||
while not engine.is_finished(session) and moves < 200000:
|
||||
state = session.state
|
||||
@@ -344,15 +344,15 @@ class ScopaStatsTest(unittest.TestCase):
|
||||
id=f"stats-scopa-{player_count}",
|
||||
game_type=engine.id,
|
||||
join_code="SC0001",
|
||||
creator_sub="alice",
|
||||
players=[Seat(user_sub="alice", display_name="Alice")],
|
||||
creator_sub=make_principal("alice"),
|
||||
players=[Seat(user_sub=make_principal("alice"), display_name="Alice")],
|
||||
)
|
||||
engine.create(
|
||||
session,
|
||||
{"target_score": 1, "asso_pigliatutto": asso_pigliatutto},
|
||||
)
|
||||
for name in PLAYERS[1:player_count]:
|
||||
engine.join(session, name, name.capitalize())
|
||||
engine.join(session, make_principal(name), name.capitalize())
|
||||
moves = 0
|
||||
while not engine.is_finished(session) and moves < 200000:
|
||||
state = session.state
|
||||
|
||||
Reference in New Issue
Block a user