|
|
@@ -124,6 +124,72 @@ final class OidcFlowTest extends AppTestCase
|
|
|
);
|
|
|
}
|
|
|
|
|
|
+ public function testNoneRoleDoesNotLogRawSubject(): void
|
|
|
+ {
|
|
|
+ // SEC_REVIEW F34: a SIEM operator must not see the raw OIDC subject
|
|
|
+ // in the no-role-assigned warning. A stable fingerprint is logged
|
|
|
+ // instead so triage can still correlate repeated denials for the
|
|
|
+ // same user.
|
|
|
+ $rawSubject = 'aad-objectid-secret-9b6f7e3c-1234-5678';
|
|
|
+ $this->bindOidcAuthenticator(new class ($rawSubject) implements OidcAuthenticator {
|
|
|
+ public function __construct(private readonly string $sub)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ public function authenticate(): OidcClaims
|
|
|
+ {
|
|
|
+ return new OidcClaims($this->sub, 'x@x', 'X', []);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ $this->enqueueApiResponse(200, [
|
|
|
+ 'user_id' => 0, 'role' => 'none', 'email' => 'x@x', 'display_name' => 'X', 'is_local' => false,
|
|
|
+ ]);
|
|
|
+ $logs = $this->captureLogs();
|
|
|
+
|
|
|
+ $this->request('GET', '/oidc/callback');
|
|
|
+
|
|
|
+ self::assertNotEmpty($logs->getRecords(), 'expected the no-role denial to be logged');
|
|
|
+ foreach ($logs->getRecords() as $record) {
|
|
|
+ $serialized = json_encode([
|
|
|
+ 'message' => $record->message,
|
|
|
+ 'context' => $record->context,
|
|
|
+ ], \JSON_THROW_ON_ERROR);
|
|
|
+ self::assertStringNotContainsString($rawSubject, $serialized);
|
|
|
+ self::assertArrayNotHasKey('subject', $record->context);
|
|
|
+ }
|
|
|
+ self::assertTrue($logs->hasWarningThatContains('oidc user has no role assigned'));
|
|
|
+ }
|
|
|
+
|
|
|
+ public function testDisabledUserDeniedDoesNotLogRawSubject(): void
|
|
|
+ {
|
|
|
+ // SEC_REVIEW F34: same redaction on the user_disabled branch.
|
|
|
+ $rawSubject = 'aad-objectid-other-7c8d2f';
|
|
|
+ $this->bindOidcAuthenticator(new class ($rawSubject) implements OidcAuthenticator {
|
|
|
+ public function __construct(private readonly string $sub)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ public function authenticate(): OidcClaims
|
|
|
+ {
|
|
|
+ return new OidcClaims($this->sub, 'x@x', 'X', []);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ $this->enqueueApiResponse(403, ['error' => 'user_disabled']);
|
|
|
+ $logs = $this->captureLogs();
|
|
|
+
|
|
|
+ $this->request('GET', '/oidc/callback');
|
|
|
+
|
|
|
+ foreach ($logs->getRecords() as $record) {
|
|
|
+ $serialized = json_encode([
|
|
|
+ 'message' => $record->message,
|
|
|
+ 'context' => $record->context,
|
|
|
+ ], \JSON_THROW_ON_ERROR);
|
|
|
+ self::assertStringNotContainsString($rawSubject, $serialized);
|
|
|
+ self::assertArrayNotHasKey('subject', $record->context);
|
|
|
+ }
|
|
|
+ self::assertTrue($logs->hasWarningThatContains('oidc login denied: user disabled'));
|
|
|
+ }
|
|
|
+
|
|
|
public function testApiUnreachableDuringUpsertFlashesAndRedirects(): void
|
|
|
{
|
|
|
$this->bindOidcAuthenticator(new class () implements OidcAuthenticator {
|