feat: comprehensive audit log
This commit is contained in:
1 parent
867bd413c8
commit
02e0d672bd
20 files changed
+932
-82
No files matched your search
@@ -9,6 +9,7 @@ use App\Repository\UserRepository;
|
||||
use App\Security\OAuth2\AuthorizationRequestException;
|
||||
use App\Security\OAuth2\EpClient;
|
||||
use App\Security\OAuth2\UserinfoUnavailableException;
|
||||
use App\Service\AuditLogger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -42,6 +43,7 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly AuditLogger $audit,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -56,6 +58,10 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
$accessToken = $this->client->fetchAccessToken($request);
|
||||
} catch (AuthorizationRequestException|IdentityProviderException $e) {
|
||||
$this->logger->error('Login via myep failed: no access token', ['exception' => $e]);
|
||||
// Audited here rather than from a failure event: this is the only point that still
|
||||
// knows why the sign-in did not happen. Downstream there is only the German message
|
||||
// the login page shows.
|
||||
$this->audit->signInRefused('no_access_token');
|
||||
|
||||
throw new CustomUserMessageAuthenticationException('Anmeldung über myep fehlgeschlagen.');
|
||||
}
|
||||
@@ -64,6 +70,7 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
$userinfo = $this->client->fetchUserinfo($accessToken);
|
||||
} catch (UserinfoUnavailableException $e) {
|
||||
$this->logger->error('Login via myep failed: userinfo unreadable', ['exception' => $e]);
|
||||
$this->audit->signInRefused('userinfo_unreadable');
|
||||
|
||||
throw new CustomUserMessageAuthenticationException('Anmeldung über myep fehlgeschlagen.');
|
||||
}
|
||||
@@ -74,6 +81,7 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
$email = $userinfo['email'] ?? null;
|
||||
if (!\is_string($email) || '' === $email) {
|
||||
$this->logger->error('Login via myep failed: userinfo carried no email claim');
|
||||
$this->audit->signInRefused('no_email_claim');
|
||||
|
||||
throw new CustomUserMessageAuthenticationException('Anmeldung über myep fehlgeschlagen.');
|
||||
}
|
||||
@@ -81,6 +89,7 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
$roles = Role::eligibleOnly($this->claimedRoles($userinfo));
|
||||
if ([] === $roles) {
|
||||
$this->logger->info('Login via myep refused: no eligible role', ['email' => $email]);
|
||||
$this->audit->signInRefused('no_eligible_role', $email);
|
||||
|
||||
throw new CustomUserMessageAuthenticationException(
|
||||
'Für E&P Platz ist eine Mitarbeiter- oder Administrator-Rolle erforderlich.',
|
||||
@@ -154,6 +163,7 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
private function createOrUpdate(string $email, string $name, array $roles): User
|
||||
{
|
||||
$user = $this->users->findOneBy(['email' => $email]);
|
||||
$isNew = null === $user;
|
||||
|
||||
if (null === $user) {
|
||||
$user = new User($email, $name);
|
||||
@@ -162,11 +172,24 @@ class EpAuthenticator extends AbstractAuthenticator
|
||||
$user->setName($name);
|
||||
}
|
||||
|
||||
// Filtered the same way the claim was, so the comparison below is between two sets of
|
||||
// the same shape -- getRoles() would add ROLE_USER to one side and not the other.
|
||||
$previousRoles = Role::eligibleOnly($user->getRoles());
|
||||
|
||||
// myep leads: a role it no longer reports is withdrawn here, not kept because it was
|
||||
// granted once.
|
||||
$user->setRoles($roles);
|
||||
$this->em->flush();
|
||||
|
||||
// After the flush, so nothing is recorded that a failing write would have undone. Both
|
||||
// records show "anonymous" as their actor, correctly: they happen while the person is
|
||||
// being authenticated and before any token exists, which is why they name the account.
|
||||
if ($isNew) {
|
||||
$this->audit->accountProvisioned($user);
|
||||
} elseif ($previousRoles !== $roles) {
|
||||
$this->audit->rolesChanged($user, $previousRoles, $roles);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user