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:
@@ -11,7 +11,7 @@ from httpx_ws.transport import ASGIWebSocketTransport
|
||||
from tavolo.app import app, game_store, platform
|
||||
from tavolo.platform import GameSession, Seat
|
||||
from tavolo.scopone.state import Card, PlayerState, ScoponeState
|
||||
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")
|
||||
|
||||
@@ -27,8 +27,8 @@ def _started_session(
|
||||
id=game_id,
|
||||
game_type=engine.id,
|
||||
join_code=code,
|
||||
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,
|
||||
@@ -42,7 +42,7 @@ def _started_session(
|
||||
session.state.hand_ack_timeout = hand_ack_timeout
|
||||
session.state.turn_timeout = turn_timeout
|
||||
for name in PLAYERS[1:]:
|
||||
engine.join(session, name, name.capitalize())
|
||||
engine.join(session, make_principal(name), name.capitalize())
|
||||
return session
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ class WebSocketTest(unittest.TestCase):
|
||||
alice_first = await alice_ws.receive_json()
|
||||
self.assertEqual("state", alice_first["type"])
|
||||
self.assertEqual(
|
||||
"alice", alice_first["game"]["players"][0]["sub"]
|
||||
make_principal("alice"), alice_first["game"]["players"][0]["sub"]
|
||||
)
|
||||
self.assertNotIn("hand", alice_first["game"]["players"][1])
|
||||
|
||||
@@ -189,12 +189,12 @@ async def _seed_last_play_state(hand_ack_timeout: int = 30) -> str:
|
||||
id="hand-end-1",
|
||||
game_type=engine.id,
|
||||
join_code="HEND01",
|
||||
creator_sub="alice",
|
||||
creator_sub=make_principal("alice"),
|
||||
players=[
|
||||
Seat(user_sub="alice", display_name="Alice", team="A"),
|
||||
Seat(user_sub="bob", display_name="Bob", team="B"),
|
||||
Seat(user_sub="carol", display_name="Carol", team="A"),
|
||||
Seat(user_sub="dave", display_name="Dave", team="B"),
|
||||
Seat(user_sub=make_principal("alice"), display_name="Alice", team="A"),
|
||||
Seat(user_sub=make_principal("bob"), display_name="Bob", team="B"),
|
||||
Seat(user_sub=make_principal("carol"), display_name="Carol", team="A"),
|
||||
Seat(user_sub=make_principal("dave"), display_name="Dave", team="B"),
|
||||
],
|
||||
)
|
||||
session.state = ScoponeState(
|
||||
@@ -203,10 +203,10 @@ async def _seed_last_play_state(hand_ack_timeout: int = 30) -> str:
|
||||
turn=0,
|
||||
table=[Card.parse("02C")],
|
||||
players=[
|
||||
PlayerState(sub="alice", name="Alice", seat=0, hand=[Card.parse("02D")]),
|
||||
PlayerState(sub="bob", name="Bob", seat=1),
|
||||
PlayerState(sub="carol", name="Carol", seat=2),
|
||||
PlayerState(sub="dave", name="Dave", seat=3),
|
||||
PlayerState(sub=make_principal("alice"), name="Alice", seat=0, hand=[Card.parse("02D")]),
|
||||
PlayerState(sub=make_principal("bob"), name="Bob", seat=1),
|
||||
PlayerState(sub=make_principal("carol"), name="Carol", seat=2),
|
||||
PlayerState(sub=make_principal("dave"), name="Dave", seat=3),
|
||||
],
|
||||
hand_ack_timeout=hand_ack_timeout,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user