feat: proper logging to dedicated audit channel

This commit is contained in:
Björn Fromme
2026-08-12 10:34:41 +02:00
parent 898a7c7505
commit 74ebefcf65
4 changed files with 49 additions and 15 deletions
+24 -12
View File
@@ -2,7 +2,7 @@ monolog:
channels:
- deprecation
- bpn
- myep
- audit
handlers:
main:
@@ -11,7 +11,7 @@ monolog:
handler: exceptions
excluded_http_codes: [404, 405]
buffer_size: 50
channels: ["!bpn", "!myep"]
channels: ["!bpn", "!audit"]
exceptions:
type: rotating_file
path: "%kernel.logs_dir%/framework.%kernel.environment%.log"
@@ -23,14 +23,14 @@ monolog:
path: '%kernel.logs_dir%/bpn.%kernel.environment%.log'
max_files: 5
level: info
myep:
channels: [ "myep" ]
audit:
channels: [ "audit" ]
type: rotating_file
path: '%kernel.logs_dir%/myep.%kernel.environment%.log'
path: '%kernel.logs_dir%/audit.%kernel.environment%.log'
max_files: 5
level: info
database:
channels: ["myep"]
channels: ["audit"]
type: service
id: App\Logging\DatabaseHandler
console:
@@ -38,6 +38,18 @@ monolog:
process_psr_3_messages: false
channels: ["!event", "!bpn", "!doctrine", "!console"]
when@dev:
monolog:
handlers:
# Single catch-all file so everything is visible in one place during
# development. The handlers above are error-level or channel-bound,
# which makes ordinary app output invisible locally.
dev:
type: stream
path: '%kernel.logs_dir%/%kernel.environment%.log'
level: debug
channels: ["!event", "!doctrine"]
when@test:
monolog:
handlers:
@@ -61,7 +73,7 @@ when@prod:
handler: exceptions
excluded_http_codes: [404, 405]
buffer_size: 50
channels: ["!bpn", "!myep"]
channels: ["!bpn", "!audit"]
exceptions:
type: rotating_file
path: "%kernel.logs_dir%/framework.%kernel.environment%.log"
@@ -73,17 +85,17 @@ when@prod:
path: '%kernel.logs_dir%/bpn.%kernel.environment%.log'
max_files: 5
level: info
myep:
channels: [ "myep" ]
audit:
channels: [ "audit" ]
type: rotating_file
path: '%kernel.logs_dir%/myep.%kernel.environment%.log'
path: '%kernel.logs_dir%/audit.%kernel.environment%.log'
max_files: 5
level: info
database:
channels: ["myep"]
channels: ["audit"]
type: service
id: App\Logging\DatabaseHandler
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!bpn", "!myep"]
channels: ["!event", "!doctrine", "!bpn", "!audit"]
+4 -1
View File
@@ -43,7 +43,10 @@ services:
autoconfigure: true
bind:
$tempDir: '%kernel.project_dir%/temp'
$logger: '@monolog.logger.myep'
# Application audit channel, not a MyE&P one: this is what feeds the
# log table via App\Logging\DatabaseHandler. Unbinding a service from
# it drops its records from that table.
$logger: '@monolog.logger.audit'
$houses: '%houses%'
$xmlExport: '@xml_export.storage'
$xmlDump: '@xml_dump.storage'
+13
View File
@@ -49,6 +49,9 @@ class MyEpAuthenticator extends AbstractAuthenticator
try {
$accessToken = $this->client->fetchAccessToken($request);
} catch (AuthorizationRequestException|IdentityProviderException $e) {
$this->logger->error('Login via MyE&P failed due to unobtainable access token', [
'exception' => $e,
]);
throw new CustomUserMessageAuthenticationException('Invalid token');
}
@@ -110,11 +113,21 @@ class MyEpAuthenticator extends AbstractAuthenticator
{
// User is expected to have at least one role
if (false === isset($userinfo['roles']) || 0 === count($userinfo['roles'])) {
// Claim keys only, the payload itself carries the full profile
$this->logger->warning('Login via MyE&P failed due to missing roles claim', [
'claims' => array_keys($userinfo),
]);
return null;
}
// User is expected to have at least one of the roles teamer, manager, house manager or admin
if ([] === array_intersect(self::ELIGIBLE_ROLES, $userinfo['roles'])) {
$this->logger->warning('Login via MyE&P failed due to lack of an eligible role', [
'roles' => $userinfo['roles'],
'eligible_roles' => self::ELIGIBLE_ROLES,
]);
return null;
}
+8 -2
View File
@@ -6,6 +6,7 @@ use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Provider\GenericProvider;
use League\OAuth2\Client\Token\AccessTokenInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -14,8 +15,11 @@ class MyEpClient
{
private array $config;
public function __construct(private readonly UrlGeneratorInterface $urlGenerator, array $options)
{
public function __construct(
private readonly UrlGeneratorInterface $urlGenerator,
private readonly LoggerInterface $logger,
array $options,
) {
$this->config = $this->resolveConfig($options);
}
@@ -26,6 +30,7 @@ class MyEpClient
public function fetchAccessToken(Request $request): AccessTokenInterface
{
if (null === $code = $request->query->get('code')) {
$this->logger->error('OAuth2 login request missing code');
throw new AuthorizationRequestException('Missing code', 400, $request);
}
@@ -36,6 +41,7 @@ class MyEpClient
|| $request->query->get('state') !== $session->get('oauth2state')
) {
$session->remove('oauth2state');
$this->logger->error('OAuth2 login request missing state or mismatch');
throw new AuthorizationRequestException('Missing state or mismatch', 400, $request);
}