From 424a779577c7ea3072fcc64d473ac9f449e92560 Mon Sep 17 00:00:00 2001
From: yena <yena@systemli.org>
Date: Sat, 30 Nov 2024 22:02:07 +0100
Subject: [PATCH] Unify Api Access Token Handlers

---
 .env.test                                    |  9 +++---
 config/packages/security.yaml                | 18 +++++------
 config/services.yaml                         | 19 +++--------
 src/Security/ApiAccessTokenHandler.php       | 33 ++++++++++++++++++++
 src/Security/DovecotAccessTokenHandler.php   | 21 -------------
 src/Security/KeycloakAccessTokenHandler.php  | 22 -------------
 src/Security/PostfixAccessTokenHandler.php   | 23 --------------
 src/Security/RetentionAccessTokenHandler.php | 23 --------------
 tests/Controller/DovecotControllerTest.php   | 21 ++++++-------
 tests/Controller/KeycloakControllerTest.php  | 16 +++++-----
 tests/Controller/PostfixControllerTest.php   |  8 ++---
 tests/Controller/RetentionControllerTest.php | 10 +++---
 12 files changed, 78 insertions(+), 145 deletions(-)
 create mode 100644 src/Security/ApiAccessTokenHandler.php
 delete mode 100644 src/Security/DovecotAccessTokenHandler.php
 delete mode 100644 src/Security/KeycloakAccessTokenHandler.php
 delete mode 100644 src/Security/PostfixAccessTokenHandler.php
 delete mode 100644 src/Security/RetentionAccessTokenHandler.php

diff --git a/.env.test b/.env.test
index 585b660a..2ce61599 100644
--- a/.env.test
+++ b/.env.test
@@ -24,17 +24,16 @@ WEBMAIL_URL="https://webmail.example.org"
 WKD_DIRECTORY="/tmp/.well-known/openpgpkey"
 WKD_FORMAT="advanced"
 RETENTION_API_ENABLED=true
-RETENTION_API_ACCESS_TOKEN="insecure"
+RETENTION_API_ACCESS_TOKEN="retention"
 RETENTION_API_IP_ALLOWLIST="127.0.0.1, ::1"
 KEYCLOAK_API_ENABLED=true
-KEYCLOAK_API_ACCESS_TOKEN="insecure"
+KEYCLOAK_API_ACCESS_TOKEN="keycloak"
 KEYCLOAK_API_IP_ALLOWLIST="127.0.0.1, ::1"
 POSTFIX_API_ENABLED=true
-POSTFIX_API_ACCESS_TOKEN="insecure"
+POSTFIX_API_ACCESS_TOKEN="postfix"
 POSTFIX_API_IP_ALLOWLIST="127.0.0.1, ::1"
 ROUNDCUBE_API_ENABLED=true
-ROUNDCUBE_API_ACCESS_TOKEN="insecure"
 ROUNDCUBE_API_IP_ALLOWLIST="127.0.0.1, ::1"
 DOVECOT_API_ENABLED=true
-DOVECOT_API_ACCESS_TOKEN="insecure"
+DOVECOT_API_ACCESS_TOKEN="dovecot"
 DOVECOT_API_IP_ALLOWLIST="127.0.0.1, ::1"
diff --git a/config/packages/security.yaml b/config/packages/security.yaml
index 629af03b..816f07ea 100755
--- a/config/packages/security.yaml
+++ b/config/packages/security.yaml
@@ -130,31 +130,31 @@ security:
             stateless: true
             provider: retention
             access_token:
-                token_handler: App\Security\RetentionAccessTokenHandler
+                token_handler: App\Security\ApiAccessTokenHandler
         keycloak:
             pattern: ^/api/keycloak
             stateless: true
             provider: keycloak
             access_token:
-                token_handler: App\Security\KeycloakAccessTokenHandler
+                token_handler: App\Security\ApiAccessTokenHandler
         postfix:
             pattern: ^/api/postfix
             stateless: true
             provider: postfix
             access_token:
-                token_handler: App\Security\PostfixAccessTokenHandler
+                token_handler: App\Security\ApiAccessTokenHandler
+        dovecot:
+            pattern: ^/api/dovecot
+            stateless: true
+            provider: dovecot
+            access_token:
+                token_handler: App\Security\ApiAccessTokenHandler
         roundcube:
             pattern: ^/api/roundcube
             stateless: true
             provider: user
             http_basic:
                 realm: Roundcube API
