feat: identify accounts by a uuid in the oauth2 sub claim

This commit is contained in:
2026-09-23 15:17:09 +02:00
parent cc3d32fcbb
commit bad916cfb7
6 changed files with 153 additions and 7 deletions
@@ -27,13 +27,14 @@ class UserinfoControllerTest extends TestCase
{
private const LOGIN_EMAIL = '[email protected]';
private const CONTACT_EMAIL = '[email protected]';
private const UUID = '018f3c2a-7b4d-4e91-a3c5-6d2f8b1e4a07';
public function testSubAndEmailAreExportedWithoutAnyOptionalScope(): void
{
$claims = $this->claims();
self::assertSame(['sub', 'email'], array_keys($claims));
self::assertSame('42', $claims['sub']);
self::assertSame(self::UUID, $claims['sub']);
self::assertSame(self::LOGIN_EMAIL, $claims['email']);
}
@@ -43,11 +44,27 @@ class UserinfoControllerTest extends TestCase
// The ids identify the human, the subject identifies the account. A second account of the
// same person reports these same two ids and must still be told apart.
self::assertSame('42', $claims['sub']);
self::assertSame(self::UUID, $claims['sub']);
self::assertSame(7, $claims['person_id']);
self::assertSame(9, $claims['address_id']);
}
/**
* Consumers store `sub` and match their own accounts on it, so it must not disclose how many
* accounts exist or let one account's identifier be guessed from the next. The primary key
* did both.
*/
public function testSubIsTheAccountUuidRatherThanItsPrimaryKey(): void
{
$claims = $this->claims();
self::assertNotSame('42', $claims['sub']);
self::assertMatchesRegularExpression(
'/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/',
$claims['sub']
);
}
public function testEmailIsTheAuthenticatedAddressAndNotTheFirstContactOnTheBusProRecord(): void
{
$claims = $this->claims(['ROLE_OAUTH2_PROFILE']);
@@ -160,10 +177,15 @@ class UserinfoControllerTest extends TestCase
->setRoles($roles)
;
// The id is generated by Doctrine and has no setter, but it is what `sub` exports.
// The id is generated by Doctrine and has no setter. It is deliberately not what `sub`
// exports -- see testSubIsTheAccountUuidRatherThanItsPrimaryKey.
$property = new \ReflectionProperty(User::class, 'id');
$property->setValue($user, 42);
// The constructor generates a fresh uuid; pin it so the claim can be asserted literally.
$property = new \ReflectionProperty(User::class, 'uuid');
$property->setValue($user, self::UUID);
return $user;
}