Prove registration collision safety
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

This commit is contained in:
tegwick 2026-08-10 18:37:41 +02:00
parent b80de5a1f4
commit a8f190b7b7
2 changed files with 43 additions and 0 deletions

View file

@ -354,6 +354,42 @@ class PortalApplicationTests(unittest.TestCase):
)
self.assertEqual("403 Forbidden", replay["status"])
def test_public_registration_duplicate_inputs_never_take_over_identity(self):
verifier = FakeRegistrationVerification()
self.app.registration_verification = verifier
self.app.provisioning = FakeProvisioning()
self.app.registration_clients = frozenset({"coulomb-social"})
self.app.registration_tenants = frozenset({"tenant:coulomb"})
self.app.registration_oidc_issuer = "https://kc.example"
self.app.registration_password_setup_origins = frozenset({"https://kc.example"})
def register(username, email):
invoke(self.app, "/api/v1/public/registrations", method="POST", body={
"username": username, "email": email,
"client_id": "coulomb-social", "tenant": "tenant:coulomb",
})
registration_id = verifier.requested.registration_id
verifier.registration_id = registration_id
result, _ = invoke(
self.app, "/api/v1/public/registrations/verify", method="POST",
body={"handle": "x" * 32},
)
return result, self.app.service.store.registration_session(registration_id)
first, first_session = register("first.person", "shared@example.test")
second, second_session = register("second.person", "shared@example.test")
collision, collision_session = register("first.person", "other@example.test")
self.assertEqual("303 See Other", first["status"])
self.assertEqual("303 See Other", second["status"])
self.assertNotEqual(first_session.user_id, second_session.user_id)
self.assertEqual("409 Conflict", collision["status"])
linked = self.app.service.store.find_identity(
"https://kc.example", "first.person"
)
self.assertEqual(first_session.user_id, linked.user_id)
self.assertNotEqual(collision_session.user_id, linked.user_id)
def test_public_registration_rejects_unregistered_client(self):
self.app.registration_verification = FakeRegistrationVerification()
self.app.registration_clients = frozenset({"coulomb-social"})

View file

@ -197,3 +197,10 @@ verification expiry/replay redaction, peer rate limiting, identity-link
collision, cross-tenant denial, lifecycle deletion, audit/outbox redaction,
and request correlation. Deployed consumer conformance remains gated on public
runtime credentials and activation.
Duplicate-input conformance now also proves that a verified mailbox reused
with a different username creates a distinct local user instead of implicitly
linking to an existing account. Reusing an existing provider username fails at
the identity-link uniqueness boundary, leaves the original link unchanged, and
never transfers that identity to the later registration. The full suite passes
131 tests with three environment-dependent skips.