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:
@@ -23,7 +23,7 @@ from tavolo.briscola.chiamata import (
|
||||
Bid,
|
||||
)
|
||||
from tavolo.platform import GameSession, Seat
|
||||
from tests.helpers import async_test, make_user, oidc_user, ws_users
|
||||
from tests.helpers import async_test, make_principal, make_user, oidc_user, ws_users
|
||||
|
||||
PLAYERS = ("alice", "bob", "carol", "dave", "erin")
|
||||
|
||||
@@ -163,7 +163,7 @@ class ChiamataWebSocketTest(unittest.TestCase):
|
||||
session = await game_store.load(game_id)
|
||||
assert session is not None
|
||||
for sub in ("carol", "dave", "erin", "alice"):
|
||||
engine.handle_action(session, sub, "pass", {})
|
||||
engine.handle_action(session, make_principal(sub), "pass", {})
|
||||
await game_store.save(session)
|
||||
self.assertEqual(PHASE_CALLING, session.state.phase)
|
||||
self.assertEqual(1, session.state.caller)
|
||||
@@ -227,15 +227,17 @@ class ChiamataTimeoutTest(unittest.TestCase):
|
||||
id=game_id,
|
||||
game_type="briscola_chiamata",
|
||||
join_code=join_code,
|
||||
creator_sub=PLAYERS[0],
|
||||
creator_sub=make_principal(PLAYERS[0]),
|
||||
players=[
|
||||
Seat(user_sub=name, display_name=name.capitalize())
|
||||
Seat(user_sub=make_principal(name), display_name=name.capitalize())
|
||||
for name in PLAYERS
|
||||
],
|
||||
)
|
||||
session.state = rules.create_game(PLAYERS[0], "Alice", turn_timeout=1)
|
||||
session.state = rules.create_game(
|
||||
make_principal(PLAYERS[0]), "Alice", turn_timeout=1
|
||||
)
|
||||
for name in PLAYERS[1:]:
|
||||
rules.join_game(session.state, name, name.capitalize())
|
||||
rules.join_game(session.state, make_principal(name), name.capitalize())
|
||||
state_mutator(session.state)
|
||||
await game_store.save(session)
|
||||
return session.id
|
||||
|
||||
Reference in New Issue
Block a user