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:
@@ -25,7 +25,7 @@ from tavolo.telemetry import (
|
||||
)
|
||||
from tavolo.config import Settings
|
||||
|
||||
from tests.helpers import async_test
|
||||
from tests.helpers import async_test, make_principal
|
||||
|
||||
try:
|
||||
from opentelemetry.sdk._logs import LoggingHandler, LoggerProvider
|
||||
@@ -158,7 +158,7 @@ class SessionTagHookTest(unittest.TestCase):
|
||||
hashlib.sha256(b"sid").hexdigest()[:16],
|
||||
span.attributes["session.id"],
|
||||
)
|
||||
self.assertEqual("alice", span.attributes["user.id"])
|
||||
self.assertEqual(make_principal("alice"), span.attributes["user.id"])
|
||||
|
||||
def test_tags_user_of_any_provider(self) -> None:
|
||||
span = _RecordedSpan()
|
||||
@@ -166,7 +166,7 @@ class SessionTagHookTest(unittest.TestCase):
|
||||
session=Session("sid", {"oidc:google:user": {"sub": "alice"}})
|
||||
)
|
||||
session_tag_hook(span, carrier)
|
||||
self.assertEqual("alice", span.attributes["user.id"])
|
||||
self.assertEqual(make_principal("alice", "google"), span.attributes["user.id"])
|
||||
|
||||
def test_ignores_legacy_unprefixed_user_key(self) -> None:
|
||||
span = _RecordedSpan()
|
||||
@@ -190,7 +190,7 @@ class SessionTagHookTest(unittest.TestCase):
|
||||
)
|
||||
session_tag_hook(span, carrier)
|
||||
self.assertNotIn("session.id", span.attributes)
|
||||
self.assertEqual("alice", span.attributes["user.id"])
|
||||
self.assertEqual(make_principal("alice"), span.attributes["user.id"])
|
||||
|
||||
def test_missing_session_is_a_noop(self) -> None:
|
||||
span = _RecordedSpan()
|
||||
@@ -266,7 +266,7 @@ class SessionTagIntegrationTest(unittest.TestCase):
|
||||
attributes = span.attributes
|
||||
assert attributes is not None
|
||||
self.assertEqual(digest, attributes["session.id"], name)
|
||||
self.assertEqual("alice", attributes["user.id"], name)
|
||||
self.assertEqual(make_principal("alice"), attributes["user.id"], name)
|
||||
|
||||
|
||||
def _metric_points(reader) -> dict:
|
||||
|
||||
Reference in New Issue
Block a user