-        dovecot:
-            pattern: ^/api/dovecot
-            stateless: true
-            provider: dovecot
-            access_token:
-                token_handler: App\Security\DovecotAccessTokenHandler
         main:
             pattern: ^/
             provider: user
diff --git a/config/services.yaml b/config/services.yaml
index a568a500..4f63973e 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -171,21 +171,12 @@ services:
             - '@Doctrine\ORM\EntityManagerInterface'
         public: true
 
-    App\Security\RetentionAccessTokenHandler:
+    App\Security\ApiAccessTokenHandler:
         arguments:
-            $retentionAccessToken: "%env(RETENTION_API_ACCESS_TOKEN)%"
-
-    App\Security\KeycloakAccessTokenHandler:
-        arguments:
-            $keycloakApiAccessToken: "%env(KEYCLOAK_API_ACCESS_TOKEN)%"
-
-    App\Security\PostfixAccessTokenHandler:
-        arguments:
-            $postfixApiAccessToken: "%env(POSTFIX_API_ACCESS_TOKEN)%"
-
-    App\Security\DovecotAccessTokenHandler:
-        arguments:
-            $dovecotApiAccessToken: "%env(DOVECOT_API_ACCESS_TOKEN)%"
+            $accessTokenDovecot: '%env(DOVECOT_API_ACCESS_TOKEN)%'
+            $accessTokenKeycloak: '%env(KEYCLOAK_API_ACCESS_TOKEN)%'
+            $accessTokenRetention: '%env(RETENTION_API_ACCESS_TOKEN)%'
+            $accessTokenPostfix: '%env(POSTFIX_API_ACCESS_TOKEN)%'
 
     App\Sender\WelcomeMessageSender:
         public: true
diff --git a/src/Security/ApiAccessTokenHandler.php b/src/Security/ApiAccessTokenHandler.php
new file mode 100644
index 00000000..25f5221c
--- /dev/null
+++ b/src/Security/ApiAccessTokenHandler.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Security;
+
+use Symfony\Component\Security\Core\Exception\BadCredentialsException;
+use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
+use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
+
+class ApiAccessTokenHandler implements AccessTokenHandlerInterface
+{
+    public function __construct(
+        private string $accessTokenDovecot,
+        private string $accessTokenKeycloak,
+        private string $accessTokenPostfix,
+        private string $accessTokenRetention,
+    ) {}
+
+    public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
+    {
+        switch ($accessToken) {
+            case $this->accessTokenDovecot:
+                return new UserBadge('dovecot');
+            case $this->accessTokenKeycloak:
+                return new UserBadge('keycloak');
+            case $this->accessTokenRetention:
+                return new UserBadge('retention');
+            case $this->accessTokenPostfix:
+                return new UserBadge('postfix');
+            default:
+                throw new BadCredentialsException('Invalid access token');
+        }
+    }
+}
diff --git a/src/Security/DovecotAccessTokenHandler.php b/src/Security/DovecotAccessTokenHandler.php
deleted file mode 100644
index 83009275..00000000
--- a/src/Security/DovecotAccessTokenHandler.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-namespace App\Security;
-
-use Symfony\Component\Security\Core\Exception\BadCredentialsException;
-use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
-use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
-
-class DovecotAccessTokenHandler implements AccessTokenHandlerInterface
-{
-    public function __construct(private string $dovecotApiAccessToken) {}
-
-    public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
-    {
-        if ($accessToken !== $this->dovecotApiAccessToken) {
-            throw new BadCredentialsException('Invalid access token');
-        }
-
-        return new UserBadge('dovecot');
-    }
-}
diff --git a/src/Security/KeycloakAccessTokenHandler.php b/src/Security/KeycloakAccessTokenHandler.php
deleted file mode 100644
index 3a0a93c0..00000000
--- a/src/Security/KeycloakAccessTokenHandler.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-namespace App\Security;
-
-use Symfony\Component\Security\Core\Exception\BadCredentialsException;
-use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
-use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
-
-class KeycloakAccessTokenHandler implements AccessTokenHandlerInterface
-{
-    public function __construct(private string $keycloakApiAccessToken)
-    {
-    }
-
-    public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge {
-        if ($accessToken !== $this->keycloakApiAccessToken) {
-            throw new BadCredentialsException('Invalid access token');
-        }
-
-        return new UserBadge('keycloak');
-    }
-}
diff --git a/src/Security/PostfixAccessTokenHandler.php b/src/Security/PostfixAccessTokenHandler.php
deleted file mode 100644
index 33830efb..00000000
--- a/src/Security/PostfixAccessTokenHandler.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace App\Security;
-
-use Symfony\Component\Security\Core\Exception\BadCredentialsException;
-use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
-use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
-
-readonly class PostfixAccessTokenHandler implements AccessTokenHandlerInterface
-{
-    public function __construct(private string $postfixApiAccessToken)
-    {
-    }
-
-    public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
-    {
-        if ($accessToken !== $this->postfixApiAccessToken) {
-            throw new BadCredentialsException('Invalid access token');
-        }
-
-        return new UserBadge('postfix');
-    }
-}
diff --git a/src/Security/RetentionAccessTokenHandler.php b/src/Security/RetentionAccessTokenHandler.php
deleted file mode 100644
index 0ba9b5bf..00000000
--- a/src/Security/RetentionAccessTokenHandler.php
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-namespace App\Security;
-
-use Symfony\Component\Security\Core\Exception\BadCredentialsException;
-use Symfony\Component\Security\Http\AccessToken\AccessTokenHandlerInterface;
-use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
-
-class RetentionAccessTokenHandler implements AccessTokenHandlerInterface
-{
-    public function __construct(private string $retentionAccessToken)
-    {
-    }
-
-    public function getUserBadgeFrom(#[\SensitiveParameter] string $accessToken): UserBadge
-    {
-        if ($accessToken !== $this->retentionAccessToken) {
-            throw new BadCredentialsException('Invalid access token');
-        }
-
-        return new UserBadge('retention');
-    }
-}
diff --git a/tests/Controller/DovecotControllerTest.php b/tests/Controller/DovecotControllerTest.php
index e9abe917..fb47dce7 100644
--- a/tests/Controller/DovecotControllerTest.php
+++ b/tests/Controller/DovecotControllerTest.php
@@ -9,7 +9,7 @@ class DovecotControllerTest extends WebTestCase
     public function testStatus(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('GET', '/api/dovecot/status');
 
@@ -29,7 +29,7 @@ public function testStatusWrongApiToken(): void
     public function testPassdbUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('POST', '/api/dovecot/support@example.org', ['password' => 'password']);
 
@@ -39,7 +39,7 @@ public function testPassdbUser(): void
     public function testPassdbUserWrongPassword(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('POST', '/api/dovecot/support@example.org', ['password' => 'wrong']);
 
@@ -49,7 +49,7 @@ public function testPassdbUserWrongPassword(): void
     public function testPassdbNonexistentUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('POST', '/api/dovecot/nonexistent@example.org', ['password' => 'password']);
 
@@ -59,7 +59,7 @@ public function testPassdbNonexistentUser(): void
     public function testPassdbSpamUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('POST', '/api/dovecot/spam@example.org', ['password' => 'password']);
 
@@ -69,7 +69,7 @@ public function testPassdbSpamUser(): void
     public function testPassdbMailCrypt(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('POST', '/api/dovecot/mailcrypt@example.org', ['password' => 'password']);
 
@@ -82,7 +82,7 @@ public function testPassdbMailCrypt(): void
     public function testUserdbUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('GET', '/api/dovecot/user@example.org');
 
@@ -96,13 +96,12 @@ public function testUserdbUser(): void
         self::assertIsInt($data['body']['gid']);
         self::assertIsInt($data['body']['uid']);
         self::assertNotEquals($data['body']['home'], '');
-
     }
 
     public function testUserdbMailcrypt(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('GET', '/api/dovecot/mailcrypt@example.org');
 
@@ -119,7 +118,7 @@ public function testUserdbMailcrypt(): void
     public function testUserdbNonexistentUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('GET', '/api/dovecot/nonexistent@example.org');
 
@@ -130,7 +129,7 @@ public function testUserdbNonexistentUser(): void
     public function testUserdbSpamUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer dovecot',
         ]);
         $client->request('GET', '/api/dovecot/spam@example.org');
 
diff --git a/tests/Controller/KeycloakControllerTest.php b/tests/Controller/KeycloakControllerTest.php
index 63cb1db1..e264a285 100644
--- a/tests/Controller/KeycloakControllerTest.php
+++ b/tests/Controller/KeycloakControllerTest.php
@@ -20,7 +20,7 @@ public function testGetUsersSearchWrongApiToken(): void
     public function testGetUsersSearch(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('GET', '/api/keycloak/example.org?search=example&max=2');
 
@@ -37,7 +37,7 @@ public function testGetUsersSearch(): void
     public function testGetUsersSearchNonexistentDomain(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('GET', '/api/keycloak/nonexistent.org?search=example&max=2');
 
@@ -47,7 +47,7 @@ public function testGetUsersSearchNonexistentDomain(): void
     public function testGetUsersCount(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('GET', '/api/keycloak/example.org/count');
 
@@ -60,7 +60,7 @@ public function testGetUsersCount(): void
     public function testGetOneUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('GET', '/api/keycloak/example.org/user/user@example.org');
 
@@ -74,7 +74,7 @@ public function testGetOneUser(): void
     public function testGetOneNonexistentUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('GET', '/api/keycloak/example.org/user/nonexistent@example.org');
 
@@ -84,7 +84,7 @@ public function testGetOneNonexistentUser(): void
     public function testPostUserValidate(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('POST', '/api/keycloak/example.org/validate/support@example.org', ['credentialType' => 'password', 'password' => 'password']);
 
@@ -114,7 +114,7 @@ public function testPostUserValidate(): void
     public function testPostUserValidateOTP(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('POST', '/api/keycloak/example.org/validate/support@example.org', ['credentialType' => 'otp', 'password' => '123456']);
         self::assertResponseStatusCodeSame(403);
@@ -131,7 +131,7 @@ public function testPostUserValidateOTP(): void
     public function testGetIsConfiguredFor(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer keycloak',
         ]);
         $client->request('GET', '/api/keycloak/example.org/configured/otp/support@example.org');
         self::assertResponseStatusCodeSame(404);
diff --git a/tests/Controller/PostfixControllerTest.php b/tests/Controller/PostfixControllerTest.php
index 29a83e4e..432154ee 100644
--- a/tests/Controller/PostfixControllerTest.php
+++ b/tests/Controller/PostfixControllerTest.php
@@ -19,7 +19,7 @@ public function testGetAliasUsersWrongApiToken(): void
     public function testGetAliasUsers(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer postfix',
         ]);
 
         $client->request('GET', '/api/postfix/alias/alias@example.org');
@@ -44,7 +44,7 @@ public function testGetDomainWrongApiToken(): void
     public function testGetDomain(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer postfix',
         ]);
         $client->request('GET', '/api/postfix/domain/example.org');
 
@@ -65,7 +65,7 @@ public function testGetMailboxWrongApiToken(): void
     public function testGetMailbox(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer postfix',
         ]);
         $client->request('GET', '/api/postfix/mailbox/user@example.org');
 
@@ -86,7 +86,7 @@ public function testGetSendersWrongApiToken(): void
     public function testGetSenders(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer postfix',
         ]);
         $client->request('GET', '/api/postfix/senders/user@example.org');
 
diff --git a/tests/Controller/RetentionControllerTest.php b/tests/Controller/RetentionControllerTest.php
index e8c0e780..11e984e7 100644
--- a/tests/Controller/RetentionControllerTest.php
+++ b/tests/Controller/RetentionControllerTest.php
@@ -19,7 +19,7 @@ public function testPutTouchUserWrongApiToken(): void
     public function testPutTouchUserUnknownUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer retention',
         ]);
 
         $client->request('PUT', '/api/retention/nonexistant@example.org/touch');
@@ -29,7 +29,7 @@ public function testPutTouchUserUnknownUser(): void
     public function testPutTouchUserTimestampInFuture(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer retention',
         ]);
 
         $client->request('PUT', '/api/retention/user@example.org/touch', ['timestamp' => 999999999999]);
@@ -39,7 +39,7 @@ public function testPutTouchUserTimestampInFuture(): void
     public function testPutTouchUser(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer retention',
         ]);
 
         $client->request('PUT', '/api/retention/user@example.org/touch', ['timestamp' => 0]);
@@ -50,7 +50,7 @@ public function testPutTouchUser(): void
     public function testGetDeletedUsersNonexistantDomain(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer retention',
         ]);
 
         $client->request('GET', '/api/retention/nonexistant.org/users');
@@ -60,7 +60,7 @@ public function testGetDeletedUsersNonexistantDomain(): void
     public function testGetDeletedUsers(): void
     {
         $client = static::createClient([], [
-            'HTTP_Authorization' => 'Bearer insecure',
+            'HTTP_Authorization' => 'Bearer retention',
         ]);
 
         $client->request('GET', '/api/retention/example.org/users');