diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
old mode 100644
new mode 100755
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
old mode 100644
new mode 100755
index 88e2c4a2d435e9dc3f5411daece41c6d5bb39d06..af5398e22a77363683d08ed53da347c594f40b77
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,11 +2,11 @@
 # Composer stores all downloaded packages in the vendor/ directory.
 # Do not use the following if the vendor/ directory is committed to
 # your git repository.
-cache:
-  # We key the cache using the commit unique identifier.
-  key: ${CI_COMMIT_REF_SLUG}
-  paths:
-    - vendor/
+#cache:
+#  # We key the cache using the commit unique identifier.
+#  key: ${CI_COMMIT_REF_SLUG}
+#  paths:
+#    - vendor/
 
 # List of stages for jobs, and their order of execution
 stages:
@@ -21,6 +21,7 @@ test-74:
   image: cicnavi/dap:74
   script:
     - composer install --prefer-dist --no-progress --no-suggest
+    - vendor/bin/psalm --clear-cache
     - composer run-script pre-commit
 
 # PHP v8.1
@@ -31,6 +32,7 @@ test-81:
   image: cicnavi/dap:81
   script:
     - composer install --prefer-dist --no-progress --no-suggest
+    - vendor/bin/psalm --clear-cache
     - composer run-script pre-commit
 
 # TODO mivanci remove when GEANT project finishes
diff --git a/LICENSE b/LICENSE
old mode 100644
new mode 100755
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
diff --git a/composer.json b/composer.json
old mode 100644
new mode 100755
diff --git a/composer.lock b/composer.lock
index 8cb9d327edc5891640e19808a8f8b4bbfe10a7a9..121a1d684c1947f0ad6b3d1ef513c25442ff6168 100644
--- a/composer.lock
+++ b/composer.lock
@@ -266,16 +266,16 @@
         },
         {
             "name": "doctrine/deprecations",
-            "version": "v1.0.0",
+            "version": "v1.1.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/deprecations.git",
-                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
+                "reference": "8cffffb2218e01f3b370bf763e00e81697725259"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
-                "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
+                "url": "https://api.github.com/repos/doctrine/deprecations/zipball/8cffffb2218e01f3b370bf763e00e81697725259",
+                "reference": "8cffffb2218e01f3b370bf763e00e81697725259",
                 "shasum": ""
             },
             "require": {
@@ -303,9 +303,9 @@
             "homepage": "https://www.doctrine-project.org/",
             "support": {
                 "issues": "https://github.com/doctrine/deprecations/issues",
-                "source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
+                "source": "https://github.com/doctrine/deprecations/tree/v1.1.0"
             },
-            "time": "2022-05-02T15:47:09+00:00"
+            "time": "2023-05-29T18:55:17+00:00"
         },
         {
             "name": "doctrine/event-manager",
@@ -1083,16 +1083,16 @@
         },
         {
             "name": "composer/composer",
-            "version": "2.5.5",
+            "version": "2.5.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/composer/composer.git",
-                "reference": "c7cffaad16a60636a776017eac5bd8cd0095c32f"
+                "reference": "d477018d3f2ebd76dede3d3988a0b1a7add4d81e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/composer/composer/zipball/c7cffaad16a60636a776017eac5bd8cd0095c32f",
-                "reference": "c7cffaad16a60636a776017eac5bd8cd0095c32f",
+                "url": "https://api.github.com/repos/composer/composer/zipball/d477018d3f2ebd76dede3d3988a0b1a7add4d81e",
+                "reference": "d477018d3f2ebd76dede3d3988a0b1a7add4d81e",
                 "shasum": ""
             },
             "require": {
@@ -1176,7 +1176,7 @@
             "support": {
                 "irc": "ircs://irc.libera.chat:6697/composer",
                 "issues": "https://github.com/composer/composer/issues",
-                "source": "https://github.com/composer/composer/tree/2.5.5"
+                "source": "https://github.com/composer/composer/tree/2.5.7"
             },
             "funding": [
                 {
@@ -1192,7 +1192,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-21T10:50:05+00:00"
+            "time": "2023-05-24T13:00:40+00:00"
         },
         {
             "name": "composer/metadata-minifier",
@@ -2196,21 +2196,21 @@
         },
         {
             "name": "guzzlehttp/guzzle",
-            "version": "7.5.1",
+            "version": "7.7.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/guzzle.git",
-                "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9"
+                "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9",
-                "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5",
+                "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
-                "guzzlehttp/promises": "^1.5",
+                "guzzlehttp/promises": "^1.5.3 || ^2.0",
                 "guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
                 "php": "^7.2.5 || ^8.0",
                 "psr/http-client": "^1.0",
@@ -2222,7 +2222,8 @@
             "require-dev": {
                 "bamarni/composer-bin-plugin": "^1.8.1",
                 "ext-curl": "*",
-                "php-http/client-integration-tests": "^3.0",
+                "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999",
+                "php-http/message-factory": "^1.1",
                 "phpunit/phpunit": "^8.5.29 || ^9.5.23",
                 "psr/log": "^1.1 || ^2.0 || ^3.0"
             },
@@ -2236,9 +2237,6 @@
                 "bamarni-bin": {
                     "bin-links": true,
                     "forward-command": false
-                },
-                "branch-alias": {
-                    "dev-master": "7.5-dev"
                 }
             },
             "autoload": {
@@ -2304,7 +2302,7 @@
             ],
             "support": {
                 "issues": "https://github.com/guzzle/guzzle/issues",
-                "source": "https://github.com/guzzle/guzzle/tree/7.5.1"
+                "source": "https://github.com/guzzle/guzzle/tree/7.7.0"
             },
             "funding": [
                 {
@@ -2320,38 +2318,37 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-17T16:30:08+00:00"
+            "time": "2023-05-21T14:04:53+00:00"
         },
         {
             "name": "guzzlehttp/promises",
-            "version": "1.5.2",
+            "version": "2.0.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/promises.git",
-                "reference": "b94b2807d85443f9719887892882d0329d1e2598"
+                "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
-                "reference": "b94b2807d85443f9719887892882d0329d1e2598",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
+                "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6",
                 "shasum": ""
             },
             "require": {
-                "php": ">=5.5"
+                "php": "^7.2.5 || ^8.0"
             },
             "require-dev": {
-                "symfony/phpunit-bridge": "^4.4 || ^5.1"
+                "bamarni/composer-bin-plugin": "^1.8.1",
+                "phpunit/phpunit": "^8.5.29 || ^9.5.23"
             },
             "type": "library",
             "extra": {
-                "branch-alias": {
-                    "dev-master": "1.5-dev"
+                "bamarni-bin": {
+                    "bin-links": true,
+                    "forward-command": false
                 }
             },
             "autoload": {
-                "files": [
-                    "src/functions_include.php"
-                ],
                 "psr-4": {
                     "GuzzleHttp\\Promise\\": "src/"
                 }
@@ -2388,7 +2385,7 @@
             ],
             "support": {
                 "issues": "https://github.com/guzzle/promises/issues",
-                "source": "https://github.com/guzzle/promises/tree/1.5.2"
+                "source": "https://github.com/guzzle/promises/tree/2.0.0"
             },
             "funding": [
                 {
@@ -2404,7 +2401,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-08-28T14:55:35+00:00"
+            "time": "2023-05-21T13:50:22+00:00"
         },
         {
             "name": "guzzlehttp/psr7",
@@ -3681,16 +3678,16 @@
         },
         {
             "name": "nikic/php-parser",
-            "version": "v4.15.4",
+            "version": "v4.15.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nikic/PHP-Parser.git",
-                "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
+                "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
-                "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
+                "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
                 "shasum": ""
             },
             "require": {
@@ -3731,9 +3728,9 @@
             ],
             "support": {
                 "issues": "https://github.com/nikic/PHP-Parser/issues",
-                "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
+                "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
             },
-            "time": "2023-03-05T19:49:14+00:00"
+            "time": "2023-05-19T20:20:00+00:00"
         },
         {
             "name": "paragonie/random_compat",
@@ -4008,16 +4005,16 @@
         },
         {
             "name": "phpdocumentor/type-resolver",
-            "version": "1.7.1",
+            "version": "1.7.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpDocumentor/TypeResolver.git",
-                "reference": "dfc078e8af9c99210337325ff5aa152872c98714"
+                "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714",
-                "reference": "dfc078e8af9c99210337325ff5aa152872c98714",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b2fe4d22a5426f38e014855322200b97b5362c0d",
+                "reference": "b2fe4d22a5426f38e014855322200b97b5362c0d",
                 "shasum": ""
             },
             "require": {
@@ -4060,9 +4057,9 @@
             "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
             "support": {
                 "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
-                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1"
+                "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.2"
             },
-            "time": "2023-03-27T19:02:04+00:00"
+            "time": "2023-05-30T18:13:47+00:00"
         },
         {
             "name": "phpmailer/phpmailer",
@@ -4146,22 +4143,23 @@
         },
         {
             "name": "phpstan/phpdoc-parser",
-            "version": "1.20.4",
+            "version": "1.21.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpdoc-parser.git",
-                "reference": "7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd"
+                "reference": "b0c366dd2cea79407d635839d25423ba07c55dd6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd",
-                "reference": "7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd",
+                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/b0c366dd2cea79407d635839d25423ba07c55dd6",
+                "reference": "b0c366dd2cea79407d635839d25423ba07c55dd6",
                 "shasum": ""
             },
             "require": {
                 "php": "^7.2 || ^8.0"
             },
             "require-dev": {
+                "nikic/php-parser": "^4.15",
                 "php-parallel-lint/php-parallel-lint": "^1.2",
                 "phpstan/extension-installer": "^1.0",
                 "phpstan/phpstan": "^1.5",
@@ -4185,9 +4183,9 @@
             "description": "PHPDoc parser with support for nullable, intersection and generic types",
             "support": {
                 "issues": "https://github.com/phpstan/phpdoc-parser/issues",
-                "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.4"
+                "source": "https://github.com/phpstan/phpdoc-parser/tree/1.21.3"
             },
-            "time": "2023-05-02T09:19:37+00:00"
+            "time": "2023-05-29T19:31:28+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
@@ -4509,16 +4507,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "9.6.7",
+            "version": "9.6.8",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2"
+                "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
-                "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/17d621b3aff84d0c8b62539e269e87d8d5baa76e",
+                "reference": "17d621b3aff84d0c8b62539e269e87d8d5baa76e",
                 "shasum": ""
             },
             "require": {
@@ -4592,7 +4590,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.8"
             },
             "funding": [
                 {
@@ -4608,7 +4606,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-14T08:58:40+00:00"
+            "time": "2023-05-11T05:14:45+00:00"
         },
         {
             "name": "psr/container",
@@ -6048,16 +6046,16 @@
         },
         {
             "name": "seld/jsonlint",
-            "version": "1.9.0",
+            "version": "1.10.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/jsonlint.git",
-                "reference": "4211420d25eba80712bff236a98960ef68b866b7"
+                "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/4211420d25eba80712bff236a98960ef68b866b7",
-                "reference": "4211420d25eba80712bff236a98960ef68b866b7",
+                "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/594fd6462aad8ecee0b45ca5045acea4776667f1",
+                "reference": "594fd6462aad8ecee0b45ca5045acea4776667f1",
                 "shasum": ""
             },
             "require": {
@@ -6096,7 +6094,7 @@
             ],
             "support": {
                 "issues": "https://github.com/Seldaek/jsonlint/issues",
-                "source": "https://github.com/Seldaek/jsonlint/tree/1.9.0"
+                "source": "https://github.com/Seldaek/jsonlint/tree/1.10.0"
             },
             "funding": [
                 {
@@ -6108,7 +6106,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2022-04-01T13:37:23+00:00"
+            "time": "2023-05-11T13:16:46+00:00"
         },
         {
             "name": "seld/phar-utils",
@@ -6221,16 +6219,16 @@
         },
         {
             "name": "simplesamlphp/saml2",
-            "version": "v4.6.8",
+            "version": "v4.6.10",
             "source": {
                 "type": "git",
                 "url": "https://github.com/simplesamlphp/saml2.git",
-                "reference": "dea19260955a863f6a347e8479a2049d99b5cd18"
+                "reference": "a6c46e8134df2686da9ad44bc9b8f85443c03440"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/simplesamlphp/saml2/zipball/dea19260955a863f6a347e8479a2049d99b5cd18",
-                "reference": "dea19260955a863f6a347e8479a2049d99b5cd18",
+                "url": "https://api.github.com/repos/simplesamlphp/saml2/zipball/a6c46e8134df2686da9ad44bc9b8f85443c03440",
+                "reference": "a6c46e8134df2686da9ad44bc9b8f85443c03440",
                 "shasum": ""
             },
             "require": {
@@ -6273,22 +6271,22 @@
             "description": "SAML2 PHP library from SimpleSAMLphp",
             "support": {
                 "issues": "https://github.com/simplesamlphp/saml2/issues",
-                "source": "https://github.com/simplesamlphp/saml2/tree/v4.6.8"
+                "source": "https://github.com/simplesamlphp/saml2/tree/v4.6.10"
             },
-            "time": "2023-05-05T11:55:46+00:00"
+            "time": "2023-05-31T16:03:51+00:00"
         },
         {
             "name": "simplesamlphp/simplesamlphp",
-            "version": "2.0.3",
+            "version": "2.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/simplesamlphp/simplesamlphp.git",
-                "reference": "de912366cb73087889c580dca354582e8ef560e0"
+                "reference": "7f372865af8317450580373c601315eb1c2e19ad"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/simplesamlphp/simplesamlphp/zipball/de912366cb73087889c580dca354582e8ef560e0",
-                "reference": "de912366cb73087889c580dca354582e8ef560e0",
+                "url": "https://api.github.com/repos/simplesamlphp/simplesamlphp/zipball/7f372865af8317450580373c601315eb1c2e19ad",
+                "reference": "7f372865af8317450580373c601315eb1c2e19ad",
                 "shasum": ""
             },
             "require": {
@@ -6393,7 +6391,7 @@
                 "issues": "https://github.com/simplesamlphp/simplesamlphp/issues",
                 "source": "https://github.com/simplesamlphp/simplesamlphp"
             },
-            "time": "2023-03-29T20:24:27+00:00"
+            "time": "2023-05-12T15:57:40+00:00"
         },
         {
             "name": "simplesamlphp/simplesamlphp-assets-base",
@@ -6519,16 +6517,16 @@
         },
         {
             "name": "simplesamlphp/simplesamlphp-test-framework",
-            "version": "v1.5.4",
+            "version": "v1.5.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/simplesamlphp/simplesamlphp-test-framework.git",
-                "reference": "b627dd12d1d5bb50cef5336b9726f3a2d1b4969e"
+                "reference": "98e550a86b3c630820b363575a72b7f7073ef589"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/simplesamlphp/simplesamlphp-test-framework/zipball/b627dd12d1d5bb50cef5336b9726f3a2d1b4969e",
-                "reference": "b627dd12d1d5bb50cef5336b9726f3a2d1b4969e",
+                "url": "https://api.github.com/repos/simplesamlphp/simplesamlphp-test-framework/zipball/98e550a86b3c630820b363575a72b7f7073ef589",
+                "reference": "98e550a86b3c630820b363575a72b7f7073ef589",
                 "shasum": ""
             },
             "require": {
@@ -6562,7 +6560,7 @@
                 "issues": "https://github.com/simplesamlphp/simplesamlphp-test-framework/issues",
                 "source": "https://github.com/simplesamlphp/simplesamlphp-test-framework"
             },
-            "time": "2023-03-16T20:42:22+00:00"
+            "time": "2023-05-20T08:39:41+00:00"
         },
         {
             "name": "spatie/array-to-xml",
@@ -7122,16 +7120,16 @@
         },
         {
             "name": "symfony/console",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "90f21e27d0d88ce38720556dd164d4a1e4c3934c"
+                "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/90f21e27d0d88ce38720556dd164d4a1e4c3934c",
-                "reference": "90f21e27d0d88ce38720556dd164d4a1e4c3934c",
+                "url": "https://api.github.com/repos/symfony/console/zipball/560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8",
+                "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8",
                 "shasum": ""
             },
             "require": {
@@ -7201,7 +7199,7 @@
                 "terminal"
             ],
             "support": {
-                "source": "https://github.com/symfony/console/tree/v5.4.23"
+                "source": "https://github.com/symfony/console/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -7217,20 +7215,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-24T18:47:29+00:00"
+            "time": "2023-05-26T05:13:16+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "bb7b7988c898c94f5338e16403c52b5a3cae1d93"
+                "reference": "4645e032d0963fb614969398ca28e47605b1a7da"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/bb7b7988c898c94f5338e16403c52b5a3cae1d93",
-                "reference": "bb7b7988c898c94f5338e16403c52b5a3cae1d93",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4645e032d0963fb614969398ca28e47605b1a7da",
+                "reference": "4645e032d0963fb614969398ca28e47605b1a7da",
                 "shasum": ""
             },
             "require": {
@@ -7290,7 +7288,7 @@
             "description": "Allows you to standardize and centralize the way objects are constructed in your application",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/dependency-injection/tree/v5.4.23"
+                "source": "https://github.com/symfony/dependency-injection/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -7306,7 +7304,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-21T15:04:16+00:00"
+            "time": "2023-05-05T14:42:55+00:00"
         },
         {
             "name": "symfony/deprecation-contracts",
@@ -7377,16 +7375,16 @@
         },
         {
             "name": "symfony/error-handler",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/error-handler.git",
-                "reference": "218206b4772d9f412d7d277980c020d06e9d8a4e"
+                "reference": "c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/error-handler/zipball/218206b4772d9f412d7d277980c020d06e9d8a4e",
-                "reference": "218206b4772d9f412d7d277980c020d06e9d8a4e",
+                "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946",
+                "reference": "c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946",
                 "shasum": ""
             },
             "require": {
@@ -7428,7 +7426,7 @@
             "description": "Provides tools to manage errors and ease debugging PHP code",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/error-handler/tree/v5.4.23"
+                "source": "https://github.com/symfony/error-handler/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -7444,7 +7442,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-17T10:03:27+00:00"
+            "time": "2023-05-02T16:13:31+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
@@ -7739,16 +7737,16 @@
         },
         {
             "name": "symfony/framework-bundle",
-            "version": "v5.4.22",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/framework-bundle.git",
-                "reference": "6cb4f6aed4bd7fbf7b2ee74c231184a07f3d00c1"
+                "reference": "c06a56a47817d29318aaace1c655cbde16c998e8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/6cb4f6aed4bd7fbf7b2ee74c231184a07f3d00c1",
-                "reference": "6cb4f6aed4bd7fbf7b2ee74c231184a07f3d00c1",
+                "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c06a56a47817d29318aaace1c655cbde16c998e8",
+                "reference": "c06a56a47817d29318aaace1c655cbde16c998e8",
                 "shasum": ""
             },
             "require": {
@@ -7762,7 +7760,7 @@
                 "symfony/event-dispatcher": "^5.1|^6.0",
                 "symfony/filesystem": "^4.4|^5.0|^6.0",
                 "symfony/finder": "^4.4|^5.0|^6.0",
-                "symfony/http-foundation": "^5.3|^6.0",
+                "symfony/http-foundation": "^5.4.24|^6.2.11",
                 "symfony/http-kernel": "^5.4|^6.0",
                 "symfony/polyfill-mbstring": "~1.0",
                 "symfony/polyfill-php80": "^1.16",
@@ -7775,7 +7773,6 @@
                 "doctrine/persistence": "<1.3",
                 "phpdocumentor/reflection-docblock": "<3.2.2",
                 "phpdocumentor/type-resolver": "<1.4.0",
-                "phpunit/phpunit": "<5.4.3",
                 "symfony/asset": "<5.3",
                 "symfony/console": "<5.2.5",
                 "symfony/dom-crawler": "<4.4",
@@ -7870,7 +7867,7 @@
             "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/framework-bundle/tree/v5.4.22"
+                "source": "https://github.com/symfony/framework-bundle/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -7886,20 +7883,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-03-31T08:25:44+00:00"
+            "time": "2023-05-25T13:05:00+00:00"
         },
         {
             "name": "symfony/http-foundation",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-foundation.git",
-                "reference": "af9fbb378f5f956c8f29d4886644c84c193780ac"
+                "reference": "3c59f97f6249ce552a44f01b93bfcbd786a954f5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/af9fbb378f5f956c8f29d4886644c84c193780ac",
-                "reference": "af9fbb378f5f956c8f29d4886644c84c193780ac",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3c59f97f6249ce552a44f01b93bfcbd786a954f5",
+                "reference": "3c59f97f6249ce552a44f01b93bfcbd786a954f5",
                 "shasum": ""
             },
             "require": {
@@ -7946,7 +7943,7 @@
             "description": "Defines an object-oriented layer for the HTTP specification",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-foundation/tree/v5.4.23"
+                "source": "https://github.com/symfony/http-foundation/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -7962,20 +7959,20 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-18T06:30:11+00:00"
+            "time": "2023-05-19T07:21:23+00:00"
         },
         {
             "name": "symfony/http-kernel",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/http-kernel.git",
-                "reference": "48ea17a7c65ef1ede0c3b2dbc35adace99071810"
+                "reference": "f38b722e1557eb3f487d351b48f5a1279b50e9d1"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/48ea17a7c65ef1ede0c3b2dbc35adace99071810",
-                "reference": "48ea17a7c65ef1ede0c3b2dbc35adace99071810",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f38b722e1557eb3f487d351b48f5a1279b50e9d1",
+                "reference": "f38b722e1557eb3f487d351b48f5a1279b50e9d1",
                 "shasum": ""
             },
             "require": {
@@ -8058,7 +8055,7 @@
             "description": "Provides a structured process for converting a Request into a Response",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/http-kernel/tree/v5.4.23"
+                "source": "https://github.com/symfony/http-kernel/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -8074,7 +8071,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-28T13:29:52+00:00"
+            "time": "2023-05-27T08:06:30+00:00"
         },
         {
             "name": "symfony/intl",
@@ -8737,16 +8734,16 @@
         },
         {
             "name": "symfony/process",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "4b842fc4b61609e0a155a114082bd94e31e98287"
+                "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/4b842fc4b61609e0a155a114082bd94e31e98287",
-                "reference": "4b842fc4b61609e0a155a114082bd94e31e98287",
+                "url": "https://api.github.com/repos/symfony/process/zipball/e3c46cc5689c8782944274bb30702106ecbe3b64",
+                "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64",
                 "shasum": ""
             },
             "require": {
@@ -8779,7 +8776,7 @@
             "description": "Executes commands in sub-processes",
             "homepage": "https://symfony.com",
             "support": {
-                "source": "https://github.com/symfony/process/tree/v5.4.23"
+                "source": "https://github.com/symfony/process/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -8795,7 +8792,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-18T13:50:24+00:00"
+            "time": "2023-05-17T11:26:05+00:00"
         },
         {
             "name": "symfony/routing",
@@ -9257,16 +9254,16 @@
         },
         {
             "name": "symfony/var-dumper",
-            "version": "v5.4.23",
+            "version": "v5.4.24",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/var-dumper.git",
-                "reference": "9a8a5b6d6508928174ded2109e29328a55342a42"
+                "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9a8a5b6d6508928174ded2109e29328a55342a42",
-                "reference": "9a8a5b6d6508928174ded2109e29328a55342a42",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/8e12706bf9c68a2da633f23bfdc15b4dce5970b3",
+                "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3",
                 "shasum": ""
             },
             "require": {
@@ -9275,7 +9272,6 @@
                 "symfony/polyfill-php80": "^1.16"
             },
             "conflict": {
-                "phpunit/phpunit": "<5.4.3",
                 "symfony/console": "<4.4"
             },
             "require-dev": {
@@ -9326,7 +9322,7 @@
                 "dump"
             ],
             "support": {
-                "source": "https://github.com/symfony/var-dumper/tree/v5.4.23"
+                "source": "https://github.com/symfony/var-dumper/tree/v5.4.24"
             },
             "funding": [
                 {
@@ -9342,7 +9338,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-18T09:26:27+00:00"
+            "time": "2023-05-25T13:05:00+00:00"
         },
         {
             "name": "symfony/var-exporter",
@@ -9818,16 +9814,16 @@
         },
         {
             "name": "vimeo/psalm",
-            "version": "5.11.0",
+            "version": "5.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/vimeo/psalm.git",
-                "reference": "c9b192ab8400fdaf04b2b13d110575adc879aa90"
+                "reference": "f90118cdeacd0088e7215e64c0c99ceca819e176"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/vimeo/psalm/zipball/c9b192ab8400fdaf04b2b13d110575adc879aa90",
-                "reference": "c9b192ab8400fdaf04b2b13d110575adc879aa90",
+                "url": "https://api.github.com/repos/vimeo/psalm/zipball/f90118cdeacd0088e7215e64c0c99ceca819e176",
+                "reference": "f90118cdeacd0088e7215e64c0c99ceca819e176",
                 "shasum": ""
             },
             "require": {
@@ -9918,9 +9914,9 @@
             ],
             "support": {
                 "issues": "https://github.com/vimeo/psalm/issues",
-                "source": "https://github.com/vimeo/psalm/tree/5.11.0"
+                "source": "https://github.com/vimeo/psalm/tree/5.12.0"
             },
-            "time": "2023-05-04T21:35:44+00:00"
+            "time": "2023-05-22T21:19:03+00:00"
         },
         {
             "name": "web-token/jwt-framework",
diff --git a/config-templates/module_accounting.php b/config-templates/module_accounting.php
old mode 100644
new mode 100755
index a1cc2e7bdc502571235898285f7ba1c48616c2b9..69a9027adaf88b869070fa49ba29857a581ade07
--- a/config-templates/module_accounting.php
+++ b/config-templates/module_accounting.php
@@ -72,10 +72,9 @@ $config = [
     ModuleConfiguration::OPTION_PROVIDER_FOR_CONNECTED_SERVICES =>
         /**
          * Default connected services provider which expects Doctrine DBAL compatible connection to be set below.
-         * CurrentDataProvider only gathers current (latest information) about the service and user (there is no
-         * versioning, so it's faster). VersionedDataProvider keeps track of any changes in data about the service
-         * and user.
-         *
+         * CurrentDataProvider only gathers current (latest information) about the service (there is no
+         * versioning, so it's faster). VersionedDataProvider keeps track of any changes in data about
+         * the service.
          */
         Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider::class,
         //Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider::class,
@@ -91,9 +90,12 @@ $config = [
     ModuleConfiguration::OPTION_PROVIDER_FOR_ACTIVITY =>
         /**
          * Default activity provider which expects Doctrine DBAL compatible connection to be set below.
-         * Currently only VersionedDataProvider is available, which tracks all changes in services and users.
+         * CurrentDataProvider only gathers current (latest information) about the service (there is no
+         * versioning, so it's faster). VersionedDataProvider keeps track of any changes in data about
+         * the service.
          */
-        Providers\Activity\DoctrineDbal\VersionedDataProvider::class,
+        Providers\Activity\DoctrineDbal\CurrentDataProvider::class,
+        //Providers\Activity\DoctrineDbal\VersionedDataProvider::class,
 
     /**
      * Trackers
@@ -134,6 +136,12 @@ $config = [
                 'doctrine_dbal_pdo_mysql',
             ],
         ],
+        Providers\Activity\DoctrineDbal\CurrentDataProvider::class => [
+            ModuleConfiguration\ConnectionType::MASTER => 'doctrine_dbal_pdo_mysql',
+            ModuleConfiguration\ConnectionType::SLAVE => [
+                'doctrine_dbal_pdo_mysql',
+            ],
+        ],
         Providers\Activity\DoctrineDbal\VersionedDataProvider::class => [
             ModuleConfiguration\ConnectionType::MASTER => 'doctrine_dbal_pdo_mysql',
             ModuleConfiguration\ConnectionType::SLAVE => [
diff --git a/hooks/hook_adminmenu.php b/hooks/hook_adminmenu.php
old mode 100644
new mode 100755
diff --git a/hooks/hook_configpage.php b/hooks/hook_configpage.php
old mode 100644
new mode 100755
diff --git a/hooks/hook_cron.php b/hooks/hook_cron.php
old mode 100644
new mode 100755
diff --git a/locales/en/LC_MESSAGES/accounting.po b/locales/en/LC_MESSAGES/accounting.po
old mode 100644
new mode 100755
diff --git a/locales/hr/LC_MESSAGES/accounting.po b/locales/hr/LC_MESSAGES/accounting.po
old mode 100644
new mode 100755
diff --git a/phpcs.xml b/phpcs.xml
old mode 100644
new mode 100755
diff --git a/phpunit.xml b/phpunit.xml
old mode 100644
new mode 100755
diff --git a/psalm.xml b/psalm.xml
old mode 100644
new mode 100755
index 618c01a269b1d4cf27637ff4bba24ed200405b90..62ecf4e6ac338eb82d79a576194f80399f57344b
--- a/psalm.xml
+++ b/psalm.xml
@@ -7,6 +7,7 @@
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns="https://getpsalm.org/schema/config"
     xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
+    cacheDirectory="./build/psalm/cache"
 >
     <projectFiles>
         <directory name="src" />
diff --git a/public/assets/css/src/custom.css b/public/assets/css/src/custom.css
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/default.css b/public/assets/css/src/default.css
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-300.woff b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-300.woff
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-300.woff2 b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-300.woff2
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-500.woff b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-500.woff
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-500.woff2 b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-500.woff2
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-600.woff b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-600.woff
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-600.woff2 b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-600.woff2
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-regular.woff b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-regular.woff
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-regular.woff2 b/public/assets/css/src/fonts/raleway-v28-latin-ext_latin-regular.woff2
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/activity.svg b/public/assets/css/src/icons/activity.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/conn-orgs.svg b/public/assets/css/src/icons/conn-orgs.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/download.svg b/public/assets/css/src/icons/download.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/dropdown.svg b/public/assets/css/src/icons/dropdown.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/fppp-logo.svg b/public/assets/css/src/icons/fppp-logo.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/i.svg b/public/assets/css/src/icons/i.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/logout.svg b/public/assets/css/src/icons/logout.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/prof-page.svg b/public/assets/css/src/icons/prof-page.svg
old mode 100644
new mode 100755
diff --git a/public/assets/css/src/icons/x.svg b/public/assets/css/src/icons/x.svg
old mode 100644
new mode 100755
diff --git a/routing/routes/routes.yml b/routing/routes/routes.yml
old mode 100644
new mode 100755
diff --git a/routing/services/services.yml b/routing/services/services.yml
old mode 100644
new mode 100755
diff --git a/src/Auth/Process/Accounting.php b/src/Auth/Process/Accounting.php
old mode 100644
new mode 100755
diff --git a/src/Data/Providers/Activity/DoctrineDbal/CurrentDataProvider.php b/src/Data/Providers/Activity/DoctrineDbal/CurrentDataProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..a38b42baff5512d189c53c9901288de3d43c9e32
--- /dev/null
+++ b/src/Data/Providers/Activity/DoctrineDbal/CurrentDataProvider.php
@@ -0,0 +1,95 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal;
+
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\Interfaces\ActivityInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\CurrentDataTracker;
+use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface;
+use SimpleSAML\Module\accounting\Entities\Activity;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+
+class CurrentDataProvider implements ActivityInterface
+{
+    protected ModuleConfiguration $moduleConfiguration;
+    protected LoggerInterface $logger;
+    protected Store $store;
+
+    /**
+     * @throws StoreException
+     */
+    public function __construct(
+        ModuleConfiguration $moduleConfiguration,
+        LoggerInterface $logger,
+        string $connectionType = ModuleConfiguration\ConnectionType::SLAVE,
+        Store $store = null
+    ) {
+        $this->moduleConfiguration = $moduleConfiguration;
+        $this->logger = $logger;
+
+        $this->store = $store ?? new Store(
+            $this->moduleConfiguration,
+            $this->logger,
+            $this->moduleConfiguration->getClassConnectionKey(self::class),
+            $connectionType
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public static function build(
+        ModuleConfiguration $moduleConfiguration,
+        LoggerInterface $logger,
+        string $connectionType = ModuleConfiguration\ConnectionType::SLAVE
+    ): self {
+        return new self($moduleConfiguration, $logger, $connectionType);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function needsSetup(): bool
+    {
+        return $this->store->needsSetup();
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function runSetup(): void
+    {
+        if (! $this->needsSetup()) {
+            $this->logger->warning('Run setup called, however setup is not needed.');
+            return;
+        }
+
+        $this->store->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function getActivity(string $userIdentifier, int $maxResults, int $firstResult): Activity\Bag
+    {
+        return $this->store->getActivity($userIdentifier, $maxResults, $firstResult);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function getTracker(): ?DataTrackerInterface
+    {
+        return new CurrentDataTracker(
+            $this->moduleConfiguration,
+            $this->logger,
+            ModuleConfiguration\ConnectionType::MASTER,
+        );
+    }
+}
diff --git a/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProvider.php b/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProvider.php
old mode 100644
new mode 100755
diff --git a/src/Data/Providers/Builders/DataProviderBuilder.php b/src/Data/Providers/Builders/DataProviderBuilder.php
old mode 100644
new mode 100755
index 7bba072c97be2b569875315bab0c6ea85af885bf..d8f0fd5fafe73ed1622da6ee934429069c7a11c3
--- a/src/Data/Providers/Builders/DataProviderBuilder.php
+++ b/src/Data/Providers/Builders/DataProviderBuilder.php
@@ -64,6 +64,9 @@ class DataProviderBuilder
         return $provider;
     }
 
+    /**
+     * @throws Exception
+     */
     public function buildActivityProvider(
         string $class,
         string $connectionType = ModuleConfiguration\ConnectionType::SLAVE
diff --git a/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProvider.php b/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProvider.php
old mode 100644
new mode 100755
diff --git a/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProvider.php b/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProvider.php
old mode 100644
new mode 100755
diff --git a/src/Data/Providers/Interfaces/ActivityInterface.php b/src/Data/Providers/Interfaces/ActivityInterface.php
old mode 100644
new mode 100755
index 0102c89b48cd3cde707c11769b5c0251b2f7cffd..906727ecb8ca77a538fc741ea44d6d826b1d9cf8
--- a/src/Data/Providers/Interfaces/ActivityInterface.php
+++ b/src/Data/Providers/Interfaces/ActivityInterface.php
@@ -6,8 +6,6 @@ namespace SimpleSAML\Module\accounting\Data\Providers\Interfaces;
 
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Entities\Activity;
-use SimpleSAML\Module\accounting\Interfaces\BuildableUsingModuleConfigurationInterface;
-use SimpleSAML\Module\accounting\Interfaces\SetupableInterface;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 
 interface ActivityInterface extends DataProviderInterface
diff --git a/src/Data/Providers/Interfaces/ConnectedServicesInterface.php b/src/Data/Providers/Interfaces/ConnectedServicesInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Providers/Interfaces/DataProviderInterface.php b/src/Data/Providers/Interfaces/DataProviderInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store.php
new file mode 100644
index 0000000000000000000000000000000000000000..39ea7df96538013b9a82205e146609157086adcc
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store.php
@@ -0,0 +1,97 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current;
+
+use DateTimeImmutable;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Repository;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Traits\Store\GettableActivityTrait;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store as BaseStore;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
+use SimpleSAML\Module\accounting\Data\Stores\Interfaces\ActivityInterface;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+
+class Store extends BaseStore implements ActivityInterface
+{
+    use GettableActivityTrait;
+
+    protected Repository $repository;
+
+    /**
+     * @throws StoreException
+     */
+    public function __construct(
+        ModuleConfiguration $moduleConfiguration,
+        LoggerInterface $logger,
+        string $connectionKey = null,
+        string $connectionType = ModuleConfiguration\ConnectionType::MASTER,
+        Factory $connectionFactory = null,
+        HelpersManager $helpersManager = null,
+        Repository $repository = null
+    ) {
+        parent::__construct(
+            $moduleConfiguration,
+            $logger,
+            $connectionKey,
+            $connectionType,
+            $connectionFactory,
+            $helpersManager,
+            $repository
+        );
+
+        $this->repository = $repository ?? new Repository($this->connection, $this->logger);
+    }
+
+    /**
+     * Build store instance.
+     * @throws StoreException
+     */
+    public static function build(
+        ModuleConfiguration $moduleConfiguration,
+        LoggerInterface $logger,
+        string $connectionKey = null,
+        string $connectionType = ModuleConfiguration\ConnectionType::MASTER
+    ): self {
+        return new self(
+            $moduleConfiguration,
+            $logger,
+            $connectionKey,
+            $connectionType
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function persist(Event $authenticationEvent): void
+    {
+        $hashDecoratedState = new HashDecoratedState($authenticationEvent->getState());
+
+        $spId = $this->resolveSpId($hashDecoratedState);
+        $userId = $this->resolveUserId($hashDecoratedState);
+        $userVersionId = $this->resolveUserVersionId($userId, $hashDecoratedState);
+
+        $this->repository->insertAuthenticationEvent(
+            $spId,
+            $userVersionId,
+            $authenticationEvent->getHappenedAt(),
+            $authenticationEvent->getState()->getClientIpAddress(),
+            $authenticationEvent->getState()->getAuthenticationProtocol()->getDesignation()
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function deleteDataOlderThan(DateTimeImmutable $dateTime): void
+    {
+        // Only delete authentication events.
+        $this->repository->deleteAuthenticationEventsOlderThan($dateTime);
+    }
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000200CreateSpTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000200CreateSpTable.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b295f48036e413b5adf7d84c5b15f1ef2c26f3e
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000200CreateSpTable.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable;
+
+class Version20220801000200CreateSpTable extends CreateSpTable
+{
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000400CreateUserTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000400CreateUserTable.php
new file mode 100644
index 0000000000000000000000000000000000000000..1aa8c3c2394396f021c1d66127b140a725bbe7b2
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000400CreateUserTable.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable;
+
+class Version20220801000400CreateUserTable extends CreateUserTable
+{
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000500CreateUserVersionTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000500CreateUserVersionTable.php
new file mode 100644
index 0000000000000000000000000000000000000000..7cf49583c2bddadc2883bbe4eb69796ba9d88174
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000500CreateUserVersionTable.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
+
+class Version20220801000500CreateUserVersionTable extends Migrations\CreateUserVersionTable
+{
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php
new file mode 100644
index 0000000000000000000000000000000000000000..6ac9a2b2c1f7985eecf0415b3573831d94c4dc3b
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php
@@ -0,0 +1,98 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations;
+
+use Doctrine\DBAL\Schema\Table;
+use Doctrine\DBAL\Types\Types;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use Throwable;
+
+use function sprintf;
+
+class Version20220801000700CreateAuthenticationEventTable extends AbstractMigration
+{
+    protected function getLocalTablePrefix(): string
+    {
+        return 'cds_';
+    }
+
+    /**
+     * @inheritDoc
+     * @throws MigrationException
+     */
+    public function run(): void
+    {
+        $tableName = $this->preparePrefixedTableName('authentication_event');
+
+        try {
+            $table = new Table($tableName);
+
+            $table->addColumn('id', Types::BIGINT)
+                ->setUnsigned(true)
+                ->setAutoincrement(true);
+
+            $table->addColumn('sp_id', Types::BIGINT)
+                ->setUnsigned(true);
+
+            $table->addColumn('user_version_id', Types::BIGINT)
+                ->setUnsigned(true);
+
+            $table->addColumn('happened_at', Types::DATETIMETZ_IMMUTABLE);
+
+            $table->addColumn('client_ip_address', Types::STRING)
+                ->setLength(BaseTableConstantsAlias::COLUMN_IP_ADDRESS_LENGTH)
+                ->setNotnull(false);
+
+            $table->addColumn('authentication_protocol_designation', Types::STRING)
+                ->setLength(BaseTableConstantsAlias::COLUMN_AUTHENTICATION_PROTOCOL_DESIGNATION_LENGTH)
+                ->setNotnull(false);
+
+            $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
+
+            $table->setPrimaryKey(['id']);
+
+            $table->addForeignKeyConstraint(
+                $this->preparePrefixedTableName('sp'),
+                ['sp_id'],
+                ['id']
+            );
+
+            // We are using versioned data for user management.
+            $versionedDataStoreTablePrefix = 'vds_';
+            $table->addForeignKeyConstraint(
+                $this->preparePrefixedTableName('user_version', $versionedDataStoreTablePrefix),
+                ['user_version_id'],
+                ['id']
+            );
+
+            // Old data can be deleted using happened_at column, so add index for it.
+            $table->addIndex(['happened_at']);
+
+            $this->schemaManager->createTable($table);
+        } catch (Throwable $exception) {
+            throw $this->prepareGenericMigrationException(
+                sprintf('Error creating table \'%s.', $tableName),
+                $exception
+            );
+        }
+    }
+
+    /**
+     * @inheritDoc
+     * @throws MigrationException
+     */
+    public function revert(): void
+    {
+        $tableName = $this->preparePrefixedTableName('authentication_event');
+
+        try {
+            $this->schemaManager->dropTable($tableName);
+        } catch (Throwable $exception) {
+            throw $this->prepareGenericMigrationException(sprintf('Could not drop table %s.', $tableName), $exception);
+        }
+    }
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Repository.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Repository.php
new file mode 100644
index 0000000000000000000000000000000000000000..c6a9ca0f9c69ad63ec0bff3af80dfd10a15e7001
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Repository.php
@@ -0,0 +1,204 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+
+use DateTimeImmutable;
+use Doctrine\DBAL\Types\Types;
+use Psr\Log\LoggerInterface;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Traits\Repository\DeletableAuthenticationEventsTrait;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository as BaseRepository;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedBaseTableconstants;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use Throwable;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\EntityTableConstants;
+
+class Repository extends BaseRepository
+{
+    use DeletableAuthenticationEventsTrait;
+
+    protected string $tableNameAuthenticationEvent;
+
+    public function __construct(Connection $connection, LoggerInterface $logger)
+    {
+        parent::__construct($connection, $logger);
+
+        $this->tableNameAuthenticationEvent =
+            $this->preparePrefixedTableName(
+                TableConstants::TABLE_NAME_AUTHENTICATION_EVENT
+            );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function insertAuthenticationEvent(
+        int $spId,
+        int $userVersionId,
+        DateTimeImmutable $happenedAt,
+        string $clientIpAddress = null,
+        string $authenticationProtocolDesignation = null,
+        DateTimeImmutable $createdAt = null
+    ): void {
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+            $createdAt = $createdAt ?? new DateTimeImmutable();
+
+            $queryBuilder->insert($this->tableNameAuthenticationEvent)
+                ->values(
+                    [
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_SP_ID => ':' .
+                            TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_SP_ID,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_USER_VERSION_ID => ':' .
+                            TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_USER_VERSION_ID,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT => ':' .
+                            TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CLIENT_IP_ADDRESS => ':' .
+                            TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CLIENT_IP_ADDRESS,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =>
+                            ':' .
+                            TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CREATED_AT => ':' .
+                            TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CREATED_AT,
+                    ]
+                )
+                ->setParameters(
+                    [
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_SP_ID =>
+                            $spId,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_USER_VERSION_ID =>
+                            $userVersionId,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT => $happenedAt,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CLIENT_IP_ADDRESS => $clientIpAddress,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =>
+                            $authenticationProtocolDesignation,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CREATED_AT => $createdAt,
+                    ],
+                    [
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_SP_ID => Types::BIGINT,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_USER_VERSION_ID => Types::BIGINT,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT =>
+                            Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CLIENT_IP_ADDRESS =>
+                            Types::STRING,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =>
+                            Types::STRING,
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CREATED_AT =>
+                            Types::DATETIMETZ_IMMUTABLE,
+                    ]
+                );
+
+            $queryBuilder->executeStatement();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to insert AuthenticationEvent. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->error($message, compact('spId', 'userVersionId'));
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function getActivity(string $userIdentifierHashSha256, int $maxResults, int $firstResult): array
+    {
+        try {
+            $authenticationEventsQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+            /** @psalm-suppress TooManyArguments */
+            $authenticationEventsQueryBuilder->select(
+                //'cae.happened_at',
+                TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' .
+                TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT,
+                TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' .
+                TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CLIENT_IP_ADDRESS,
+                TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' .
+                TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION,
+                //'cs.metadata AS sp_metadata',
+                BaseTableConstants::TABLE_ALIAS_SP . '.' .
+                BaseTableConstants::TABLE_SP_COLUMN_NAME_METADATA .
+                ' AS ' . EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA,
+                //'cuv.attributes AS user_attributes'
+                VersionedBaseTableconstants::TABLE_ALIAS_USER_VERSION . '.' .
+                VersionedBaseTableconstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' .
+                EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES
+            )->from(
+                //'cds_authentication_event', 'cae'
+                $this->tableNameAuthenticationEvent,
+                //'cae'
+                TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT
+            )
+                ->leftJoin(
+                    //'cae',
+                    TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT,
+                    //'cds_sp',
+                    $this->tableNameSp,
+                    //'vs',
+                    BaseTableConstants::TABLE_ALIAS_SP,
+                    //'cae.sp_id = cs.id'
+                    TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' .
+                    TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_SP_ID . ' = ' .
+                    BaseTableConstants::TABLE_ALIAS_SP . '.' .
+                    BaseTableConstants::TABLE_SP_COLUMN_NAME_ID
+                )
+                ->leftJoin(
+                    //'cae',
+                    TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT,
+                    //'vds_user_version',
+                    $this->tableNameUserVersion,
+                    //'vuv',
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER_VERSION,
+                    //'cae.user_version_id = vuv.id'
+                    TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' .
+                    TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_USER_VERSION_ID . ' = ' .
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER_VERSION . '.' .
+                    VersionedBaseTableconstants::TABLE_USER_VERSION_COLUMN_NAME_ID
+                )
+                ->leftJoin(
+                    //'vuv',
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER_VERSION,
+                    //'vds_user',
+                    $this->tableNameUser,
+                    //'vu',
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER,
+                    //'vuv.user_id = vu.id'
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER_VERSION . '.' .
+                    VersionedBaseTableconstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID . ' = ' .
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER . '.' .
+                    VersionedBaseTableconstants::TABLE_USER_COLUMN_NAME_ID
+                )
+                ->where(
+                    //'vu.identifier_hash_sha256 = ' .
+                    VersionedBaseTableconstants::TABLE_ALIAS_USER . '.' .
+                    VersionedBaseTableconstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' .
+                    $authenticationEventsQueryBuilder->createNamedParameter($userIdentifierHashSha256)
+                )
+                ->orderBy(
+                    //'cae.id',
+                    TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' .
+                    TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_ID,
+                    'DESC'
+                )
+            ->setMaxResults($maxResults)
+            ->setFirstResult($firstResult);
+
+            return $authenticationEventsQueryBuilder->executeQuery()->fetchAllAssociative();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to get connected organizations. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->error($message, compact('userIdentifierHashSha256'));
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/TableConstants.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/TableConstants.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2dde0f8c0db501a641bbf6bb4bb56f283f06b2c
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/TableConstants.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants
+    as BaseTableConstants;
+
+class TableConstants
+{
+    // Table 'authentication_event'.
+    public const TABLE_NAME_AUTHENTICATION_EVENT = 'authentication_event';
+    public const TABLE_ALIAS_AUTHENTICATION_EVENT = BaseTableConstants::TABLE_PREFIX . 'ae';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_ID = 'id';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_SP_ID = 'sp_id';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_USER_VERSION_ID = 'user_version_id';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT = 'happened_at';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CLIENT_IP_ADDRESS = 'client_ip_address';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =
+        'authentication_protocol_designation';
+    public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CREATED_AT = 'created_at';
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/EntityTableConstants.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/EntityTableConstants.php
new file mode 100644
index 0000000000000000000000000000000000000000..be31816908062e17a977a4a076a7362ea733f28c
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/EntityTableConstants.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal;
+
+class EntityTableConstants
+{
+    // Entity 'Activity' related.
+    public const ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA = 'sp_metadata';
+    public const ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES = 'user_attributes';
+    public const ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT = 'happened_at';
+    public const ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS = 'client_ip_address';
+    public const ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =
+        'authentication_protocol_designation';
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivity.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/RawActivity.php
old mode 100644
new mode 100755
similarity index 70%
rename from src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivity.php
rename to src/Data/Stores/Accounting/Activity/DoctrineDbal/RawActivity.php
index ed7b42cba9e6d162f0f6b194f92508eb186fdc98..2261afa604d211fe05a11a68821c2fe09b111989
--- a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivity.php
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/RawActivity.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store;
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal;
 
 use DateTimeImmutable;
 use Doctrine\DBAL\Platforms\AbstractPlatform;
@@ -22,25 +22,25 @@ class RawActivity extends AbstractRawEntity
         parent::__construct($rawRow, $abstractPlatform);
 
         $this->serviceProviderMetadata = $this->resolveServiceProviderMetadata(
-            (string)$rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA]
+            (string)$rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA]
         );
 
         $this->userAttributes = $this->resolveUserAttributes(
-            (string)$rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES]
+            (string)$rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES]
         );
 
         $this->happenedAt = $this->resolveDateTimeImmutable(
-            $rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]
+            $rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]
         );
 
-        $this->clientIpAddress = empty($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS]) ?
+        $this->clientIpAddress = empty($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS]) ?
             null :
-            (string)$rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS];
+            (string)$rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS];
 
         $this->authenticationProtocolDesignation =
-            empty($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION]) ?
+            empty($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION]) ?
             null :
-            (string)$rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION];
+            (string)$rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION];
     }
 
     /**
@@ -89,9 +89,9 @@ class RawActivity extends AbstractRawEntity
     protected function validate(array $rawRow): void
     {
         $columnsToCheck = [
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA,
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES,
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT,
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA,
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES,
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT,
         ];
 
         foreach ($columnsToCheck as $column) {
@@ -101,26 +101,26 @@ class RawActivity extends AbstractRawEntity
         }
 
         /** @noinspection DuplicatedCode */
-        if (! is_string($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA
+                EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA
             );
             throw new UnexpectedValueException($message);
         }
 
-        if (! is_string($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES
+                EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES
             );
             throw new UnexpectedValueException($message);
         }
 
-        if (! is_string($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT
+                EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT
             );
             throw new UnexpectedValueException($message);
         }
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Traits/Repository/DeletableAuthenticationEventsTrait.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Traits/Repository/DeletableAuthenticationEventsTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..538a96c44080b9da465a814a95d0bc4875add91a
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Traits/Repository/DeletableAuthenticationEventsTrait.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Traits\Repository;
+
+use DateTimeImmutable;
+use Doctrine\DBAL\Types\Types;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use Throwable;
+
+trait DeletableAuthenticationEventsTrait
+{
+    /**
+     * @throws StoreException
+     */
+    public function deleteAuthenticationEventsOlderThan(DateTimeImmutable $dateTime): void
+    {
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+            $queryBuilder->delete($this->tableNameAuthenticationEvent)
+                ->where(
+                    $queryBuilder->expr()->lt(
+                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT,
+                        $queryBuilder->createNamedParameter($dateTime, Types::DATETIME_IMMUTABLE)
+                    )
+                )->executeStatement();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to delete old authentication events. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->error($message);
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Traits/Store/GettableActivityTrait.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Traits/Store/GettableActivityTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..a2fe28293bc1c6c38d2a8526fa1f906c4a71f585
--- /dev/null
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Traits/Store/GettableActivityTrait.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Traits\Store;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\RawActivity;
+use SimpleSAML\Module\accounting\Entities\Activity;
+use SimpleSAML\Module\accounting\Entities\User;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use Throwable;
+
+trait GettableActivityTrait
+{
+    /**
+     * @throws StoreException
+     */
+    public function getActivity(string $userIdentifier, int $maxResults, int $firstResult): Activity\Bag
+    {
+        $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier);
+
+        $results =  $this->repository->getActivity($userIdentifierHashSha256, $maxResults, $firstResult);
+
+        $activityBag = new Activity\Bag();
+
+        if (empty($results)) {
+            return $activityBag;
+        }
+
+        try {
+            /** @var array $result */
+            foreach ($results as $result) {
+                $rawActivity = new RawActivity($result, $this->connection->dbal()->getDatabasePlatform());
+                $serviceProvider = $this->helpersManager
+                    ->getProviderResolver()
+                    ->forServiceFromMetadataArray($rawActivity->getServiceProviderMetadata());
+                $user = new User($rawActivity->getUserAttributes());
+
+                $activityBag->add(
+                    new Activity(
+                        $serviceProvider,
+                        $user,
+                        $rawActivity->getHappenedAt(),
+                        $rawActivity->getClientIpAddress(),
+                        $rawActivity->getAuthenticationProtocolDesignation()
+                    )
+                );
+            }
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error populating activity bag. Error was: %s',
+                $exception->getMessage()
+            );
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+
+        return $activityBag;
+    }
+}
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php
old mode 100644
new mode 100755
index 149b8bca2b4f74c4707bdbdbeb1afd7e7864e7a0..c25864c1d72d72919d78116da19e5aa0a484b7d1
--- a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php
@@ -6,22 +6,21 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineD
 
 use DateTimeImmutable;
 use Psr\Log\LoggerInterface;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\RawActivity;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Traits\Store\GettableActivityTrait;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Repository;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store as BaseStore;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
 use SimpleSAML\Module\accounting\Data\Stores\Interfaces\ActivityInterface;
-use SimpleSAML\Module\accounting\Entities\Activity;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event;
-use SimpleSAML\Module\accounting\Entities\User;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
-use Throwable;
 
 class Store extends BaseStore implements ActivityInterface
 {
+    use GettableActivityTrait;
+
     protected Repository $repository;
 
     /**
@@ -90,51 +89,6 @@ class Store extends BaseStore implements ActivityInterface
         );
     }
 
-    /**
-     * @throws StoreException
-     */
-    public function getActivity(string $userIdentifier, int $maxResults, int $firstResult): Activity\Bag
-    {
-        $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier);
-
-        $results =  $this->repository->getActivity($userIdentifierHashSha256, $maxResults, $firstResult);
-
-        $activityBag = new Activity\Bag();
-
-        if (empty($results)) {
-            return $activityBag;
-        }
-
-        try {
-            /** @var array $result */
-            foreach ($results as $result) {
-                $rawActivity = new RawActivity($result, $this->connection->dbal()->getDatabasePlatform());
-                $serviceProvider = $this->helpersManager
-                    ->getProviderResolver()
-                    ->forServiceFromMetadataArray($rawActivity->getServiceProviderMetadata());
-                $user = new User($rawActivity->getUserAttributes());
-
-                $activityBag->add(
-                    new Activity(
-                        $serviceProvider,
-                        $user,
-                        $rawActivity->getHappenedAt(),
-                        $rawActivity->getClientIpAddress(),
-                        $rawActivity->getAuthenticationProtocolDesignation()
-                    )
-                );
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error populating activity bag. Error was: %s',
-                $exception->getMessage()
-            );
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        return $activityBag;
-    }
-
     /**
      * @throws StoreException
      */
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php
old mode 100644
new mode 100755
index d477b56c40326e5cc546c33aaba8b098c4a7b839..42b14ba1ca4225b9104f4b53e10dc0579f8a3b6b
--- a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineD
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -41,11 +41,11 @@ class Version20220801000700CreateAuthenticationEventTable extends AbstractMigrat
             $table->addColumn('happened_at', Types::DATETIMETZ_IMMUTABLE);
 
             $table->addColumn('client_ip_address', Types::STRING)
-                ->setLength(TableConstants::COLUMN_IP_ADDRESS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_IP_ADDRESS_LENGTH)
                 ->setNotnull(false);
 
             $table->addColumn('authentication_protocol_designation', Types::STRING)
-                ->setLength(TableConstants::COLUMN_AUTHENTICATION_PROTOCOL_DESIGNATION_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_AUTHENTICATION_PROTOCOL_DESIGNATION_LENGTH)
                 ->setNotnull(false);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php
old mode 100644
new mode 100755
index 7071f0095aae17cde4e04b0d3c709c56d64f1da9..c47e8a62e6bd9b3512c28b12cdd97563c73f2f09
--- a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php
@@ -7,15 +7,20 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineD
 use DateTimeImmutable;
 use Doctrine\DBAL\Types\Types;
 use Psr\Log\LoggerInterface;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Traits\Repository\DeletableAuthenticationEventsTrait;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository as BaseRepository;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants
     as BaseTableConstants;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use Throwable;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\EntityTableConstants;
 
 class Repository extends BaseRepository
 {
+    use DeletableAuthenticationEventsTrait;
+
     protected string $tableNameAuthenticationEvent;
 
     public function __construct(Connection $connection, LoggerInterface $logger)
@@ -114,11 +119,11 @@ class Repository extends BaseRepository
                 //'vsv.metadata AS sp_metadata',
                 BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' .
                 BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA .
-                ' AS ' . TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA,
+                ' AS ' . EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA,
                 //'vuv.attributes AS user_attributes'
                 BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
                 BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' .
-                TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES
+                EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES
             )->from(
                 //'vds_authentication_event', 'vae'
                 $this->tableNameAuthenticationEvent,
@@ -215,29 +220,4 @@ class Repository extends BaseRepository
             throw new StoreException($message, (int)$exception->getCode(), $exception);
         }
     }
-
-    /**
-     * @throws StoreException
-     */
-    public function deleteAuthenticationEventsOlderThan(DateTimeImmutable $dateTime): void
-    {
-        try {
-            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-            $queryBuilder->delete($this->tableNameAuthenticationEvent)
-                ->where(
-                    $queryBuilder->expr()->lt(
-                        TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT,
-                        $queryBuilder->createNamedParameter($dateTime, Types::DATETIME_IMMUTABLE)
-                    )
-                )->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to delete old authentication events. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->error($message);
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
 }
diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/TableConstants.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/TableConstants.php
old mode 100644
new mode 100755
index df5b36c75d946c921b31f98f26f0358903be457b..dcfdaa15ff1f1fcda645ba63bc014394b8fdafeb
--- a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/TableConstants.php
+++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/TableConstants.php
@@ -19,12 +19,4 @@ class TableConstants
     public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =
         'authentication_protocol_designation';
     public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_CREATED_AT = 'created_at';
-
-    // Entity 'Activity' related.
-    public const ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA = 'sp_metadata';
-    public const ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES = 'user_attributes';
-    public const ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT = 'happened_at';
-    public const ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS = 'client_ip_address';
-    public const ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =
-        'authentication_protocol_designation';
 }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store.php
old mode 100644
new mode 100755
index 0090783a3631b033a6cf8aa1589180587cdd7066..940654664c8483ae946c84247c18742ec610b246
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store.php
@@ -7,18 +7,20 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store as VersionedStore;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
 use SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
 use SimpleSAML\Module\accounting\Data\Stores\Interfaces\StoreInterface;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
-use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
 use Throwable;
 
 class Store extends AbstractStore implements StoreInterface
 {
+    use VersionedStore\UserVersionResolvingTrait;
+
     protected HelpersManager $helpersManager;
     private Repository $repository;
 
@@ -125,118 +127,4 @@ class Store extends AbstractStore implements StoreInterface
             throw new StoreException($message, (int)$exception->getCode(), $exception);
         }
     }
-
-    /**
-     * @throws StoreException
-     */
-    public function resolveUserId(HashDecoratedState $hashDecoratedState): int
-    {
-        $userIdentifierAttributeName = $this->moduleConfiguration->getUserIdAttributeName();
-
-        $userIdentifierValue = $hashDecoratedState->getState()->getFirstAttributeValue($userIdentifierAttributeName);
-        if ($userIdentifierValue === null) {
-            $message = sprintf('Attributes do not contain user ID attribute %s.', $userIdentifierAttributeName);
-            throw new UnexpectedValueException($message);
-        }
-
-        $userIdentifierValueHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifierValue);
-
-        // Check if it already exists.
-        try {
-            $result = $this->repository->getUser($userIdentifierValueHashSha256);
-            $userId = $result->fetchOne();
-
-            if ($userId !== false) {
-                return (int)$userId;
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        // Create new
-        try {
-            $this->repository->insertUser($userIdentifierValue, $userIdentifierValueHashSha256);
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error inserting new user, however, continuing in case of race condition. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->warning($message);
-        }
-
-        // Try again, this time it should exist...
-        try {
-            $result = $this->repository->getUser($userIdentifierValueHashSha256);
-            $userIdNew = $result->fetchOne();
-
-            if ($userIdNew !== false) {
-                return (int)$userIdNew;
-            }
-
-            $message = sprintf(
-                'Error fetching user even after insertion for identifier value hash SHA256 %s.',
-                $userIdentifierValueHashSha256
-            );
-            throw new StoreException($message);
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function resolveUserVersionId(int $userId, HashDecoratedState $hashDecoratedState): int
-    {
-        $attributeArrayHashSha256 = $hashDecoratedState->getAttributesArrayHashSha256();
-
-        // Check if it already exists.
-        try {
-            $result = $this->repository->getUserVersion($userId, $attributeArrayHashSha256);
-            $userVersionId = $result->fetchOne();
-
-            if ($userVersionId !== false) {
-                return (int)$userVersionId;
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user version ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        // Create new
-        try {
-            $this->repository->insertUserVersion(
-                $userId,
-                serialize($hashDecoratedState->getState()->getAttributes()),
-                $attributeArrayHashSha256
-            );
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error inserting new user version, however, continuing in case of race condition. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->warning($message);
-        }
-
-        // Try again, this time it should exist...
-        try {
-            $result = $this->repository->getUserVersion($userId, $attributeArrayHashSha256);
-            $userVersionIdNew = $result->fetchOne();
-
-            if ($userVersionIdNew !== false) {
-                return (int)$userVersionIdNew;
-            }
-
-            $message = sprintf(
-                'Error fetching user version even after insertion for user ID %s.',
-                $userId
-            );
-            throw new StoreException($message);
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user version ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
 }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTable.php
old mode 100644
new mode 100755
index 840cd8fbe8c9b98870bb6b36ff8f035ea6a4f314..1d3769a0fd6b63752ee015b588a93dab787653e0
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -41,16 +41,16 @@ class CreateIdpTable extends AbstractMigration
                 ->setAutoincrement(true);
 
             $table->addColumn('entity_id', Types::STRING)
-                ->setLength(TableConstants::COLUMN_ENTITY_ID_LENGTH);
+                ->setLength(BaseTableConstantsAlias::COLUMN_ENTITY_ID_LENGTH);
 
             $table->addColumn('entity_id_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('metadata', Types::TEXT);
 
             $table->addColumn('metadata_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTable.php
old mode 100644
new mode 100755
index 5551faf0e6b12907fbb4b1d8d0e02e6b53174a30..7bc5142fce81a913b3882aa8c06c0bb08f8a741c
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -41,16 +41,16 @@ class CreateSpTable extends AbstractMigration
                 ->setAutoincrement(true);
 
             $table->addColumn('entity_id', Types::STRING)
-                ->setLength(TableConstants::COLUMN_ENTITY_ID_LENGTH);
+                ->setLength(BaseTableConstantsAlias::COLUMN_ENTITY_ID_LENGTH);
 
             $table->addColumn('entity_id_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('metadata', Types::TEXT);
 
             $table->addColumn('metadata_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTable.php
old mode 100644
new mode 100755
index 090357d69e9484499309c44a5ca737c4f62b8496..7dee55c3c0e0b730bc171310d81a401340b02504
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTable.php
@@ -4,75 +4,11 @@ declare(strict_types=1);
 
 namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
 
-use Doctrine\DBAL\Schema\Table;
-use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants;
-use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
-use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
-use Throwable;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations;
 
-use function sprintf;
-
-class CreateUserTable extends AbstractMigration
+/**
+ * We use versioned data to manage users, so we reuse versioned user table definitions.
+ */
+class CreateUserTable extends Migrations\CreateUserTable
 {
-    protected function getLocalTablePrefix(): string
-    {
-        return 'cds_';
-    }
-
-    /**
-     * @inheritDoc
-     * @throws MigrationException
-     */
-    public function run(): void
-    {
-        $tableName = $this->preparePrefixedTableName('user');
-
-        try {
-            if ($this->schemaManager->tablesExist($tableName)) {
-                return;
-            }
-
-            $table = new Table($tableName);
-
-            $table->addColumn('id', Types::BIGINT)
-                ->setUnsigned(true)
-                ->setAutoincrement(true);
-
-            $table->addColumn('identifier', Types::TEXT)
-                ->setLength(65535);
-
-            $table->addColumn('identifier_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
-                ->setFixed(true);
-
-            $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
-
-            $table->setPrimaryKey(['id']);
-
-            $table->addUniqueConstraint(['identifier_hash_sha256']);
-
-            $this->schemaManager->createTable($table);
-        } catch (Throwable $exception) {
-            throw $this->prepareGenericMigrationException(
-                sprintf('Error creating table \'%s.', $tableName),
-                $exception
-            );
-        }
-    }
-
-    /**
-     * @inheritDoc
-     * @throws MigrationException
-     */
-    public function revert(): void
-    {
-        $tableName = $this->preparePrefixedTableName('user');
-
-        try {
-            $this->schemaManager->dropTable($tableName);
-        } catch (Throwable $exception) {
-            throw $this->prepareGenericMigrationException(sprintf('Could not drop table %s.', $tableName), $exception);
-        }
-    }
 }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTable.php
old mode 100644
new mode 100755
index 11f7926b19a3dadbfcc8fcd74917f90fb8304311..e0975592614fe4ad870e0b10c3fe32e937a899ab
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTable.php
@@ -6,10 +6,9 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations;
 
+/**
+ * We use versioned data to manage users, so we reuse versioned user table definitions.
+ */
 class CreateUserVersionTable extends Migrations\CreateUserVersionTable
 {
-    protected function getLocalTablePrefix(): string
-    {
-        return 'cds_';
-    }
 }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Repository.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Repository.php
old mode 100644
new mode 100755
index 7ec179ccefcacbee5ca94ad50a9c9afa03dd3ecf..7a83da64b2b7f06c24680831f457f37ba94336ec
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Repository.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Repository.php
@@ -5,16 +5,22 @@ declare(strict_types=1);
 namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store;
 
 use DateTimeImmutable;
-use Doctrine\DBAL\ParameterType;
 use Doctrine\DBAL\Result;
 use Doctrine\DBAL\Types\Types;
 use Psr\Log\LoggerInterface;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository as VersionedRepository;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use Throwable;
 
 class Repository
 {
+    // We user versioned user data, so let's reuse versioned user tables.
+    use VersionedRepository\UserVersionManagementTrait;
+
     protected Connection $connection;
     protected LoggerInterface $logger;
     protected string $tableNameIdp;
@@ -29,13 +35,23 @@ class Repository
 
         $this->tableNameIdp = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_IDP);
         $this->tableNameSp = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_SP);
-        $this->tableNameUser = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_USER);
-        $this->tableNameUserVersion = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_USER_VERSION);
+
+        // For user management we use versioned data, so we will reuse tables from versioned data store.
+        $versionedDataStoreTablePrefix = VersionedTableConstantsAlias::TABLE_PREFIX;
+        $this->tableNameUser = $this->preparePrefixedTableName(
+            VersionedTableConstantsAlias::TABLE_NAME_USER,
+            $versionedDataStoreTablePrefix
+        );
+        $this->tableNameUserVersion = $this->preparePrefixedTableName(
+            VersionedTableConstantsAlias::TABLE_NAME_USER_VERSION,
+            $versionedDataStoreTablePrefix
+        );
     }
 
-    protected function preparePrefixedTableName(string $tableName): string
+    protected function preparePrefixedTableName(string $tableName, string $tablePrefixOverride = null): string
     {
-        return $this->connection->preparePrefixedTableName(TableConstants::TABLE_PREFIX . $tableName);
+        $tablePrefix = $tablePrefixOverride ?? TableConstants::TABLE_PREFIX;
+        return $this->connection->preparePrefixedTableName($tablePrefix . $tableName);
     }
 
     /**
@@ -178,48 +194,48 @@ class Repository
         string $metadataHashSha256,
         DateTimeImmutable $updatedAt = null
     ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $updatedAt = $updatedAt ?? new DateTimeImmutable();
+            $updatedAt = $updatedAt ?? new DateTimeImmutable();
 
-        $queryBuilder->update($this->tableNameIdp)
-            ->set(
-                TableConstants::TABLE_IDP_COLUMN_NAME_METADATA,
-                ':' . TableConstants::TABLE_IDP_COLUMN_NAME_METADATA
-            )
-            ->set(
-                TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256,
-                ':' . TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256
-            )
-            ->set(
-                TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT,
-                ':' . TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT
-            )
-            ->setParameter(
-                TableConstants::TABLE_IDP_COLUMN_NAME_METADATA,
-                $metadata,
-                Types::STRING
-            )
-            ->setParameter(
-                TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256,
-                $metadataHashSha256,
-                Types::STRING
-            )
-            ->setParameter(
-                TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT,
-                $updatedAt,
-                Types::DATETIMETZ_IMMUTABLE
-            )
-            ->where(
-                $queryBuilder->expr()->and(
-                    $queryBuilder->expr()->eq(
-                        TableConstants::TABLE_IDP_COLUMN_NAME_ID,
-                        $queryBuilder->createNamedParameter($idpId, Types::INTEGER)
-                    )
+            $queryBuilder->update($this->tableNameIdp)
+                ->set(
+                    TableConstants::TABLE_IDP_COLUMN_NAME_METADATA,
+                    ':' . TableConstants::TABLE_IDP_COLUMN_NAME_METADATA
                 )
-            );
+                ->set(
+                    TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256,
+                    ':' . TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256
+                )
+                ->set(
+                    TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT,
+                    ':' . TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT
+                )
+                ->setParameter(
+                    TableConstants::TABLE_IDP_COLUMN_NAME_METADATA,
+                    $metadata,
+                    Types::STRING
+                )
+                ->setParameter(
+                    TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256,
+                    $metadataHashSha256,
+                    Types::STRING
+                )
+                ->setParameter(
+                    TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT,
+                    $updatedAt,
+                    Types::DATETIMETZ_IMMUTABLE
+                )
+                ->where(
+                    $queryBuilder->expr()->and(
+                        $queryBuilder->expr()->eq(
+                            TableConstants::TABLE_IDP_COLUMN_NAME_ID,
+                            $queryBuilder->createNamedParameter($idpId, Types::INTEGER)
+                        )
+                    )
+                );
 
-        try {
             $queryBuilder->executeStatement();
         } catch (Throwable $exception) {
             $message = sprintf('Error executing query to update IdP. Error was: %s.', $exception->getMessage());
@@ -296,234 +312,52 @@ class Repository
         string $metadataHashSha256,
         DateTimeImmutable $updatedAt = null
     ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-        $updatedAt = $updatedAt ?? new DateTimeImmutable();
-
-        $queryBuilder->update($this->tableNameSp)
-            ->set(
-                TableConstants::TABLE_SP_COLUMN_NAME_METADATA,
-                ':' . TableConstants::TABLE_SP_COLUMN_NAME_METADATA
-            )
-            ->set(
-                TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256,
-                ':' . TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256
-            )
-            ->set(
-                TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT,
-                ':' . TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT
-            )
-            ->setParameter(
-                TableConstants::TABLE_SP_COLUMN_NAME_METADATA,
-                $metadata,
-                Types::STRING
-            )
-            ->setParameter(
-                TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256,
-                $metadataHashSha256,
-                Types::STRING
-            )
-            ->setParameter(
-                TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT,
-                $updatedAt,
-                Types::DATETIMETZ_IMMUTABLE
-            )
-            ->where(
-                $queryBuilder->expr()->and(
-                    $queryBuilder->expr()->eq(
-                        TableConstants::TABLE_SP_COLUMN_NAME_ID,
-                        $queryBuilder->createNamedParameter($spId, Types::INTEGER)
-                    )
-                )
-            );
-
-        try {
-            $queryBuilder->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf('Error executing query to update SP. Error was: %s.', $exception->getMessage());
-            $this->logger->error($message, compact('spId', 'metadata', 'metadataHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function getUser(string $identifierHashSha256): Result
-    {
         try {
             $queryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-            /** @psalm-suppress TooManyArguments */
-            $queryBuilder->select(
-                TableConstants::TABLE_USER_COLUMN_NAME_ID,
-                TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER,
-                TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
-                TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT,
-            )
-                ->from($this->tableNameUser)
-                ->where(
-                    $queryBuilder->expr()->and(
-                        $queryBuilder->expr()->eq(
-                            TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
-                            $queryBuilder->createNamedParameter($identifierHashSha256)
-                        )
-                    )
-                )->setMaxResults(1);
-
-            return $queryBuilder->executeQuery();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to get user. Error was: %s.',
-                $identifierHashSha256,
-                $exception->getMessage()
-            );
-            $this->logger->error($message, compact('identifierHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function insertUser(
-        string $identifier,
-        string $identifierHashSha256,
-        DateTimeImmutable $createdAt = null
-    ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+            $updatedAt = $updatedAt ?? new DateTimeImmutable();
 
-        $createdAt = $createdAt ?? new DateTimeImmutable();
-
-        $queryBuilder->insert($this->tableNameUser)
-            ->values(
-                [
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => ':' .
-                        TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER,
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => ':' .
-                        TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
-                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => $identifier,
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => $identifierHashSha256,
-                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => $createdAt,
-                ],
-                [
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => Types::TEXT,
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => Types::STRING,
-                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE
-                ]
-            );
-
-        try {
-            $queryBuilder->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf('Error executing query to insert user. Error was: %s.', $exception->getMessage());
-            $this->logger->error(
-                $message,
-                compact('identifier', 'identifierHashSha256')
-            );
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-
-    /**
-     * @throws StoreException
-     */
-    public function getUserVersion(int $userId, string $attributesHashSha256): Result
-    {
-        try {
-            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-            /** @psalm-suppress TooManyArguments */
-            $queryBuilder->select(
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT,
-            )
-                ->from($this->tableNameUserVersion)
+            $queryBuilder->update($this->tableNameSp)
+                ->set(
+                    TableConstants::TABLE_SP_COLUMN_NAME_METADATA,
+                    ':' . TableConstants::TABLE_SP_COLUMN_NAME_METADATA
+                )
+                ->set(
+                    TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256,
+                    ':' . TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256
+                )
+                ->set(
+                    TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT,
+                    ':' . TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT
+                )
+                ->setParameter(
+                    TableConstants::TABLE_SP_COLUMN_NAME_METADATA,
+                    $metadata,
+                    Types::STRING
+                )
+                ->setParameter(
+                    TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256,
+                    $metadataHashSha256,
+                    Types::STRING
+                )
+                ->setParameter(
+                    TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT,
+                    $updatedAt,
+                    Types::DATETIMETZ_IMMUTABLE
+                )
                 ->where(
                     $queryBuilder->expr()->and(
                         $queryBuilder->expr()->eq(
-                            TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
-                            $queryBuilder->createNamedParameter($userId, ParameterType::INTEGER)
-                        ),
-                        $queryBuilder->expr()->eq(
-                            TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
-                            $queryBuilder->createNamedParameter($attributesHashSha256)
+                            TableConstants::TABLE_SP_COLUMN_NAME_ID,
+                            $queryBuilder->createNamedParameter($spId, Types::INTEGER)
                         )
                     )
-                )->setMaxResults(1);
-
-            return $queryBuilder->executeQuery();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to get user version for user ID %s and attribute array hash %s. Error was: %s.',
-                $userId,
-                $attributesHashSha256,
-                $exception->getMessage()
-            );
-            $this->logger->error($message, compact('userId', 'attributesHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function insertUserVersion(
-        int $userId,
-        string $attributes,
-        string $attributesHashSha256,
-        DateTimeImmutable $createdAt = null
-    ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-        $createdAt = $createdAt ?? new DateTimeImmutable();
-
-        $queryBuilder->insert($this->tableNameUserVersion)
-            ->values(
-                [
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => $userId,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => $attributes,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => $attributesHashSha256,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => $createdAt,
-                ],
-                [
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => Types::BIGINT,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => Types::TEXT,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => Types::STRING,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
-                ]
-            );
+                );
 
-        try {
             $queryBuilder->executeStatement();
         } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to insert user version. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->error($message, compact('userId', 'attributesHashSha256'));
+            $message = sprintf('Error executing query to update SP. Error was: %s.', $exception->getMessage());
+            $this->logger->error($message, compact('spId', 'metadata', 'metadataHashSha256'));
             throw new StoreException($message, (int)$exception->getCode(), $exception);
         }
     }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/TableConstants.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/TableConstants.php
old mode 100644
new mode 100755
index 40b45a00b61dc6f2ec2ea4d6119d7e01c6544d8e..34572f5854c6d8f3b63bc9640dc2cb792333bad0
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/TableConstants.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/TableConstants.php
@@ -31,22 +31,4 @@ class TableConstants extends BaseTableConstants
     public const TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256 = 'metadata_hash_sha256';
     public const TABLE_SP_COLUMN_NAME_CREATED_AT = 'created_at';
     public const TABLE_SP_COLUMN_NAME_UPDATED_AT = 'updated_at';
-
-    // Table 'user'
-    public const TABLE_NAME_USER = 'user';
-    public const TABLE_ALIAS_USER = self::TABLE_PREFIX . 'u';
-    public const TABLE_USER_COLUMN_NAME_ID = 'id'; // int
-    public const TABLE_USER_COLUMN_NAME_IDENTIFIER = 'identifier'; // text, varies... (can be ePTID, which is long XML).
-    public const TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 = 'identifier_hash_sha256';
-    public const TABLE_USER_COLUMN_NAME_CREATED_AT = 'created_at';
-
-    // Table 'user_version' (versioned attributes)
-    public const TABLE_NAME_USER_VERSION = 'user_version';
-    public const TABLE_ALIAS_USER_VERSION = self::TABLE_PREFIX . 'uv';
-    public const TABLE_ALIAS_USER_VERSION_2 = self::TABLE_ALIAS_USER_VERSION . '_2';
-    public const TABLE_USER_VERSION_COLUMN_NAME_ID = 'id'; // int ID
-    public const TABLE_USER_VERSION_COLUMN_NAME_USER_ID = 'user_id'; // FK
-    public const TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES = 'attributes'; // Serialized attributes version
-    public const TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 = 'attributes_hash_sha256';
-    public const TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT = 'created_at';
 }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php
old mode 100644
new mode 100755
index 6c3214b6565d5629f0d4a7d272159b63a325688e..411da5f7bf00bc54a1a0c200e5d041c888b905a5
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php
@@ -11,13 +11,14 @@ use SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
 use SimpleSAML\Module\accounting\Data\Stores\Interfaces\StoreInterface;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
-use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
 use Throwable;
 
 class Store extends AbstractStore implements StoreInterface
 {
+    use Store\UserVersionResolvingTrait;
+
     protected HelpersManager $helpersManager;
     private Repository $repository;
 
@@ -275,120 +276,6 @@ class Store extends AbstractStore implements StoreInterface
         }
     }
 
-    /**
-     * @throws StoreException
-     */
-    public function resolveUserId(HashDecoratedState $hashDecoratedState): int
-    {
-        $userIdentifierAttributeName = $this->moduleConfiguration->getUserIdAttributeName();
-
-        $userIdentifierValue = $hashDecoratedState->getState()->getFirstAttributeValue($userIdentifierAttributeName);
-        if ($userIdentifierValue === null) {
-            $message = sprintf('Attributes do not contain user ID attribute %s.', $userIdentifierAttributeName);
-            throw new UnexpectedValueException($message);
-        }
-
-        $userIdentifierValueHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifierValue);
-
-        // Check if it already exists.
-        try {
-            $result = $this->repository->getUser($userIdentifierValueHashSha256);
-            $userId = $result->fetchOne();
-
-            if ($userId !== false) {
-                return (int)$userId;
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        // Create new
-        try {
-            $this->repository->insertUser($userIdentifierValue, $userIdentifierValueHashSha256);
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error inserting new user, however, continuing in case of race condition. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->warning($message);
-        }
-
-        // Try again, this time it should exist...
-        try {
-            $result = $this->repository->getUser($userIdentifierValueHashSha256);
-            $userIdNew = $result->fetchOne();
-
-            if ($userIdNew !== false) {
-                return (int)$userIdNew;
-            }
-
-            $message = sprintf(
-                'Error fetching user even after insertion for identifier value hash SHA256 %s.',
-                $userIdentifierValueHashSha256
-            );
-            throw new StoreException($message);
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function resolveUserVersionId(int $userId, HashDecoratedState $hashDecoratedState): int
-    {
-        $attributeArrayHashSha256 = $hashDecoratedState->getAttributesArrayHashSha256();
-
-        // Check if it already exists.
-        try {
-            $result = $this->repository->getUserVersion($userId, $attributeArrayHashSha256);
-            $userVersionId = $result->fetchOne();
-
-            if ($userVersionId !== false) {
-                return (int)$userVersionId;
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user version ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        // Create new
-        try {
-            $this->repository->insertUserVersion(
-                $userId,
-                serialize($hashDecoratedState->getState()->getAttributes()),
-                $attributeArrayHashSha256
-            );
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error inserting new user version, however, continuing in case of race condition. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->warning($message);
-        }
-
-        // Try again, this time it should exist...
-        try {
-            $result = $this->repository->getUserVersion($userId, $attributeArrayHashSha256);
-            $userVersionIdNew = $result->fetchOne();
-
-            if ($userVersionIdNew !== false) {
-                return (int)$userVersionIdNew;
-            }
-
-            $message = sprintf(
-                'Error fetching user version even after insertion for user ID %s.',
-                $userId
-            );
-            throw new StoreException($message);
-        } catch (Throwable $exception) {
-            $message = sprintf('Error resolving user version ID. Error was: %s.', $exception->getMessage());
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
     /**
      * @throws StoreException
      */
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php
old mode 100644
new mode 100755
index 7a37729835d79bc1dc1269406081ffd19215cf91..852ae2d3d6e07e10fd9cdc990314b299a43d4286
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -41,10 +41,10 @@ class CreateIdpTable extends AbstractMigration
                 ->setAutoincrement(true);
 
             $table->addColumn('entity_id', Types::STRING)
-                ->setLength(TableConstants::COLUMN_ENTITY_ID_LENGTH);
+                ->setLength(BaseTableConstantsAlias::COLUMN_ENTITY_ID_LENGTH);
 
             $table->addColumn('entity_id_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php
old mode 100644
new mode 100755
index fdbd7c5240b20734e02756c8940db0ab2717d70d..261ab9091ac6c9fc1da18de9799530b0c058666c
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php
@@ -6,11 +6,13 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
 
+use function sprintf;
+
 class CreateIdpVersionTable extends AbstractMigration
 {
     protected function getLocalTablePrefix(): string
@@ -43,7 +45,7 @@ class CreateIdpVersionTable extends AbstractMigration
             $table->addColumn('metadata', Types::TEXT);
 
             $table->addColumn('metadata_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
@@ -57,7 +59,7 @@ class CreateIdpVersionTable extends AbstractMigration
             $this->schemaManager->createTable($table);
         } catch (Throwable $exception) {
             throw $this->prepareGenericMigrationException(
-                \sprintf('Error creating table \'%s.', $tableName),
+                sprintf('Error creating table \'%s.', $tableName),
                 $exception
             );
         }
@@ -74,7 +76,7 @@ class CreateIdpVersionTable extends AbstractMigration
         try {
             $this->schemaManager->dropTable($tableName);
         } catch (Throwable $exception) {
-            throw $this->prepareGenericMigrationException(\sprintf('Could not drop table %s.', $tableName), $exception);
+            throw $this->prepareGenericMigrationException(sprintf('Could not drop table %s.', $tableName), $exception);
         }
     }
 }
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php
old mode 100644
new mode 100755
index a168bdfc1f1dd0c3225cf92a7196f0f07504de3e..70e5ff92bcd9196428748ca321e46faaa1014ff3
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -41,10 +41,10 @@ class CreateSpTable extends AbstractMigration
                 ->setAutoincrement(true);
 
             $table->addColumn('entity_id', Types::STRING)
-                ->setLength(TableConstants::COLUMN_ENTITY_ID_LENGTH);
+                ->setLength(BaseTableConstantsAlias::COLUMN_ENTITY_ID_LENGTH);
 
             $table->addColumn('entity_id_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php
old mode 100644
new mode 100755
index 6f7a4c097d3bcd08b5b46885c50af2cbf8fa4260..615759072aa253ca88418e23f4fec6c0d380aac2
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -45,7 +45,7 @@ class CreateSpVersionTable extends AbstractMigration
             $table->addColumn('metadata', Types::TEXT);
 
             $table->addColumn('metadata_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php
old mode 100644
new mode 100755
index 511bf5b71e3b3dddb0b7cafdb1964055d1fe638a..133b9f0d246c48120b85716c66c7fe34e8b4eddc
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -43,7 +43,7 @@ class CreateUserTable extends AbstractMigration
                 ->setLength(65535);
 
             $table->addColumn('identifier_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php
old mode 100644
new mode 100755
index 54b2693c37cdccdd75c9e33ae3c721b29c64ad7c..57c95d76e71f6c261f730289cb80e3d4bb958d3e
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php
@@ -6,7 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal
 
 use Doctrine\DBAL\Schema\Table;
 use Doctrine\DBAL\Types\Types;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstantsAlias;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use Throwable;
@@ -46,7 +46,7 @@ class CreateUserVersionTable extends AbstractMigration
                 ->setComment('Serialized attributes.');
 
             $table->addColumn('attributes_hash_sha256', Types::STRING)
-                ->setLength(TableConstants::COLUMN_HASH_SHA265_HEXITS_LENGTH)
+                ->setLength(BaseTableConstantsAlias::COLUMN_HASH_SHA265_HEXITS_LENGTH)
                 ->setFixed(true);
 
             $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE);
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php
old mode 100644
new mode 100755
index 0284c4e585714741f9582fbbf404e52d4501c0ab..b1a120bba880087b5172b78e5f5a1db88b0117dd
--- a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php
@@ -15,6 +15,8 @@ use Throwable;
 
 class Repository
 {
+    use Repository\UserVersionManagementTrait;
+
     protected Connection $connection;
     protected LoggerInterface $logger;
     protected string $tableNameIdp;
@@ -388,180 +390,6 @@ class Repository
         }
     }
 
-    /**
-     * @throws StoreException
-     */
-    public function getUser(string $identifierHashSha256): Result
-    {
-        try {
-            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-            /** @psalm-suppress TooManyArguments */
-            $queryBuilder->select(
-                TableConstants::TABLE_USER_COLUMN_NAME_ID,
-                TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER,
-                TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
-                TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT,
-            )
-                ->from($this->tableNameUser)
-                ->where(
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' .
-                    $queryBuilder->createNamedParameter($identifierHashSha256)
-                )->setMaxResults(1);
-
-            return $queryBuilder->executeQuery();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to get user by identifier hash SHA256 \'%s\'. Error was: %s.',
-                $identifierHashSha256,
-                $exception->getMessage()
-            );
-            $this->logger->error($message, compact('identifierHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function insertUser(
-        string $identifier,
-        string $identifierHashSha256,
-        DateTimeImmutable $createdAt = null
-    ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-        $createdAt = $createdAt ?? new DateTimeImmutable();
-
-        $queryBuilder->insert($this->tableNameUser)
-            ->values(
-                [
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => ':' .
-                        TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER,
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => ':' .
-                        TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
-                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => $identifier,
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => $identifierHashSha256,
-                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => $createdAt,
-                ],
-                [
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => Types::TEXT,
-                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => Types::STRING,
-                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE
-                ]
-            );
-
-        try {
-            $queryBuilder->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf('Error executing query to insert user. Error was: %s.', $exception->getMessage());
-            $this->logger->error($message, compact('identifier', 'identifierHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function getUserVersion(int $userId, string $attributesHashSha256): Result
-    {
-        try {
-            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-            /** @psalm-suppress TooManyArguments */
-            $queryBuilder->select(
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
-                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT,
-            )
-                ->from($this->tableNameUserVersion)
-                ->where(
-                    $queryBuilder->expr()->and(
-                        $queryBuilder->expr()->eq(
-                            TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
-                            $queryBuilder->createNamedParameter($userId, ParameterType::INTEGER)
-                        ),
-                        $queryBuilder->expr()->eq(
-                            TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
-                            $queryBuilder->createNamedParameter($attributesHashSha256)
-                        )
-                    )
-                )->setMaxResults(1);
-
-            return $queryBuilder->executeQuery();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to get user version for user ID %s and attribute array hash %s. Error was: %s.',
-                $userId,
-                $attributesHashSha256,
-                $exception->getMessage()
-            );
-            $this->logger->error($message, compact('userId', 'attributesHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
-    /**
-     * @throws StoreException
-     */
-    public function insertUserVersion(
-        int $userId,
-        string $attributes,
-        string $attributesHashSha256,
-        DateTimeImmutable $createdAt = null
-    ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-        $createdAt = $createdAt ?? new DateTimeImmutable();
-
-        $queryBuilder->insert($this->tableNameUserVersion)
-            ->values(
-                [
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => $userId,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => $attributes,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => $attributesHashSha256,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => $createdAt,
-                ],
-                [
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => Types::BIGINT,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => Types::TEXT,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => Types::STRING,
-                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
-                ]
-            );
-
-        try {
-            $queryBuilder->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to insert user version. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->error($message, compact('userId', 'attributesHashSha256'));
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
-
     /**
      * @throws StoreException
      */
@@ -620,39 +448,39 @@ class Repository
         int $userVersionId,
         DateTimeImmutable $createdAt = null
     ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $createdAt = $createdAt ?? new DateTimeImmutable();
+            $createdAt = $createdAt ?? new DateTimeImmutable();
 
-        $queryBuilder->insert($this->tableNameIdpSpUserVersion)
-            ->values(
-                [
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID => ':' .
-                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID => ':' .
-                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID => ':' .
-                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID => $idpVersionId,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID => $spVersionId,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID => $userVersionId,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT => $createdAt,
-                ],
-                [
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID => Types::BIGINT,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID => Types::BIGINT,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID => Types::BIGINT,
-                    TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE
-                ]
-            );
+            $queryBuilder->insert($this->tableNameIdpSpUserVersion)
+                ->values(
+                    [
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID => ':' .
+                            TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID => ':' .
+                            TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID => ':' .
+                            TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT => ':' .
+                            TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT,
+                    ]
+                )
+                ->setParameters(
+                    [
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID => $idpVersionId,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID => $spVersionId,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID => $userVersionId,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT => $createdAt,
+                    ],
+                    [
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID => Types::BIGINT,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID => Types::BIGINT,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID => Types::BIGINT,
+                        TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE
+                    ]
+                );
 
-        try {
             $queryBuilder->executeStatement();
         } catch (Throwable $exception) {
             $message = sprintf(
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository/UserVersionManagementTrait.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository/UserVersionManagementTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..5fa5e6687322ee155c7ef0e2f74d3793e01f9bee
--- /dev/null
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository/UserVersionManagementTrait.php
@@ -0,0 +1,188 @@
+<?php
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository;
+
+use DateTimeImmutable;
+use Doctrine\DBAL\ParameterType;
+use Doctrine\DBAL\Result;
+use Doctrine\DBAL\Types\Types;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use Throwable;
+
+trait UserVersionManagementTrait
+{
+    /**
+     * @throws StoreException
+     */
+    public function getUser(string $identifierHashSha256): Result
+    {
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+            /** @psalm-suppress TooManyArguments */
+            $queryBuilder->select(
+                TableConstants::TABLE_USER_COLUMN_NAME_ID,
+                TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER,
+                TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
+                TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT,
+            )
+                ->from($this->tableNameUser)
+                ->where(
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' .
+                    $queryBuilder->createNamedParameter($identifierHashSha256)
+                )->setMaxResults(1);
+
+            return $queryBuilder->executeQuery();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to get user by identifier hash SHA256 \'%s\'. Error was: %s.',
+                $identifierHashSha256,
+                $exception->getMessage()
+            );
+            $this->logger->error($message, compact('identifierHashSha256'));
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function insertUser(
+        string $identifier,
+        string $identifierHashSha256,
+        DateTimeImmutable $createdAt = null
+    ): void {
+        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+        $createdAt = $createdAt ?? new DateTimeImmutable();
+
+        $queryBuilder->insert($this->tableNameUser)
+            ->values(
+                [
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => ':' .
+                        TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER,
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => ':' .
+                        TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256,
+                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => ':' .
+                        TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT,
+                ]
+            )
+            ->setParameters(
+                [
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => $identifier,
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => $identifierHashSha256,
+                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => $createdAt,
+                ],
+                [
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER => Types::TEXT,
+                    TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 => Types::STRING,
+                    TableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE
+                ]
+            );
+
+        try {
+            $queryBuilder->executeStatement();
+        } catch (Throwable $exception) {
+            $message = sprintf('Error executing query to insert user. Error was: %s.', $exception->getMessage());
+            $this->logger->error($message, compact('identifier', 'identifierHashSha256'));
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function getUserVersion(int $userId, string $attributesHashSha256): Result
+    {
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+            /** @psalm-suppress TooManyArguments */
+            $queryBuilder->select(
+                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID,
+                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
+                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES,
+                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
+                TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT,
+            )
+                ->from($this->tableNameUserVersion)
+                ->where(
+                    $queryBuilder->expr()->and(
+                        $queryBuilder->expr()->eq(
+                            TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
+                            $queryBuilder->createNamedParameter($userId, ParameterType::INTEGER)
+                        ),
+                        $queryBuilder->expr()->eq(
+                            TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
+                            $queryBuilder->createNamedParameter($attributesHashSha256)
+                        )
+                    )
+                )->setMaxResults(1);
+
+            return $queryBuilder->executeQuery();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to get user version for user ID %s and attribute array hash %s. Error was: %s.',
+                $userId,
+                $attributesHashSha256,
+                $exception->getMessage()
+            );
+            $this->logger->error($message, compact('userId', 'attributesHashSha256'));
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function insertUserVersion(
+        int $userId,
+        string $attributes,
+        string $attributesHashSha256,
+        DateTimeImmutable $createdAt = null
+    ): void {
+        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+        $createdAt = $createdAt ?? new DateTimeImmutable();
+
+        $queryBuilder->insert($this->tableNameUserVersion)
+            ->values(
+                [
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => ':' .
+                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => ':' .
+                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => ':' .
+                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => ':' .
+                        TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT,
+                ]
+            )
+            ->setParameters(
+                [
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => $userId,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => $attributes,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => $attributesHashSha256,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => $createdAt,
+                ],
+                [
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID => Types::BIGINT,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES => Types::TEXT,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 => Types::STRING,
+                    TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
+                ]
+            );
+
+        try {
+            $queryBuilder->executeStatement();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to insert user version. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->error($message, compact('userId', 'attributesHashSha256'));
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+}
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/TableConstants.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/TableConstants.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/UserVersionResolvingTrait.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/UserVersionResolvingTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..60ec4b2e120723514c0af209a9465db6183c34c4
--- /dev/null
+++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/UserVersionResolvingTrait.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
+use Throwable;
+
+trait UserVersionResolvingTrait
+{
+    /**
+     * @throws StoreException
+     */
+    public function resolveUserId(HashDecoratedState $hashDecoratedState): int
+    {
+        $userIdentifierAttributeName = $this->moduleConfiguration->getUserIdAttributeName();
+
+        $userIdentifierValue = $hashDecoratedState->getState()->getFirstAttributeValue($userIdentifierAttributeName);
+        if ($userIdentifierValue === null) {
+            $message = sprintf('Attributes do not contain user ID attribute %s.', $userIdentifierAttributeName);
+            throw new UnexpectedValueException($message);
+        }
+
+        $userIdentifierValueHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifierValue);
+
+        // Check if it already exists.
+        try {
+            $result = $this->repository->getUser($userIdentifierValueHashSha256);
+            $userId = $result->fetchOne();
+
+            if ($userId !== false) {
+                return (int)$userId;
+            }
+        } catch (Throwable $exception) {
+            $message = sprintf('Error resolving user ID. Error was: %s.', $exception->getMessage());
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+
+        // Create new
+        try {
+            $this->repository->insertUser($userIdentifierValue, $userIdentifierValueHashSha256);
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error inserting new user, however, continuing in case of race condition. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->warning($message);
+        }
+
+        // Try again, this time it should exist...
+        try {
+            $result = $this->repository->getUser($userIdentifierValueHashSha256);
+            $userIdNew = $result->fetchOne();
+
+            if ($userIdNew !== false) {
+                return (int)$userIdNew;
+            }
+
+            $message = sprintf(
+                'Error fetching user even after insertion for identifier value hash SHA256 %s.',
+                $userIdentifierValueHashSha256
+            );
+            throw new StoreException($message);
+        } catch (Throwable $exception) {
+            $message = sprintf('Error resolving user ID. Error was: %s.', $exception->getMessage());
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function resolveUserVersionId(int $userId, HashDecoratedState $hashDecoratedState): int
+    {
+        $attributeArrayHashSha256 = $hashDecoratedState->getAttributesArrayHashSha256();
+
+        // Check if it already exists.
+        try {
+            $result = $this->repository->getUserVersion($userId, $attributeArrayHashSha256);
+            $userVersionId = $result->fetchOne();
+
+            if ($userVersionId !== false) {
+                return (int)$userVersionId;
+            }
+        } catch (Throwable $exception) {
+            $message = sprintf('Error resolving user version ID. Error was: %s.', $exception->getMessage());
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+
+        // Create new
+        try {
+            $this->repository->insertUserVersion(
+                $userId,
+                serialize($hashDecoratedState->getState()->getAttributes()),
+                $attributeArrayHashSha256
+            );
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error inserting new user version, however, continuing in case of race condition. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->warning($message);
+        }
+
+        // Try again, this time it should exist...
+        try {
+            $result = $this->repository->getUserVersion($userId, $attributeArrayHashSha256);
+            $userVersionIdNew = $result->fetchOne();
+
+            if ($userVersionIdNew !== false) {
+                return (int)$userVersionIdNew;
+            }
+
+            $message = sprintf(
+                'Error fetching user version even after insertion for user ID %s.',
+                $userId
+            );
+            throw new StoreException($message);
+        } catch (Throwable $exception) {
+            $message = sprintf('Error resolving user version ID. Error was: %s.', $exception->getMessage());
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+}
diff --git a/src/Data/Stores/Accounting/Bases/HashDecoratedState.php b/src/Data/Stores/Accounting/Bases/HashDecoratedState.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/Bases/TableConstants.php b/src/Data/Stores/Accounting/Bases/TableConstants.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php
old mode 100644
new mode 100755
index 8e84d9cd328ef6c07bee3931c53064d738eb49bf..69d1c716948d19205a5e0f2e865a1475b015c845
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php
@@ -5,23 +5,24 @@ declare(strict_types=1);
 namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current;
 
 use DateTimeImmutable;
+use Doctrine\DBAL\Exception;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store as BaseStore;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Traits\Store\GettableConnectedServicesTrait;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
 use SimpleSAML\Module\accounting\Data\Stores\Interfaces\ConnectedServicesInterface;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event;
-use SimpleSAML\Module\accounting\Entities\ConnectedService;
-use SimpleSAML\Module\accounting\Entities\User;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
-use Throwable;
 
 class Store extends BaseStore implements ConnectedServicesInterface
 {
+    use GettableConnectedServicesTrait;
+
     protected Repository $repository;
 
     /**
@@ -67,7 +68,7 @@ class Store extends BaseStore implements ConnectedServicesInterface
     }
 
     /**
-     * @throws StoreException
+     * @throws StoreException|Exception
      */
     public function persist(Event $authenticationEvent): void
     {
@@ -96,54 +97,6 @@ class Store extends BaseStore implements ConnectedServicesInterface
         }
     }
 
-    /**
-     * @throws StoreException
-     */
-    public function getConnectedServices(string $userIdentifier): ConnectedService\Bag
-    {
-        $connectedServiceProviderBag = new ConnectedService\Bag();
-
-        $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier);
-
-        $results = $this->repository->getConnectedServices($userIdentifierHashSha256);
-
-        if (empty($results)) {
-            return $connectedServiceProviderBag;
-        }
-
-        try {
-            $databasePlatform = $this->connection->dbal()->getDatabasePlatform();
-
-            /** @var array $result */
-            foreach ($results as $result) {
-                $rawConnectedServiceProvider = new RawConnectedService($result, $databasePlatform);
-
-                $serviceProvider = $this->helpersManager
-                    ->getProviderResolver()
-                    ->forServiceFromMetadataArray($rawConnectedServiceProvider->getServiceProviderMetadata());
-                $user = new User($rawConnectedServiceProvider->getUserAttributes());
-
-                $connectedServiceProviderBag->addOrReplace(
-                    new ConnectedService(
-                        $serviceProvider,
-                        $rawConnectedServiceProvider->getNumberOfAuthentications(),
-                        $rawConnectedServiceProvider->getLastAuthenticationAt(),
-                        $rawConnectedServiceProvider->getFirstAuthenticationAt(),
-                        $user
-                    )
-                );
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error populating connected service provider bag. Error was: %s',
-                $exception->getMessage()
-            );
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        return $connectedServiceProviderBag;
-    }
-
     /**
      * @throws StoreException
      */
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505100CreateSpTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505100CreateSpTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505200CreateUserTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505200CreateUserTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505300CreateUserVersionTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505300CreateUserVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTable.php
old mode 100644
new mode 100755
index bd61e6d0cf4172fe63f397f7e314d5887941c211..fa770ade3b25899cec733cf38ddcfc8f36b6f0b8
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTable.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTable.php
@@ -61,14 +61,17 @@ class Version20240505400CreateConnectedServiceTable extends AbstractMigration
                 ['id']
             );
 
+            // We are using versioned data for user management.
+            $versionedDataStoreTablePrefix = 'vds_';
+
             $table->addForeignKeyConstraint(
-                $this->preparePrefixedTableName('user'),
+                $this->preparePrefixedTableName('user', $versionedDataStoreTablePrefix),
                 ['user_id'],
                 ['id']
             );
 
             $table->addForeignKeyConstraint(
-                $this->preparePrefixedTableName('user_version'),
+                $this->preparePrefixedTableName('user_version', $versionedDataStoreTablePrefix),
                 ['user_version_id'],
                 ['id']
             );
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Repository.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Repository.php
old mode 100644
new mode 100755
index 74a8e6264d64dd2f33bbe01513e1315b9acf8120..f36e0a76efe5c9bbc8a239f9d296e9ef83bab356
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Repository.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Repository.php
@@ -10,14 +10,21 @@ use Doctrine\DBAL\Result;
 use Doctrine\DBAL\Types\Types;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository as BaseRepository;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants
-    as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedBaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Traits\Repository\DeletableConnectedServicesTrait;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use Throwable;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
 
 class Repository extends BaseRepository
 {
+    use DeletableConnectedServicesTrait;
+
     protected string $tableNameConnectedService;
 
     public function __construct(Connection $connection, LoggerInterface $logger)
@@ -86,62 +93,63 @@ class Repository extends BaseRepository
         int $count = 1,
         DateTimeImmutable $createdUpdatedAt = null
     ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $firstAuthenticationAt = $firstAuthenticationAt ?? new DateTimeImmutable();
-        $lastAuthenticationAt = $lastAuthenticationAt ?? $firstAuthenticationAt;
-        $count = max($count, 1);
-        $createdUpdatedAt = $createdUpdatedAt ?? new DateTimeImmutable();
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $queryBuilder->insert($this->tableNameConnectedService)
-            ->values(
-                [
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID => $spId,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID => $userId,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID => $userVersionId,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
-                        $firstAuthenticationAt,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
-                        $lastAuthenticationAt,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => $count,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => $createdUpdatedAt,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => $createdUpdatedAt,
-                ],
-                [
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID => Types::BIGINT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID => Types::BIGINT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID => Types::BIGINT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
-                        Types::DATETIMETZ_IMMUTABLE,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
-                        Types::DATETIMETZ_IMMUTABLE,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => Types::BIGINT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => Types::DATETIMETZ_IMMUTABLE,
-                ]
-            );
+            $firstAuthenticationAt = $firstAuthenticationAt ?? new DateTimeImmutable();
+            $lastAuthenticationAt = $lastAuthenticationAt ?? $firstAuthenticationAt;
+            $count = max($count, 1);
+            $createdUpdatedAt = $createdUpdatedAt ?? new DateTimeImmutable();
+
+            $queryBuilder->insert($this->tableNameConnectedService)
+                ->values(
+                    [
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
+                    ]
+                )
+                ->setParameters(
+                    [
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID => $spId,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID => $userId,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID => $userVersionId,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
+                            $firstAuthenticationAt,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
+                            $lastAuthenticationAt,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => $count,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => $createdUpdatedAt,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => $createdUpdatedAt,
+                    ],
+                    [
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID => Types::BIGINT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID => Types::BIGINT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID => Types::BIGINT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
+                            Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
+                            Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => Types::BIGINT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => Types::DATETIMETZ_IMMUTABLE,
+                    ]
+                );
 
-        try {
             $queryBuilder->executeStatement();
         } catch (Throwable $exception) {
             $message = sprintf(
@@ -167,50 +175,50 @@ class Repository extends BaseRepository
     ): void {
         $incrementCountBy = max($incrementCountBy, 1);
 
-        $updateCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        try {
+            $updateCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $updateCountQueryBuilder->update($this->tableNameConnectedService)
-            ->set(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT . ' + ' . $incrementCountBy
-            )
-            ->set(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID,
-                ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID
-            )
-            ->set(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT
-            )
-            ->set(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
-                ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT
-            )
-            ->setParameter(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID,
-                $userVersionId,
-                Types::INTEGER
-            )
-            ->setParameter(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                $happenedAt,
-                Types::DATETIMETZ_IMMUTABLE
-            )
-            ->setParameter(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
-                new DateTimeImmutable(),
-                Types::DATETIMETZ_IMMUTABLE
-            )
-            ->where(
-                $updateCountQueryBuilder->expr()->and(
-                    $updateCountQueryBuilder->expr()->eq(
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID,
-                        $updateCountQueryBuilder->createNamedParameter($connectedServiceId, Types::INTEGER)
-                    )
+            $updateCountQueryBuilder->update($this->tableNameConnectedService)
+                ->set(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT . ' + ' . $incrementCountBy
                 )
-            );
+                ->set(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID,
+                    ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID
+                )
+                ->set(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                    ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT
+                )
+                ->set(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
+                    ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT
+                )
+                ->setParameter(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID,
+                    $userVersionId,
+                    Types::INTEGER
+                )
+                ->setParameter(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                    $happenedAt,
+                    Types::DATETIMETZ_IMMUTABLE
+                )
+                ->setParameter(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
+                    new DateTimeImmutable(),
+                    Types::DATETIMETZ_IMMUTABLE
+                )
+                ->where(
+                    $updateCountQueryBuilder->expr()->and(
+                        $updateCountQueryBuilder->expr()->eq(
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID,
+                            $updateCountQueryBuilder->createNamedParameter($connectedServiceId, Types::INTEGER)
+                        )
+                    )
+                );
 
-        try {
             $updateCountQueryBuilder->executeStatement();
         } catch (Throwable $exception) {
             $message = sprintf(
@@ -237,22 +245,22 @@ class Repository extends BaseRepository
             $connectedServicesQueryBuilder->select(
                 BaseTableConstants::TABLE_ALIAS_SP . '.' .
                 BaseTableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID,
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
                 BaseTableConstants::TABLE_ALIAS_SP . '.' .
                 BaseTableConstants::TABLE_SP_COLUMN_NAME_METADATA . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA,
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA,
+                VersionedBaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
+                VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' .
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES,
             )->from($this->tableNameConnectedService, TableConstants::TABLE_ALIAS_CONNECTED_SERVICE)
             ->innerJoin(
                 //'ccs',
@@ -270,27 +278,27 @@ class Repository extends BaseRepository
             ->innerJoin(
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE,
                 $this->tableNameUser,
-                BaseTableConstants::TABLE_ALIAS_USER,
+                VersionedBaseTableConstants::TABLE_ALIAS_USER,
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID . ' = ' .
-                BaseTableConstants::TABLE_ALIAS_USER . '.' .
-                BaseTableConstants::TABLE_USER_COLUMN_NAME_ID
+                VersionedBaseTableConstants::TABLE_ALIAS_USER . '.' .
+                VersionedBaseTableConstants::TABLE_USER_COLUMN_NAME_ID
             )
             ->innerJoin(
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE,
                 $this->tableNameUserVersion,
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION,
+                VersionedBaseTableConstants::TABLE_ALIAS_USER_VERSION,
                 TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID . ' = ' .
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID
+                VersionedBaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
+                VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID
             )
             ->where(
-                BaseTableConstants::TABLE_ALIAS_USER . '.' .
-                BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' .
+                VersionedBaseTableConstants::TABLE_ALIAS_USER . '.' .
+                VersionedBaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' .
                 $connectedServicesQueryBuilder->createNamedParameter($userIdentifierHashSha256)
             )->orderBy(
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
                 'DESC'
             );
 
@@ -304,29 +312,4 @@ class Repository extends BaseRepository
             throw new StoreException($message, (int)$exception->getCode(), $exception);
         }
     }
-
-    /**
-     * @throws StoreException
-     */
-    public function deleteConnectedServicesOlderThan(DateTimeImmutable $dateTime): void
-    {
-        try {
-            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-            $queryBuilder->delete($this->tableNameConnectedService)
-                ->where(
-                    $queryBuilder->expr()->lt(
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                        $queryBuilder->createNamedParameter($dateTime, Types::DATETIME_IMMUTABLE)
-                    )
-                )->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to delete old connected services. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->error($message);
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
 }
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/TableConstants.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/TableConstants.php
old mode 100644
new mode 100755
index d0331be7b040900eca9b549e26e09bdc11bec9e0..f0a9dc6f131762dfb90cb1f648e7752d3d4a77be
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/TableConstants.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/TableConstants.php
@@ -13,7 +13,6 @@ class TableConstants
     public const TABLE_NAME_CONNECTED_SERVICE = 'connected_service';
     public const TABLE_ALIAS_CONNECTED_SERVICE = BaseTableConstants::TABLE_PREFIX . 'cs';
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID = 'id'; // int
-    public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_ID = 'idp_id'; // int
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID = 'sp_id'; // int
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID = 'user_id'; // int
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_VERSION_ID = 'user_version_id'; // int
@@ -22,12 +21,4 @@ class TableConstants
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT = 'count'; // int
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT = 'created_at'; // datetime
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT = 'updated_at'; // datetime
-
-    // Entity 'ConnectedService' (service provider) related.
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID = 'sp_entity_id';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS = 'number_of_authentications';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT = 'last_authentication_at';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT = 'first_authentication_at';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA = 'sp_metadata';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES = 'user_attributes';
 }
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/EntityTableConstants.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/EntityTableConstants.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f255fa5c72bc0683436a237cac8e6c1a5112aac
--- /dev/null
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/EntityTableConstants.php
@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal;
+
+class EntityTableConstants
+{
+    // Entity 'ConnectedService' (service provider) related.
+    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID = 'sp_entity_id';
+    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS = 'number_of_authentications';
+    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT = 'last_authentication_at';
+    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT = 'first_authentication_at';
+    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA = 'sp_metadata';
+    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES = 'user_attributes';
+}
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php
old mode 100644
new mode 100755
index 41334eaf9d90deca5f43f7269cc332c8794ba3b2..65cb1791831fc232c2b0d4f0469414d6ebeeb080
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php
@@ -6,7 +6,6 @@ namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\
 
 use DateTimeImmutable;
 use Doctrine\DBAL\Platforms\AbstractPlatform;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\TableConstants;
 use SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity;
 use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
 
@@ -23,23 +22,23 @@ class RawConnectedService extends AbstractRawEntity
         parent::__construct($rawRow, $abstractPlatform);
 
         $this->numberOfAuthentications = (int)$rawRow[
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS
+        EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS
         ];
 
         $this->lastAuthenticationAt = $this->resolveDateTimeImmutable(
-            $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT]
+            $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT]
         );
 
         $this->firstAuthenticationAt = $this->resolveDateTimeImmutable(
-            $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT]
+            $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT]
         );
 
         $this->serviceProviderMetadata = $this->resolveServiceProviderMetadata(
-            (string)$rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+            (string)$rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
         );
 
         $this->userAttributes = $this->resolveUserAttributes(
-            (string)$rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+            (string)$rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
         );
     }
 
@@ -89,11 +88,11 @@ class RawConnectedService extends AbstractRawEntity
     protected function validate(array $rawRow): void
     {
         $columnsToCheck = [
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES,
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA,
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES,
         ];
 
         foreach ($columnsToCheck as $column) {
@@ -103,44 +102,44 @@ class RawConnectedService extends AbstractRawEntity
         }
 
         if (
-            ! is_numeric($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS])
+            ! is_numeric($rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS])
         ) {
             $message = sprintf(
                 'Column %s must be numeric.',
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS
             );
             throw new UnexpectedValueException($message);
         }
 
         /** @noinspection DuplicatedCode */
-        if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT
             );
             throw new UnexpectedValueException($message);
         }
 
-        if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT
             );
             throw new UnexpectedValueException($message);
         }
 
-        if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA
             );
             throw new UnexpectedValueException($message);
         }
 
-        if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES])) {
+        if (! is_string($rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES])) {
             $message = sprintf(
                 'Column %s must be string.',
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES
             );
             throw new UnexpectedValueException($message);
         }
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Traits/Repository/DeletableConnectedServicesTrait.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Traits/Repository/DeletableConnectedServicesTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..2d795028aa1d9a4e3a607a5605ffcba1ada7b832
--- /dev/null
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Traits/Repository/DeletableConnectedServicesTrait.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Traits\Repository;
+
+use DateTimeImmutable;
+use Doctrine\DBAL\Types\Types;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use Throwable;
+
+trait DeletableConnectedServicesTrait
+{
+    /**
+     * @throws StoreException
+     */
+    public function deleteConnectedServicesOlderThan(DateTimeImmutable $dateTime): void
+    {
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+            $queryBuilder->delete($this->tableNameConnectedService)
+                ->where(
+                    $queryBuilder->expr()->lt(
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                        $queryBuilder->createNamedParameter($dateTime, Types::DATETIME_IMMUTABLE)
+                    )
+                )->executeStatement();
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error executing query to delete old connected services. Error was: %s.',
+                $exception->getMessage()
+            );
+            $this->logger->error($message);
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+    }
+}
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Traits/Store/GettableConnectedServicesTrait.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Traits/Store/GettableConnectedServicesTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa22c8a189c001e29499441ca148860e29653daf
--- /dev/null
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Traits/Store/GettableConnectedServicesTrait.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Traits\Store;
+
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService;
+use SimpleSAML\Module\accounting\Entities\ConnectedService;
+use SimpleSAML\Module\accounting\Entities\User;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use Throwable;
+
+trait GettableConnectedServicesTrait
+{
+    /**
+     * @throws StoreException
+     */
+    public function getConnectedServices(string $userIdentifier): ConnectedService\Bag
+    {
+        $connectedServiceProviderBag = new ConnectedService\Bag();
+
+        $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier);
+
+        $results = $this->repository->getConnectedServices($userIdentifierHashSha256);
+
+        if (empty($results)) {
+            return $connectedServiceProviderBag;
+        }
+
+        try {
+            $databasePlatform = $this->connection->dbal()->getDatabasePlatform();
+
+            /** @var array $result */
+            foreach ($results as $result) {
+                $rawConnectedServiceProvider = new RawConnectedService($result, $databasePlatform);
+
+                $serviceProvider = $this->helpersManager
+                    ->getProviderResolver()
+                    ->forServiceFromMetadataArray($rawConnectedServiceProvider->getServiceProviderMetadata());
+                $user = new User($rawConnectedServiceProvider->getUserAttributes());
+
+                $connectedServiceProviderBag->addOrReplace(
+                    new ConnectedService(
+                        $serviceProvider,
+                        $rawConnectedServiceProvider->getNumberOfAuthentications(),
+                        $rawConnectedServiceProvider->getLastAuthenticationAt(),
+                        $rawConnectedServiceProvider->getFirstAuthenticationAt(),
+                        $user
+                    )
+                );
+            }
+        } catch (Throwable $exception) {
+            $message = sprintf(
+                'Error populating connected service provider bag. Error was: %s',
+                $exception->getMessage()
+            );
+            throw new StoreException($message, (int)$exception->getCode(), $exception);
+        }
+
+        return $connectedServiceProviderBag;
+    }
+}
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php
old mode 100644
new mode 100755
index 6887a5bff291b1b36398e09e0cb633b9a6c94de7..6f81fdec9771f670bfe27c9f3f836db4db71ae52
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php
@@ -5,23 +5,24 @@ declare(strict_types=1);
 namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned;
 
 use DateTimeImmutable;
+use Doctrine\DBAL\Exception;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store as BaseStore;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Traits\Store\GettableConnectedServicesTrait;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Repository;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
 use SimpleSAML\Module\accounting\Data\Stores\Interfaces\ConnectedServicesInterface;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event;
-use SimpleSAML\Module\accounting\Entities\ConnectedService;
-use SimpleSAML\Module\accounting\Entities\User;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
-use Throwable;
 
 class Store extends BaseStore implements ConnectedServicesInterface
 {
+    use GettableConnectedServicesTrait;
+
     protected Repository $repository;
 
     /**
@@ -67,7 +68,7 @@ class Store extends BaseStore implements ConnectedServicesInterface
     }
 
     /**
-     * @throws StoreException
+     * @throws StoreException|Exception
      */
     public function persist(Event $authenticationEvent): void
     {
@@ -96,54 +97,6 @@ class Store extends BaseStore implements ConnectedServicesInterface
         $this->repository->touchConnectedServiceVersionsTimestamp($userId, $spId);
     }
 
-    /**
-     * @throws StoreException
-     */
-    public function getConnectedServices(string $userIdentifier): ConnectedService\Bag
-    {
-        $connectedServiceProviderBag = new ConnectedService\Bag();
-
-        $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier);
-
-        $results = $this->repository->getConnectedServices($userIdentifierHashSha256);
-
-        if (empty($results)) {
-            return $connectedServiceProviderBag;
-        }
-
-        try {
-            $databasePlatform = $this->connection->dbal()->getDatabasePlatform();
-
-            /** @var array $result */
-            foreach ($results as $result) {
-                $rawConnectedServiceProvider = new RawConnectedService($result, $databasePlatform);
-
-                $serviceProvider = $this->helpersManager
-                    ->getProviderResolver()
-                    ->forServiceFromMetadataArray($rawConnectedServiceProvider->getServiceProviderMetadata());
-                $user = new User($rawConnectedServiceProvider->getUserAttributes());
-
-                $connectedServiceProviderBag->addOrReplace(
-                    new ConnectedService(
-                        $serviceProvider,
-                        $rawConnectedServiceProvider->getNumberOfAuthentications(),
-                        $rawConnectedServiceProvider->getLastAuthenticationAt(),
-                        $rawConnectedServiceProvider->getFirstAuthenticationAt(),
-                        $user
-                    )
-                );
-            }
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error populating connected service provider bag. Error was: %s',
-                $exception->getMessage()
-            );
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-
-        return $connectedServiceProviderBag;
-    }
-
     /**
      * @throws StoreException
      */
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateConnectedServiceTable.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateConnectedServiceTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php
old mode 100644
new mode 100755
index 51f2f5a849382ce83cd96284b461e06bc1a098eb..bba95b63da07e3c326997a9b20556f62318445be
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php
@@ -10,14 +10,19 @@ use Doctrine\DBAL\Result;
 use Doctrine\DBAL\Types\Types;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository as BaseRepository;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants
-    as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Traits\Repository\DeletableConnectedServicesTrait;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use Throwable;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
 
 class Repository extends BaseRepository
 {
+    use DeletableConnectedServicesTrait;
+
     protected string $tableNameConnectedService;
 
     public function __construct(Connection $connection, LoggerInterface $logger)
@@ -78,54 +83,55 @@ class Repository extends BaseRepository
         int $count = 1,
         DateTimeImmutable $createdUpdatedAt = null
     ): void {
-        $queryBuilder = $this->connection->dbal()->createQueryBuilder();
+        try {
+            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $firstAuthenticationAt = $firstAuthenticationAt ?? new DateTimeImmutable();
-        $lastAuthenticationAt = $lastAuthenticationAt ?? $firstAuthenticationAt;
-        $count = max($count, 1);
-        $createdUpdatedAt = $createdUpdatedAt ?? new DateTimeImmutable();
+            $firstAuthenticationAt = $firstAuthenticationAt ?? new DateTimeImmutable();
+            $lastAuthenticationAt = $lastAuthenticationAt ?? $firstAuthenticationAt;
+            $count = max($count, 1);
+            $createdUpdatedAt = $createdUpdatedAt ?? new DateTimeImmutable();
 
-        $queryBuilder->insert($this->tableNameConnectedService)
-            ->values(
-                [
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => ':' .
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
-                ]
-            )
-            ->setParameters(
-                [
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID => $idpSpUserVersionId,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
-                        $firstAuthenticationAt,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
-                        $lastAuthenticationAt,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => $count,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => $createdUpdatedAt,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => $createdUpdatedAt,
-                ],
-                [
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID => Types::BIGINT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
-                        Types::DATETIMETZ_IMMUTABLE,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
-                        Types::DATETIMETZ_IMMUTABLE,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => Types::BIGINT,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
-                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => Types::DATETIMETZ_IMMUTABLE,
-                ]
-            );
+            $queryBuilder->insert($this->tableNameConnectedService)
+                ->values(
+                    [
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => ':' .
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
+                    ]
+                )
+                ->setParameters(
+                    [
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID =>
+                            $idpSpUserVersionId,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
+                            $firstAuthenticationAt,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
+                            $lastAuthenticationAt,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => $count,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => $createdUpdatedAt,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => $createdUpdatedAt,
+                    ],
+                    [
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID => Types::BIGINT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
+                            Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
+                            Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT => Types::BIGINT,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE,
+                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT => Types::DATETIMETZ_IMMUTABLE,
+                    ]
+                );
 
-        try {
             $queryBuilder->executeStatement();
         } catch (Throwable $exception) {
             $message = sprintf(
@@ -148,34 +154,34 @@ class Repository extends BaseRepository
         DateTimeImmutable $happenedAt,
         int $incrementCountBy = 1
     ): void {
-        $incrementCountBy = max($incrementCountBy, 1);
+        try {
+            $incrementCountBy = max($incrementCountBy, 1);
 
-        $updateCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+            $updateCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $updateCountQueryBuilder->update($this->tableNameConnectedService)
-            ->set(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT . ' + ' . $incrementCountBy
-            )
-            ->set(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT
-            )
-            ->setParameter(
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
-                $happenedAt,
-                Types::DATETIMETZ_IMMUTABLE
-            )
-            ->where(
-                $updateCountQueryBuilder->expr()->and(
-                    $updateCountQueryBuilder->expr()->eq(
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID,
-                        $updateCountQueryBuilder->createNamedParameter($connectedServiceId, Types::INTEGER)
-                    )
+            $updateCountQueryBuilder->update($this->tableNameConnectedService)
+                ->set(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT,
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT . ' + ' . $incrementCountBy
                 )
-            );
+                ->set(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                    ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT
+                )
+                ->setParameter(
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                    $happenedAt,
+                    Types::DATETIMETZ_IMMUTABLE
+                )
+                ->where(
+                    $updateCountQueryBuilder->expr()->and(
+                        $updateCountQueryBuilder->expr()->eq(
+                            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID,
+                            $updateCountQueryBuilder->createNamedParameter($connectedServiceId, Types::INTEGER)
+                        )
+                    )
+                );
 
-        try {
             $updateCountQueryBuilder->executeStatement();
         } catch (Throwable $exception) {
             $message = sprintf(
@@ -198,92 +204,92 @@ class Repository extends BaseRepository
         int $spId,
         DateTimeImmutable $happenedAt = null
     ): void {
-        $happenedAt = $happenedAt ?? new DateTimeImmutable();
+        try {
+            $happenedAt = $happenedAt ?? new DateTimeImmutable();
 
-        $selectConnectedServiceVersionsQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+            $selectConnectedServiceVersionsQueryBuilder = $this->connection->dbal()->createQueryBuilder();
 
-        $selectConnectedServiceVersionsQueryBuilder->select(
-            TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '. ' .
-            TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID
-        )
-            ->from($this->tableNameConnectedService, TableConstants::TABLE_ALIAS_CONNECTED_SERVICE)
-            ->innerJoin(
-            //'vcs',
-                TableConstants::TABLE_ALIAS_CONNECTED_SERVICE,
-                //'vds_idp_sp_user_version',
-                $this->tableNameIdpSpUserVersion,
+            $selectConnectedServiceVersionsQueryBuilder->select(
+                TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '. ' .
+                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID
+            )
+                ->from($this->tableNameConnectedService, TableConstants::TABLE_ALIAS_CONNECTED_SERVICE)
+                ->innerJoin(
+                //'vcs',
+                    TableConstants::TABLE_ALIAS_CONNECTED_SERVICE,
+                    //'vds_idp_sp_user_version',
+                    $this->tableNameIdpSpUserVersion,
+                    //'visuv',
+                    BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION,
+                    //'vcs.idp_sp_user_version_id =  visuv.id'
+                    TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
+                    TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID . ' = ' .
+                    BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' .
+                    BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID
+                )->innerJoin(
                 //'visuv',
-                BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION,
-                //'vcs.idp_sp_user_version_id =  visuv.id'
-                TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
-                TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID . ' = ' .
-                BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID
-            )->innerJoin(
-            //'visuv',
-                BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION,
-                //'vds_sp_version',
-                $this->tableNameSpVersion,
+                    BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION,
+                    //'vds_sp_version',
+                    $this->tableNameSpVersion,
+                    //'vsv',
+                    BaseTableConstants::TABLE_ALIAS_SP_VERSION,
+                    //'visuv.sp_version_id = vsv.id'
+                    BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' .
+                    BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID . ' = ' .
+                    BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' .
+                    BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID
+                )->innerJoin(
                 //'vsv',
-                BaseTableConstants::TABLE_ALIAS_SP_VERSION,
-                //'visuv.sp_version_id = vsv.id'
-                BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID . ' = ' .
-                BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' .
-                BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID
-            )->innerJoin(
-            //'vsv',
-                BaseTableConstants::TABLE_ALIAS_SP_VERSION,
-                //'vds_sp',
-                $this->tableNameSp,
-                //'vs',
-                BaseTableConstants::TABLE_ALIAS_SP,
-                //'vsv.sp_id = vs.id'
-                BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' .
-                BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_SP_ID .
-                ' = ' . BaseTableConstants::TABLE_ALIAS_SP . '.' .
-                BaseTableConstants::TABLE_SP_COLUMN_NAME_ID
-            )->innerJoin(
-            //'visuv',
-                BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION,
-                //'vds_user_version',
-                $this->tableNameUserVersion,
+                    BaseTableConstants::TABLE_ALIAS_SP_VERSION,
+                    //'vds_sp',
+                    $this->tableNameSp,
+                    //'vs',
+                    BaseTableConstants::TABLE_ALIAS_SP,
+                    //'vsv.sp_id = vs.id'
+                    BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' .
+                    BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_SP_ID .
+                    ' = ' . BaseTableConstants::TABLE_ALIAS_SP . '.' .
+                    BaseTableConstants::TABLE_SP_COLUMN_NAME_ID
+                )->innerJoin(
+                //'visuv',
+                    BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION,
+                    //'vds_user_version',
+                    $this->tableNameUserVersion,
+                    //'vuv',
+                    BaseTableConstants::TABLE_ALIAS_USER_VERSION,
+                    //'visuv.user_version_id = vuv.id'
+                    BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' .
+                    BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID . ' = ' .
+                    BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
+                    BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID
+                )->innerJoin(
                 //'vuv',
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION,
-                //'visuv.user_version_id = vuv.id'
-                BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID . ' = ' .
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID
-            )->innerJoin(
-            //'vuv',
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION,
-                //'vds_user',
-                $this->tableNameUser,
-                //'vu',
-                BaseTableConstants::TABLE_ALIAS_USER,
-                //'vuv.user_id = vu.id'
-                BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
-                BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID . ' = ' .
-                BaseTableConstants::TABLE_ALIAS_USER . '.' .
-                BaseTableConstants::TABLE_USER_COLUMN_NAME_ID
-            )
-            ->where(
-                $selectConnectedServiceVersionsQueryBuilder->expr()->and(
-                    $selectConnectedServiceVersionsQueryBuilder->expr()->eq(
-                    //'vs.id = ' .
-                        BaseTableConstants::TABLE_ALIAS_SP . '.' . BaseTableConstants::TABLE_SP_COLUMN_NAME_ID,
-                        $selectConnectedServiceVersionsQueryBuilder->createNamedParameter($spId)
-                    ),
-                    $selectConnectedServiceVersionsQueryBuilder->expr()->eq(
-                    //'vu.id = ' .
-                        BaseTableConstants::TABLE_ALIAS_USER . '.' . BaseTableConstants::TABLE_USER_COLUMN_NAME_ID,
-                        $selectConnectedServiceVersionsQueryBuilder->createNamedParameter($userId)
-                    )
+                    BaseTableConstants::TABLE_ALIAS_USER_VERSION,
+                    //'vds_user',
+                    $this->tableNameUser,
+                    //'vu',
+                    BaseTableConstants::TABLE_ALIAS_USER,
+                    //'vuv.user_id = vu.id'
+                    BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
+                    BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID . ' = ' .
+                    BaseTableConstants::TABLE_ALIAS_USER . '.' .
+                    BaseTableConstants::TABLE_USER_COLUMN_NAME_ID
                 )
-            );
+                ->where(
+                    $selectConnectedServiceVersionsQueryBuilder->expr()->and(
+                        $selectConnectedServiceVersionsQueryBuilder->expr()->eq(
+                        //'vs.id = ' .
+                            BaseTableConstants::TABLE_ALIAS_SP . '.' . BaseTableConstants::TABLE_SP_COLUMN_NAME_ID,
+                            $selectConnectedServiceVersionsQueryBuilder->createNamedParameter($spId)
+                        ),
+                        $selectConnectedServiceVersionsQueryBuilder->expr()->eq(
+                        //'vu.id = ' .
+                            BaseTableConstants::TABLE_ALIAS_USER . '.' . BaseTableConstants::TABLE_USER_COLUMN_NAME_ID,
+                            $selectConnectedServiceVersionsQueryBuilder->createNamedParameter($userId)
+                        )
+                    )
+                );
 
-        try {
             /** @var array<array-key,string> $connectedServiceVersions */
             $connectedServiceVersions = $selectConnectedServiceVersionsQueryBuilder->executeQuery()->fetchFirstColumn();
         } catch (Throwable $exception) {
@@ -319,6 +325,7 @@ class Repository extends BaseRepository
 
         try {
             $updateLastAuthenticationAtQueryBuilder->executeStatement();
+            // @codeCoverageIgnoreStart
         } catch (Throwable $exception) {
             $message = sprintf(
                 'Error touching connected service versions. Error was: %s.',
@@ -330,6 +337,7 @@ class Repository extends BaseRepository
             );
             throw new StoreException($message, (int)$exception->getCode(), $exception);
         }
+        // @codeCoverageIgnoreEnd
     }
 
     /**
@@ -346,19 +354,19 @@ class Repository extends BaseRepository
                 //'vs.entity_id AS sp_entity_id',
                 BaseTableConstants::TABLE_ALIAS_SP . '.' .
                 BaseTableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID,
                 //'SUM(vcs.count) AS number_of_authentications',
                 'SUM(' .  TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT . ') AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
                 //'MAX(vcs.last_authentication_at) AS last_authentication_at',
                 'MAX(' .  TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT . ') AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT,
                 //'MIN(vcs.first_authentication_at) AS first_authentication_at',
                 'MIN(' .  TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' .
                 TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT . ') AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT,
             )->from($this->tableNameConnectedService, TableConstants::TABLE_ALIAS_CONNECTED_SERVICE)
             ->innerJoin(
                 //'vcs',
@@ -428,10 +436,10 @@ class Repository extends BaseRepository
                 $connectedServicesQueryBuilder->createNamedParameter($userIdentifierHashSha256)
             )->groupBy(
                 //'sp_entity_id'
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID
             )->orderBy(
                 //'number_of_authentications',
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS,
                 'DESC'
             );
 
@@ -440,15 +448,15 @@ class Repository extends BaseRepository
                 //'vs.entity_id AS sp_entity_id',
                 BaseTableConstants::TABLE_ALIAS_SP . '.' .
                 BaseTableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID,
                 //'vsv.metadata AS sp_metadata',
                 BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' .
                 BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA,
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA,
                 //'vuv.attributes AS user_attributes',
                 BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' .
                 BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' .
-                TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES
+                EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES
             )->from(
                 //'vds_connected_service',
                 $this->tableNameConnectedService,
@@ -584,29 +592,4 @@ class Repository extends BaseRepository
             throw new StoreException($message, (int)$exception->getCode(), $exception);
         }
     }
-
-    /**
-     * @throws StoreException
-     */
-    public function deleteConnectedServicesOlderThan(DateTimeImmutable $dateTime): void
-    {
-        try {
-            $queryBuilder = $this->connection->dbal()->createQueryBuilder();
-
-            $queryBuilder->delete($this->tableNameConnectedService)
-                ->where(
-                    $queryBuilder->expr()->lt(
-                        TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT,
-                        $queryBuilder->createNamedParameter($dateTime, Types::DATETIME_IMMUTABLE)
-                    )
-                )->executeStatement();
-        } catch (Throwable $exception) {
-            $message = sprintf(
-                'Error executing query to delete old connected services. Error was: %s.',
-                $exception->getMessage()
-            );
-            $this->logger->error($message);
-            throw new StoreException($message, (int)$exception->getCode(), $exception);
-        }
-    }
 }
diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/TableConstants.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/TableConstants.php
old mode 100644
new mode 100755
index 028c4d501dc256f2363b09af35a46891b2ba7cf2..19f941117d2855e56bcfd3898322f1ceeba43b31
--- a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/TableConstants.php
+++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/TableConstants.php
@@ -19,12 +19,4 @@ class TableConstants
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT = 'count'; // int
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT = 'created_at'; // datetime
     public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT = 'updated_at'; // datetime
-
-    // Entity 'ConnectedService' (service provider) related.
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID = 'sp_entity_id';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS = 'number_of_authentications';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT = 'last_authentication_at';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT = 'first_authentication_at';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA = 'sp_metadata';
-    public const ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES = 'user_attributes';
 }
diff --git a/src/Data/Stores/Bases/AbstractStore.php b/src/Data/Stores/Bases/AbstractStore.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntity.php b/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntity.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Bases/DoctrineDbal/AbstractStore.php b/src/Data/Stores/Bases/DoctrineDbal/AbstractStore.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Builders/Bases/AbstractStoreBuilder.php b/src/Data/Stores/Builders/Bases/AbstractStoreBuilder.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Builders/DataStoreBuilder.php b/src/Data/Stores/Builders/DataStoreBuilder.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Builders/JobsStoreBuilder.php b/src/Data/Stores/Builders/JobsStoreBuilder.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Connections/Bases/AbstractMigrator.php b/src/Data/Stores/Connections/Bases/AbstractMigrator.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php b/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php
old mode 100644
new mode 100755
index f4e37ec68eab9a391121a862bb03ae8ade8faca2..73863b6ae2677e367233f95f986baf03cc8dcc76
--- a/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php
+++ b/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php
@@ -48,11 +48,14 @@ abstract class AbstractMigration implements MigrationInterface
      * Prepare prefixed table name which will include table prefix from connection, local table prefix, and table name.
      *
      * @param string $tableName
+     * @param string|null $tablePrefixOverride
      * @return string
      */
-    protected function preparePrefixedTableName(string $tableName): string
+    protected function preparePrefixedTableName(string $tableName, string $tablePrefixOverride = null): string
     {
-        return $this->connection->preparePrefixedTableName($this->getLocalTablePrefix() . $tableName);
+        $tablePrefix = $tablePrefixOverride ?? $this->getLocalTablePrefix();
+
+        return $this->connection->preparePrefixedTableName($tablePrefix . $tableName);
     }
 
     /**
diff --git a/src/Data/Stores/Connections/DoctrineDbal/Connection.php b/src/Data/Stores/Connections/DoctrineDbal/Connection.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Connections/DoctrineDbal/Factory.php b/src/Data/Stores/Connections/DoctrineDbal/Factory.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Connections/DoctrineDbal/Migrator.php b/src/Data/Stores/Connections/DoctrineDbal/Migrator.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/ActivityInterface.php b/src/Data/Stores/Interfaces/ActivityInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/ConnectedServicesInterface.php b/src/Data/Stores/Interfaces/ConnectedServicesInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/ConnectionInterface.php b/src/Data/Stores/Interfaces/ConnectionInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/DataStoreInterface.php b/src/Data/Stores/Interfaces/DataStoreInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/JobsStoreInterface.php b/src/Data/Stores/Interfaces/JobsStoreInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/MigrationInterface.php b/src/Data/Stores/Interfaces/MigrationInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Interfaces/StoreInterface.php b/src/Data/Stores/Interfaces/StoreInterface.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store.php b/src/Data/Stores/Jobs/DoctrineDbal/Store.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJob.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJob.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/Repository.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Repository.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/TableConstants.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/TableConstants.php
old mode 100644
new mode 100755
diff --git a/src/Data/Stores/Jobs/PhpRedis/RedisStore.php b/src/Data/Stores/Jobs/PhpRedis/RedisStore.php
old mode 100644
new mode 100755
diff --git a/src/Data/Trackers/Activity/DoctrineDbal/CurrentDataTracker.php b/src/Data/Trackers/Activity/DoctrineDbal/CurrentDataTracker.php
new file mode 100644
index 0000000000000000000000000000000000000000..9013533708e11fa4e1c8e7cb199876d4c70e58a9
--- /dev/null
+++ b/src/Data/Trackers/Activity/DoctrineDbal/CurrentDataTracker.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal;
+
+use DateInterval;
+use DateTimeImmutable;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\CurrentDataProvider;
+use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+
+class CurrentDataTracker extends CurrentDataProvider implements DataTrackerInterface
+{
+    /**
+     * @throws StoreException
+     */
+    public static function build(
+        ModuleConfiguration $moduleConfiguration,
+        LoggerInterface $logger,
+        string $connectionType = ModuleConfiguration\ConnectionType::MASTER
+    ): self {
+        return new self($moduleConfiguration, $logger, $connectionType);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function process(Event $authenticationEvent): void
+    {
+        $this->store->persist($authenticationEvent);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function enforceDataRetentionPolicy(DateInterval $retentionPolicy): void
+    {
+        $dateTime = (new DateTimeImmutable())->sub($retentionPolicy);
+
+        $this->store->deleteDataOlderThan($dateTime);
+    }
+}
diff --git a/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php b/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php
old mode 100644
new mode 100755
index 8f30682233ad5f30284d94da2d16c25a15ef8f40..8759f0841af4b91d2f697ec4fbcf3701c68839e5
--- a/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php
+++ b/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php
@@ -26,11 +26,17 @@ class VersionedDataTracker extends VersionedDataProvider implements DataTrackerI
         return new self($moduleConfiguration, $logger, $connectionType);
     }
 
+    /**
+     * @throws StoreException
+     */
     public function process(Event $authenticationEvent): void
     {
         $this->store->persist($authenticationEvent);
     }
 
+    /**
+     * @throws StoreException
+     */
     public function enforceDataRetentionPolicy(DateInterval $retentionPolicy): void
     {
         $dateTime = (new DateTimeImmutable())->sub($retentionPolicy);
diff --git a/src/Data/Trackers/Builders/DataTrackerBuilder.php b/src/Data/Trackers/Builders/DataTrackerBuilder.php
old mode 100644
new mode 100755
diff --git a/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php b/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php
old mode 100644
new mode 100755
index 0ff530fcada5f5aa1916c2efc5fad8e3eb95d89b..5ab79192259141d4e7ba9c0a265f6a31736f7440
--- a/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php
+++ b/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php
@@ -6,6 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineD
 
 use DateInterval;
 use DateTimeImmutable;
+use Doctrine\DBAL\Exception;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider;
 use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface;
@@ -26,6 +27,10 @@ class CurrentDataTracker extends CurrentDataProvider implements DataTrackerInter
         return new self($moduleConfiguration, $logger, $connectionType);
     }
 
+    /**
+     * @throws StoreException
+     * @throws Exception
+     */
     public function process(Event $authenticationEvent): void
     {
         $this->store->persist($authenticationEvent);
diff --git a/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php b/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php
old mode 100644
new mode 100755
index 96013b0974ac4eed693db57b36d8a4a7634c432b..d324ab83b4b83416919be9e6e05ddebbdbbcf827
--- a/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php
+++ b/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php
@@ -6,6 +6,7 @@ namespace SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineD
 
 use DateInterval;
 use DateTimeImmutable;
+use Doctrine\DBAL\Exception;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider;
 use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface;
@@ -26,6 +27,10 @@ class VersionedDataTracker extends VersionedDataProvider implements DataTrackerI
         return new self($moduleConfiguration, $logger, $connectionType);
     }
 
+    /**
+     * @throws StoreException
+     * @throws Exception
+     */
     public function process(Event $authenticationEvent): void
     {
         $this->store->persist($authenticationEvent);
diff --git a/src/Data/Trackers/Interfaces/DataTrackerInterface.php b/src/Data/Trackers/Interfaces/DataTrackerInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Activity.php b/src/Entities/Activity.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Activity/Bag.php b/src/Entities/Activity/Bag.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Event.php b/src/Entities/Authentication/Event.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Event/Job.php b/src/Entities/Authentication/Event/Job.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Event/State/Oidc.php b/src/Entities/Authentication/Event/State/Oidc.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Event/State/Saml2.php b/src/Entities/Authentication/Event/State/Saml2.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Protocol/Bag.php b/src/Entities/Authentication/Protocol/Bag.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Protocol/Oidc.php b/src/Entities/Authentication/Protocol/Oidc.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Authentication/Protocol/Saml2.php b/src/Entities/Authentication/Protocol/Saml2.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Bases/AbstractJob.php b/src/Entities/Bases/AbstractJob.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Bases/AbstractPayload.php b/src/Entities/Bases/AbstractPayload.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Bases/AbstractProvider.php b/src/Entities/Bases/AbstractProvider.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Bases/AbstractState.php b/src/Entities/Bases/AbstractState.php
old mode 100644
new mode 100755
diff --git a/src/Entities/ConnectedService.php b/src/Entities/ConnectedService.php
old mode 100644
new mode 100755
diff --git a/src/Entities/ConnectedService/Bag.php b/src/Entities/ConnectedService/Bag.php
old mode 100644
new mode 100755
diff --git a/src/Entities/GenericJob.php b/src/Entities/GenericJob.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Interfaces/AuthenticationProtocolInterface.php b/src/Entities/Interfaces/AuthenticationProtocolInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Interfaces/IdentityProviderInterface.php b/src/Entities/Interfaces/IdentityProviderInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Interfaces/JobInterface.php b/src/Entities/Interfaces/JobInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Interfaces/ProviderInterface.php b/src/Entities/Interfaces/ProviderInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Interfaces/ServiceProviderInterface.php b/src/Entities/Interfaces/ServiceProviderInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Interfaces/StateInterface.php b/src/Entities/Interfaces/StateInterface.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Providers/Identity/Oidc.php b/src/Entities/Providers/Identity/Oidc.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Providers/Identity/Saml2.php b/src/Entities/Providers/Identity/Saml2.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Providers/Service/Oidc.php b/src/Entities/Providers/Service/Oidc.php
old mode 100644
new mode 100755
diff --git a/src/Entities/Providers/Service/Saml2.php b/src/Entities/Providers/Service/Saml2.php
old mode 100644
new mode 100755
diff --git a/src/Entities/User.php b/src/Entities/User.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/Exception.php b/src/Exceptions/Exception.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/InvalidConfigurationException.php b/src/Exceptions/InvalidConfigurationException.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/InvalidValueException.php b/src/Exceptions/InvalidValueException.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/MetadataException.php b/src/Exceptions/MetadataException.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/StateException.php b/src/Exceptions/StateException.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/StoreException.php b/src/Exceptions/StoreException.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/StoreException/MigrationException.php b/src/Exceptions/StoreException/MigrationException.php
old mode 100644
new mode 100755
diff --git a/src/Exceptions/UnexpectedValueException.php b/src/Exceptions/UnexpectedValueException.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Arr.php b/src/Helpers/Arr.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Attributes.php b/src/Helpers/Attributes.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/AuthenticationEventStateResolver.php b/src/Helpers/AuthenticationEventStateResolver.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/DateTime.php b/src/Helpers/DateTime.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Environment.php b/src/Helpers/Environment.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Filesystem.php b/src/Helpers/Filesystem.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Hash.php b/src/Helpers/Hash.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/InstanceBuilderUsingModuleConfiguration.php b/src/Helpers/InstanceBuilderUsingModuleConfiguration.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Network.php b/src/Helpers/Network.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/ProviderResolver.php b/src/Helpers/ProviderResolver.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Random.php b/src/Helpers/Random.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/Routes.php b/src/Helpers/Routes.php
old mode 100644
new mode 100755
diff --git a/src/Helpers/SspModule.php b/src/Helpers/SspModule.php
old mode 100644
new mode 100755
index df5798a0f7c18fc57b3c7e2c0b5409da8615c06b..33819ab70dfe8de20d13f3d3df4a21ecd1f6b4a7
--- a/src/Helpers/SspModule.php
+++ b/src/Helpers/SspModule.php
@@ -15,6 +15,7 @@ class SspModule
     {
         try {
             return Module::isModuleEnabled($moduleName);
+            // @codeCoverageIgnoreStart
         } catch (\Throwable $exception) {
             $message = sprintf('Could not check if module %s is enabled', $moduleName);
             throw new Module\accounting\Exceptions\InvalidConfigurationException(
@@ -23,5 +24,6 @@ class SspModule
                 $exception
             );
         }
+        // @codeCoverageIgnoreEnd
     }
 }
diff --git a/src/Http/Controllers/Admin/Configuration.php b/src/Http/Controllers/Admin/Configuration.php
old mode 100644
new mode 100755
diff --git a/src/Http/Controllers/User/Profile.php b/src/Http/Controllers/User/Profile.php
old mode 100644
new mode 100755
diff --git a/src/Interfaces/BuildableUsingModuleConfigurationInterface.php b/src/Interfaces/BuildableUsingModuleConfigurationInterface.php
old mode 100644
new mode 100755
diff --git a/src/Interfaces/SetupableInterface.php b/src/Interfaces/SetupableInterface.php
old mode 100644
new mode 100755
diff --git a/src/ModuleConfiguration.php b/src/ModuleConfiguration.php
old mode 100644
new mode 100755
diff --git a/src/ModuleConfiguration/AccountingProcessingType.php b/src/ModuleConfiguration/AccountingProcessingType.php
old mode 100644
new mode 100755
diff --git a/src/ModuleConfiguration/ConnectionType.php b/src/ModuleConfiguration/ConnectionType.php
old mode 100644
new mode 100755
diff --git a/src/Services/AlertsBag.php b/src/Services/AlertsBag.php
old mode 100644
new mode 100755
diff --git a/src/Services/AlertsBag/Alert.php b/src/Services/AlertsBag/Alert.php
old mode 100644
new mode 100755
diff --git a/src/Services/CsrfToken.php b/src/Services/CsrfToken.php
old mode 100644
new mode 100755
diff --git a/src/Services/HelpersManager.php b/src/Services/HelpersManager.php
old mode 100644
new mode 100755
diff --git a/src/Services/JobRunner.php b/src/Services/JobRunner.php
old mode 100644
new mode 100755
diff --git a/src/Services/JobRunner/RateLimiter.php b/src/Services/JobRunner/RateLimiter.php
old mode 100644
new mode 100755
diff --git a/src/Services/JobRunner/State.php b/src/Services/JobRunner/State.php
old mode 100644
new mode 100755
diff --git a/src/Services/Logger.php b/src/Services/Logger.php
old mode 100644
new mode 100755
diff --git a/src/Services/MenuManager.php b/src/Services/MenuManager.php
old mode 100644
new mode 100755
diff --git a/src/Services/MenuManager/MenuItem.php b/src/Services/MenuManager/MenuItem.php
old mode 100644
new mode 100755
diff --git a/src/Services/SspModuleManager.php b/src/Services/SspModuleManager.php
old mode 100644
new mode 100755
diff --git a/src/Services/TrackerResolver.php b/src/Services/TrackerResolver.php
old mode 100644
new mode 100755
diff --git a/src/SspModule/Oidc.php b/src/SspModule/Oidc.php
old mode 100644
new mode 100755
diff --git a/src/Traits/HasUserAttributesTrait.php b/src/Traits/HasUserAttributesTrait.php
old mode 100644
new mode 100755
diff --git a/templates/admin/configuration/status.twig b/templates/admin/configuration/status.twig
old mode 100644
new mode 100755
index d8b00d9e62461235ce75cded91061ce922783201..1dac6f8635e443a5125a732fab48cb0c9facb70e
--- a/templates/admin/configuration/status.twig
+++ b/templates/admin/configuration/status.twig
@@ -33,7 +33,7 @@
 
             {% if providers is not empty %}
                 <li>
-                    <strong>{{ 'Providers setup'|trans }}</strong>:
+                    <strong>{{ 'Providers setup needed'|trans }}</strong>:
                     <ul>
                         {% for providerClass, providerInstance in providers %}
                             <li>
diff --git a/templates/base.twig b/templates/base.twig
old mode 100644
new mode 100755
diff --git a/templates/includes/_alerts.twig b/templates/includes/_alerts.twig
old mode 100644
new mode 100755
diff --git a/templates/includes/_header.twig b/templates/includes/_header.twig
old mode 100644
new mode 100755
diff --git a/templates/includes/_navigation.twig b/templates/includes/_navigation.twig
old mode 100644
new mode 100755
diff --git a/templates/user/activity.twig b/templates/user/activity.twig
old mode 100644
new mode 100755
diff --git a/templates/user/activity.twig.bak b/templates/user/activity.twig.bak
old mode 100644
new mode 100755
diff --git a/templates/user/connected-organizations.twig b/templates/user/connected-organizations.twig
old mode 100644
new mode 100755
diff --git a/templates/user/connected-organizations.twig.bak b/templates/user/connected-organizations.twig.bak
old mode 100644
new mode 100755
diff --git a/templates/user/personal-data.twig b/templates/user/personal-data.twig
old mode 100644
new mode 100755
diff --git a/templates/user/personal-data.twig.bak b/templates/user/personal-data.twig.bak
old mode 100644
new mode 100755
diff --git a/tests/attributemap/test.php b/tests/attributemap/test.php
old mode 100644
new mode 100755
diff --git a/tests/attributemap/test2.php b/tests/attributemap/test2.php
old mode 100644
new mode 100755
diff --git a/tests/config-templates/config.php b/tests/config-templates/config.php
old mode 100644
new mode 100755
diff --git a/tests/config-templates/module_accounting.php b/tests/config-templates/module_accounting.php
old mode 100644
new mode 100755
diff --git a/tests/config-templates/module_oidc.php b/tests/config-templates/module_oidc.php
new file mode 100644
index 0000000000000000000000000000000000000000..2452edbecbb1f3b0bc3952a9c95ada21bcc7bd49
--- /dev/null
+++ b/tests/config-templates/module_oidc.php
@@ -0,0 +1,253 @@
+<?php
+
+/*
+ * This file is part of the simplesamlphp-module-oidc.
+ *
+ * Copyright (C) 2018 by the Spanish Research and Academic Network.
+ *
+ * This code was developed by Universidad de Córdoba (UCO https://www.uco.es)
+ * for the RedIRIS SIR service (SIR: http://www.rediris.es/sir)
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$config = [
+    // pagination
+    'items_per_page' => 20,
+
+    // The private key passphrase (optional)
+    // 'pass_phrase' => 'secret',
+    // The cert and key for signing the ID token. Default names are oidc_module.key and oidc_module.crt
+    // 'privatekey' => 'oidc_module.key',
+    // 'certificate' => 'oidc_module.crt',
+
+    // Tokens TTL
+    'authCodeDuration' => 'PT10M', // 10 minutes
+    'refreshTokenDuration' => 'P1M', // 1 month
+    'accessTokenDuration' => 'PT1H', // 1 hour,
+
+    // Tag to run storage cleanup script using the cron module...
+    'cron_tag' => 'hourly',
+
+    // Set token signer
+    // See Lcobucci\JWT\Signer algorithms in https://github.com/lcobucci/jwt/tree/master/src/Signer
+    'signer' => \Lcobucci\JWT\Signer\Rsa\Sha256::class,
+    // 'signer' => \Lcobucci\JWT\Signer\Hmac\Sha256::class,
+    // 'signer' => \Lcobucci\JWT\Signer\Ecdsa\Sha256::class,
+
+    // The key id to use in the header. Default is a finger print of the public key
+    // 'kid' => 'abcd',
+
+
+    // this is the default auth source used for authentication if the auth source
+    // is not specified on particular client
+    'auth' => 'default-sp',
+
+    // useridattr is the attribute-name that contains the userid as returned from idp. By default, this attribute
+    // will be dynamically added to the 'sub' claim in the attribute-to-claim translation table (you will probably
+    // want to use this attribute as the 'sub' claim since it designates unique identifier for the user).
+    'useridattr' => 'uid',
+
+    /**
+     * Permissions let the module expose functionality to specific users.
+     * In the below configuration, a user's eduPersonEntitlement attribute is examined. If the user
+     * tries to do something that requires the 'client' permission (such as registering their own client)
+     * then they will need one of the eduPersonEntitlements from the `client` permission array.
+     *
+     * A permission can be disable by commenting it out.
+     */
+    'permissions' => [
+        // Attribute to inspect to determine user's permissions
+        'attribute' => 'eduPersonEntitlement',
+        // Which entitlements allow for registering, editing, delete a client. OIDC clients are owned by the creator
+        'client' => ['urn:example:oidc:manage:client'],
+    ],
+
+    // Settings regarding Authentication Processing Filters.
+    // Note: OIDC authN state array will not contain all of the keys which are available during SAML authN,
+    // like Service Provider metadata, etc.
+    //
+    // At the moment, the following SAML authN data will be available during OIDC authN in the sate array:
+    // - ['Attributes'], ['Authority'], ['AuthnInstant'], ['Expire']
+    // Source and destination will have entity IDs corresponding to the OP issuer ID and Client ID respectively.
+    // - ['Source']['entityid'] - contains OpenId Provider issuer ID
+    // - ['Destination']['entityid'] - contains Relying Party (OIDC Client) ID
+    // In addition to that, the following OIDC related data will be available in the state array:
+    // - ['Oidc']['OpenIdProviderMetadata'] - contains information otherwise available from the OIDC configuration URL.
+    // - ['Oidc']['RelyingPartyMetadata'] - contains information about the OIDC client making the authN request.
+    // - ['Oidc']['AuthorizationRequestParameters'] - contains relevant authorization request query parameters.
+    //
+    // List of authproc filters which will run for every OIDC authN. Add filters as described in docs for SAML authproc
+    // @see https://simplesamlphp.org/docs/stable/simplesamlphp-authproc
+    'authproc.oidc' => [
+        // Add authproc filters here
+    ],
+
+    // Optional custom scopes. You can create as many scopes as you want and assign claims to them.
+    'scopes' => [
+//        'private' => [ // The key represents the scope name.
+//            'description' => 'private scope',
+//            'claim_name_prefix' => '', // Prefix to apply for all claim names from this scope
+//            'are_multiple_claim_values_allowed' => false, // Are claims for this scope allowed to have multiple values
+//            'claims' => ['national_document_id'] // Claims from the translation table which this scope will contain
+//        ],
+    ],
+    'translate' => [
+        /*
+         * This is the default translate table from SAML to OIDC.
+         * You can change here the behaviour or add more translation to your
+         * private attributes scopes
+         *
+         * The basic format is
+         *
+         * 'claimName' => [
+         *     'type' => 'string|int|bool|json',
+         *      // For non JSON types
+         *     'attributes' => ['samlAttribute1', 'samlAttribute2']
+         *      // For JSON types
+         *     'claims => [
+         *          'subclaim' => [ 'type' => 'string', 'attributes' => ['saml1']]
+         *      ]
+         *  ]
+         *
+         * For convenience the default type is "string" so type does not need to be defined.
+         * If "attributes" is not set, then it is assumed that the rest of the values are saml
+         * attribute names.
+         *
+         * Note on 'sub' claim: by default, the list of attributes for 'sub' claim will also contain attribute defined
+         * in 'useridattr' setting. You will probably want to use this attribute as the 'sub' claim since it
+         * designates unique identifier for the user, However, override as necessary.
+         */
+//        'sub' => [
+//            'attribute-defined-in-useridattr', // will be dynamically added if the list for 'sub' claim is not set.
+//            'eduPersonPrincipalName',
+//            'eduPersonTargetedID',
+//            'eduPersonUniqueId',
+//        ],
+//        'name' => [
+//            'cn',
+//            'displayName',
+//        ],
+//        'family_name' => [
+//            'sn',
+//        ],
+//        'given_name' => [
+//            'givenName',
+//        ],
+//        'middle_name' => [
+//            // Empty
+//        ],
+//        'nickname' => [
+//            'eduPersonNickname',
+//        ],
+//        'preferred_username' => [
+//            'uid',
+//        ],
+//        'profile' => [
+//            'labeledURI',
+//            'description',
+//        ],
+//        'picture' => [
+//            // Empty. Previously 'jpegPhoto' however spec calls for a url to photo, not an actual photo.
+//        ],
+//        'website' => [
+//            // Empty
+//        ],
+//        'gender' => [
+//            // Empty
+//        ],
+//        'birthdate' => [
+//            // Empty
+//        ],
+//        'zoneinfo' => [
+//            // Empty
+//        ],
+//        'locale' => [
+//            'preferredLanguage',
+//        ],
+//        'updated_at' => [
+//            'type' => 'int',
+//            'attributes' => [],
+//        ],
+//        'email' => [
+//            'mail',
+//        ],
+//        'email_verified' => [
+//            'type' => 'bool',
+//            'attributes' => [],
+//        ],
+//         // address is a json object. Set the 'formatted' sub claim to postalAddress
+//        'address' => [
+//            'type' => 'json',
+//            'claims' => [
+//                'formatted' => ['postalAddress'],
+//            ]
+//        ],
+//        'phone_number' => [
+//            'mobile',
+//            'telephoneNumber',
+//            'homePhone',
+//        ],
+//        'phone_number_verified' => [
+//            'type' => 'bool',
+//            'attributes' => [],
+//        ],
+        /*
+         * Optional scopes attributes
+         */
+//        'national_document_id' => [
+//            'schacPersonalUniqueId',
+//        ],
+    ],
+
+    // Optional list of the Authentication Context Class References that this OP supports.
+    // If populated, this list will be available in OP discovery document (OP Metadata) as 'acr_values_supported'.
+    // @see https://datatracker.ietf.org/doc/html/rfc6711
+    // @see https://www.iana.org/assignments/loa-profiles/loa-profiles.xhtml
+    // @see https://openid.net/specs/openid-connect-core-1_0.html#IDToken (acr claim)
+    // @see https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest (acr_values parameter)
+    // Syntax: string[] (array of strings)
+    'acrValuesSupported' => [
+//        'https://refeds.org/assurance/profile/espresso',
+//        'https://refeds.org/assurance/profile/cappuccino',
+//        'https://refeds.org/profile/mfa',
+//        'https://refeds.org/profile/sfa',
+//        'urn:mace:incommon:iap:silver',
+//        'urn:mace:incommon:iap:bronze',
+//        '4',
+//        '3',
+//        '2',
+//        '1',
+//        '0',
+//        '...',
+    ],
+
+    // If this OP supports ACRs, indicate which usable auth source supports which ACRs.
+    // Order of ACRs is important, more important ones being first.
+    // Syntax: array<string,string[]> (array with auth source as key and value being array of ACR values as strings)
+    'authSourcesToAcrValuesMap' => [
+//        'example-userpass' => ['1', '0'],
+//        'default-sp' => ['http://id.incommon.org/assurance/bronze', '2', '1', '0'],
+//        'strongly-assured-authsource' => [
+//            'https://refeds.org/assurance/profile/espresso',
+//            'https://refeds.org/profile/mfa',
+//            'https://refeds.org/assurance/profile/cappuccino',
+//            'https://refeds.org/profile/sfa',
+//            '3',
+//            '2',
+//            '1',
+//            '0',
+//        ],
+    ],
+
+    // If this OP supports ACRs, indicate if authentication using cookie should be forced to specific ACR value.
+    // If this option is set to null, no specific ACR will be forced for cookie authentication and the resulting ACR
+    // will be one of the ACRs supported on used auth source during authentication, that is, session creation.
+    // If this option is set to specific ACR, with ACR value being one of the ACR value this OP supports, it will be
+    // set to that ACR for cookie authentication.
+    // For example, OIDC Core Spec notes that authentication using a long-lived browser cookie is one example where
+    // the use of "level 0" is appropriate:
+//     'forcedAcrValueForCookieAuthentication' => '0',
+    'forcedAcrValueForCookieAuthentication' => null,
+];
diff --git a/tests/src/Auth/Process/AccountingTest.php b/tests/src/Auth/Process/AccountingTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Constants/ConnectionParameters.php b/tests/src/Constants/ConnectionParameters.php
old mode 100644
new mode 100755
diff --git a/tests/src/Constants/DateTime.php b/tests/src/Constants/DateTime.php
old mode 100644
new mode 100755
diff --git a/tests/src/Constants/RawRowResult.php b/tests/src/Constants/RawRowResult.php
old mode 100644
new mode 100755
index faa0e7ee6d652fd07c8fe8d7038f65a0c7f5b93b..7f05120eed13d774dc8d64e5f380c14056816079
--- a/tests/src/Constants/RawRowResult.php
+++ b/tests/src/Constants/RawRowResult.php
@@ -5,8 +5,8 @@ declare(strict_types=1);
 
 namespace SimpleSAML\Test\Module\accounting\Constants;
 
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\TableConstants as ActivityTableConstants;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\TableConstants as ConnectedServicesTableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\EntityTableConstants as ActivityTableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants as ConnectedServicesTableConstants;
 
 class RawRowResult
 {
diff --git a/tests/src/Constants/SerializedJob.php b/tests/src/Constants/SerializedJob.php
old mode 100644
new mode 100755
diff --git a/tests/src/Constants/StateArrays.php b/tests/src/Constants/StateArrays.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Providers/Activity/DoctrineDbal/CurrentDataProviderTest.php b/tests/src/Data/Providers/Activity/DoctrineDbal/CurrentDataProviderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..96d2eeab9f8fc08f733b0a41bcaef99974458238
--- /dev/null
+++ b/tests/src/Data/Providers/Activity/DoctrineDbal/CurrentDataProviderTest.php
@@ -0,0 +1,155 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Providers\Activity\DoctrineDbal;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\CurrentDataProvider;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\CurrentDataTracker;
+use SimpleSAML\Module\accounting\Entities\Activity\Bag;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\CurrentDataProvider
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ */
+class CurrentDataProviderTest extends TestCase
+{
+    /**
+     * @var MockObject
+     */
+    protected $moduleConfigurationMock;
+    /**
+     * @var MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var MockObject
+     */
+    protected $storeMock;
+    /**
+     * @var MockObject
+     */
+    protected $activityBagMock;
+
+    protected function setUp(): void
+    {
+        $this->moduleConfigurationMock = $this->createMock(ModuleConfiguration::class);
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationMock->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->storeMock = $this->createMock(Store::class);
+
+        $this->activityBagMock = $this->createMock(Bag::class);
+    }
+
+    protected function prepareMockedInstance(): CurrentDataProvider
+    {
+        return new CurrentDataProvider(
+            $this->moduleConfigurationMock,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::SLAVE,
+            $this->storeMock
+        );
+    }
+
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(CurrentDataProvider::class, $this->prepareMockedInstance());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanSelfBuild(): void
+    {
+        $this->assertInstanceOf(
+            CurrentDataProvider::class,
+            CurrentDataProvider::build($this->moduleConfigurationMock, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupReturnsTrue(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(true);
+
+        $this->assertTrue($this->prepareMockedInstance()->needsSetup());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupReturnsFalse(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(false);
+
+        $this->assertFalse($this->prepareMockedInstance()->needsSetup());
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testRunSetupLogsWarningIfNotNeeded(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(false);
+        $this->loggerMock->expects($this->once())->method('warning');
+
+        $this->prepareMockedInstance()->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testRunSetupIfNeeded(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(true);
+        $this->storeMock->expects($this->once())->method('runSetup');
+
+        $this->prepareMockedInstance()->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetActivitiy(): void
+    {
+        $this->storeMock->expects($this->once())
+            ->method('getActivity')
+            ->with('userId', 10, 0)
+            ->willReturn($this->activityBagMock);
+
+        $this->prepareMockedInstance()->getActivity('userId', 10, 0);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetTracker(): void
+    {
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            $this->prepareMockedInstance()->getTracker()
+        );
+    }
+}
diff --git a/tests/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProviderTest.php b/tests/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProviderTest.php
old mode 100644
new mode 100755
index d505423f99cb424025b1638e4e3d1a4b87d46f69..befe42f59d0d87bbc25ec4a89af60b21ca10152a
--- a/tests/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProviderTest.php
+++ b/tests/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProviderTest.php
@@ -9,22 +9,25 @@ use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider;
 use PHPUnit\Framework\TestCase;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store;
-use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface;
+use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker;
+use SimpleSAML\Module\accounting\Entities\Activity\Bag;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
 
 /**
  * @covers \SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Repository
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Repository
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
  */
 class VersionedDataProviderTest extends TestCase
 {
@@ -40,12 +43,22 @@ class VersionedDataProviderTest extends TestCase
      * @var MockObject
      */
     protected $storeMock;
+    /**
+     * @var MockObject
+     */
+    protected $activityBagMock;
 
     protected function setUp(): void
     {
         $this->moduleConfigurationMock = $this->createMock(ModuleConfiguration::class);
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationMock->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+
         $this->loggerMock = $this->createMock(LoggerInterface::class);
         $this->storeMock = $this->createMock(Store::class);
+
+        $this->activityBagMock = $this->createMock(Bag::class);
     }
 
     protected function prepareMockedInstance(): VersionedDataProvider
@@ -61,64 +74,83 @@ class VersionedDataProviderTest extends TestCase
     public function testCanCreateInstance(): void
     {
         $this->assertInstanceOf(VersionedDataProvider::class, $this->prepareMockedInstance());
+    }
 
-        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
-        $this->moduleConfigurationMock->method('getConnectionParameters')
-            ->willReturn($connectionParams);
-
-        $this->assertInstanceOf(VersionedDataProvider::class, VersionedDataProvider::build(
-            $this->moduleConfigurationMock,
-            $this->loggerMock,
-        ));
+    /**
+     * @throws StoreException
+     */
+    public function testCanSelfBuild(): void
+    {
+        $this->assertInstanceOf(
+            VersionedDataProvider::class,
+            VersionedDataProvider::build($this->moduleConfigurationMock, $this->loggerMock)
+        );
     }
 
-    public function testNeedsSetup(): void
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupReturnsTrue(): void
     {
         $this->storeMock->method('needsSetup')->willReturn(true);
+
         $this->assertTrue($this->prepareMockedInstance()->needsSetup());
     }
 
-    public function testDoesNotNeedsSetup(): void
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupReturnsFalse(): void
     {
         $this->storeMock->method('needsSetup')->willReturn(false);
+
         $this->assertFalse($this->prepareMockedInstance()->needsSetup());
     }
 
-    public function testSetupDoesNotRunWhenNotNeeded(): void
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testRunSetupLogsWarningIfNotNeeded(): void
     {
         $this->storeMock->method('needsSetup')->willReturn(false);
-        $this->loggerMock->expects($this->once())
-            ->method('warning');
-        $this->storeMock->expects($this->never())
-            ->method('runSetup');
+        $this->loggerMock->expects($this->once())->method('warning');
 
         $this->prepareMockedInstance()->runSetup();
     }
 
-    public function testSetupRunsWhenNeeded(): void
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testRunSetupIfNeeded(): void
     {
         $this->storeMock->method('needsSetup')->willReturn(true);
         $this->storeMock->expects($this->once())->method('runSetup');
+
         $this->prepareMockedInstance()->runSetup();
     }
 
-    public function testGetsActivityFromStore(): void
+    /**
+     * @throws StoreException
+     */
+    public function testGetActivitiy(): void
     {
         $this->storeMock->expects($this->once())
             ->method('getActivity')
-            ->with('1', 2, 3);
+            ->with('userId', 10, 0)
+            ->willReturn($this->activityBagMock);
 
-        $this->prepareMockedInstance()->getActivity('1', 2, 3);
+        $this->prepareMockedInstance()->getActivity('userId', 10, 0);
     }
 
-    public function testCanGetTracker(): void
+    /**
+     * @throws StoreException
+     */
+    public function testGetTracker(): void
     {
-        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
-        $this->moduleConfigurationMock->method('getConnectionParameters')
-            ->willReturn($connectionParams);
-
         $this->assertInstanceOf(
-            DataTrackerInterface::class,
+            VersionedDataTracker::class,
             $this->prepareMockedInstance()->getTracker()
         );
     }
diff --git a/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php b/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php
old mode 100644
new mode 100755
index 7b2b5ac7a031a3b6cad82420c15a3b94a3d607d7..64e3c4a9570d8e8ad4d6493f16545f5a50a3e40a
--- a/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php
+++ b/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php
@@ -7,9 +7,13 @@ namespace SimpleSAML\Test\Module\accounting\Data\Providers\Builders;
 use PHPUnit\Framework\MockObject\Stub;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider as
+    ActivityVersionedDataProviderAlias;
 use SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder;
-use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker;
+use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider as
+    ConnectedServicesVersionedDataProviderAlias;
 use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
 use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
@@ -25,13 +29,15 @@ use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store
- * @uses \SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
  * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
  * @uses \SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Repository
  */
 class DataProviderBuilderTest extends TestCase
 {
@@ -51,15 +57,20 @@ class DataProviderBuilderTest extends TestCase
         $this->helpersManager = new HelpersManager();
     }
 
+    protected function prepareMockedInstance(): DataProviderBuilder
+    {
+        return new DataProviderBuilder(
+            $this->moduleConfigurationStub,
+            $this->loggerStub,
+            $this->helpersManager
+        );
+    }
+
     public function testCanCreateInstance(): void
     {
         $this->assertInstanceOf(
             DataProviderBuilder::class,
-            new DataProviderBuilder(
-                $this->moduleConfigurationStub,
-                $this->loggerStub,
-                $this->helpersManager
-            )
+            $this->prepareMockedInstance()
         );
     }
 
@@ -68,23 +79,53 @@ class DataProviderBuilderTest extends TestCase
      */
     public function testCanBuildDataProvider(): void
     {
-        $builder = new DataProviderBuilder(
-            $this->moduleConfigurationStub,
-            $this->loggerStub,
-            $this->helpersManager
-        );
+        $builder = $this->prepareMockedInstance();
 
-        $this->assertInstanceOf(VersionedDataTracker::class, $builder->build(VersionedDataTracker::class));
+        $this->assertInstanceOf(
+            ActivityVersionedDataProviderAlias::class,
+            $builder->build(ActivityVersionedDataProviderAlias::class)
+        );
     }
 
     public function testThrowsForInvalidClass(): void
     {
         $this->expectException(Exception::class);
 
-        (new DataProviderBuilder(
-            $this->moduleConfigurationStub,
-            $this->loggerStub,
-            $this->helpersManager
-        ))->build('invalid');
+        $this->prepareMockedInstance()->build('invalid');
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testCanBuildActivityProvider(): void
+    {
+        $this->assertInstanceOf(
+            ActivityVersionedDataProviderAlias::class,
+            $this->prepareMockedInstance()->buildActivityProvider(ActivityVersionedDataProviderAlias::class)
+        );
+    }
+
+    public function testBuildActivityProviderThrowsForInvalidClass(): void
+    {
+        $this->expectException(UnexpectedValueException::class);
+
+        $this->prepareMockedInstance()->buildActivityProvider(ConnectedServicesVersionedDataProviderAlias::class);
+    }
+
+    public function testCanBuildConnectedServicesProvider(): void
+    {
+        $this->assertInstanceOf(
+            ConnectedServicesVersionedDataProviderAlias::class,
+            $this->prepareMockedInstance()->buildConnectedServicesProvider(
+                ConnectedServicesVersionedDataProviderAlias::class
+            )
+        );
+    }
+
+    public function testBuildConnectedServicesProviderThrowsForInvalidClass(): void
+    {
+        $this->expectException(UnexpectedValueException::class);
+
+        $this->prepareMockedInstance()->buildConnectedServicesProvider(ActivityVersionedDataProviderAlias::class);
     }
 }
diff --git a/tests/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProviderTest.php b/tests/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProviderTest.php
new file mode 100755
index 0000000000000000000000000000000000000000..38a4246e22e5039c7432345cb91ce52148be6492
--- /dev/null
+++ b/tests/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProviderTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal;
+
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider;
+use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Entities\ConnectedService\Bag;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ */
+class CurrentDataProviderTest extends TestCase
+{
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $moduleConfigurationMock;
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $storeMock;
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $connectedServicesBagMock;
+
+    protected function setUp(): void
+    {
+        $this->moduleConfigurationMock = $this->createMock(ModuleConfiguration::class);
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationMock->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->storeMock = $this->createMock(Store::class);
+
+        $this->connectedServicesBagMock = $this->createMock(Bag::class);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    protected function prepareMockedInstance(): CurrentDataProvider
+    {
+        return new CurrentDataProvider(
+            $this->moduleConfigurationMock,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::SLAVE,
+            $this->storeMock
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(CurrentDataProvider::class, $this->prepareMockedInstance());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanSelfBuild(): void
+    {
+        $this->assertInstanceOf(
+            CurrentDataProvider::class,
+            CurrentDataProvider::build($this->moduleConfigurationMock, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupWhenTrue(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(true);
+        $this->assertTrue($this->prepareMockedInstance()->needsSetup());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupWhenFalse(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(false);
+        $this->assertFalse($this->prepareMockedInstance()->needsSetup());
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testRunSetupLogsWarningWhenNotNeeded(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(false);
+        $this->loggerMock->expects($this->once())->method('warning');
+
+        $this->prepareMockedInstance()->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testCanRunSetupWhenNeeded(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(true);
+        $this->storeMock->expects($this->once())->method('runSetup');
+
+        $this->prepareMockedInstance()->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanGetConnectedServices(): void
+    {
+        $this->storeMock->expects($this->once())->method('getConnectedServices')->with('userId');
+
+        $this->prepareMockedInstance()->getConnectedServices('userId');
+    }
+
+    public function testCanGetTracker(): void
+    {
+        $this->assertInstanceOf(
+            CurrentDataProvider::class,
+            $this->prepareMockedInstance()->getTracker()
+        );
+    }
+}
diff --git a/tests/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProviderTest.php b/tests/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProviderTest.php
new file mode 100755
index 0000000000000000000000000000000000000000..0e10822a0dc7efe315e246116a2d405c5e626680
--- /dev/null
+++ b/tests/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProviderTest.php
@@ -0,0 +1,153 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SimpleSAML\Test\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal;
+
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store;
+use SimpleSAML\Module\accounting\Entities\ConnectedService\Bag;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ */
+class VersionedDataProviderTest extends TestCase
+{
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $moduleConfigurationMock;
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $storeMock;
+    /**
+     * @var \PHPUnit\Framework\MockObject\MockObject
+     */
+    protected $connectedServicesBagMock;
+    protected function setUp(): void
+    {
+        $this->moduleConfigurationMock = $this->createMock(ModuleConfiguration::class);
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationMock->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->storeMock = $this->createMock(Store::class);
+
+        $this->connectedServicesBagMock = $this->createMock(Bag::class);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function prepareMockedInstance(): VersionedDataProvider
+    {
+        return new VersionedDataProvider(
+            $this->moduleConfigurationMock,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::SLAVE,
+            $this->storeMock
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(VersionedDataProvider::class, $this->prepareMockedInstance());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanSelfBuild(): void
+    {
+        $this->assertInstanceOf(
+            VersionedDataProvider::class,
+            VersionedDataProvider::build($this->moduleConfigurationMock, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupWhenTrue(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(true);
+        $this->assertTrue($this->prepareMockedInstance()->needsSetup());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testNeedsSetupWhenFalse(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(false);
+        $this->assertFalse($this->prepareMockedInstance()->needsSetup());
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testRunSetupLogsWarningWhenNotNeeded(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(false);
+        $this->loggerMock->expects($this->once())->method('warning');
+        $this->prepareMockedInstance()->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    public function testCanRunSetupWhenNeeded(): void
+    {
+        $this->storeMock->method('needsSetup')->willReturn(true);
+        $this->storeMock->expects($this->once())->method('runSetup');
+        $this->prepareMockedInstance()->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanGetConnectedServices(): void
+    {
+        $this->storeMock->expects($this->once())->method('getConnectedServices')->with('userId');
+        $this->storeMock->method('getConnectedServices')->willReturn($this->connectedServicesBagMock);
+
+        $this->assertInstanceOf(Bag::class, $this->prepareMockedInstance()->getConnectedServices('userId'));
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanGetTracker(): void
+    {
+        $this->assertInstanceOf(
+            VersionedDataProvider::class,
+            $this->prepareMockedInstance()->getTracker()
+        );
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c8e44504077a184ba63f95cee44a5d926442d852
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations;
+
+// phpcs:ignore
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000700CreateAuthenticationEventTable;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000700CreateAuthenticationEventTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ */
+class Version20220801000700CreateAuthenticationEventTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'cds_authentication_event';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     * @throws Exception
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new Version20220801000700CreateAuthenticationEventTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsMigrationException(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')->willReturn($this->tableName);
+        $this->schemaManagerStub->method('createTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new Version20220801000700CreateAuthenticationEventTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRevertThrowsMigrationException(): void
+    {
+        $this->schemaManagerStub->method('dropTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new Version20220801000700CreateAuthenticationEventTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->revert();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsOnIvalidTableNameIdp(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')
+            ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table
+
+        $migration = new Version20220801000700CreateAuthenticationEventTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/RepositoryTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/RepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..aa869d6d1ed95dda28a010c0290e635c05a79465
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/Store/RepositoryTest.php
@@ -0,0 +1,289 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+
+use DateInterval;
+use DateTimeImmutable;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Repository;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
+use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Test\Module\accounting\Constants\DateTime;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedBaseTableConstants;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000700CreateAuthenticationEventTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem
+ * @uses \SimpleSAML\Module\accounting\ModuleConfiguration
+ * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
+ */
+class RepositoryTest extends TestCase
+{
+    protected Connection $connection;
+    protected Stub $loggerStub;
+    protected Migrator $migrator;
+    protected string $dateTimeFormat;
+    protected string $idpEntityId;
+    protected string $idpEntityIdHash;
+    protected string $idpMetadata;
+    protected string $idpMetadataHash;
+    protected string $spEntityId;
+    protected string $spMetadataHash;
+    protected string $userIdentifier;
+    protected string $userIdentifierHash;
+    protected string $userAttributes;
+    protected string $userAttributesHash;
+    protected Repository $repository;
+    protected DateTimeImmutable $createdAt;
+    protected Stub $connectionStub;
+    protected string $spEntityIdHash;
+    protected string $spMetadata;
+    protected string $clientIpAddress;
+    protected string $authenticationProtocolDesignation;
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    protected function setUp(): void
+    {
+        // For stubbing.
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->loggerStub = $this->createStub(LoggerInterface::class);
+
+        // For real DB testing.
+        $connectionParameters = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->connection = new Connection($connectionParameters);
+        $this->migrator = new Migrator($this->connection, $this->loggerStub);
+        $moduleConfiguration = new ModuleConfiguration();
+        $migrationsDirectory = $moduleConfiguration->getModuleSourceDirectory() . DIRECTORY_SEPARATOR .
+            'Data' . DIRECTORY_SEPARATOR .
+            'Stores' . DIRECTORY_SEPARATOR .
+            'Accounting' . DIRECTORY_SEPARATOR .
+            'Activity' . DIRECTORY_SEPARATOR .
+            'DoctrineDbal' . DIRECTORY_SEPARATOR .
+            'Current' . DIRECTORY_SEPARATOR .
+            'Store' . DIRECTORY_SEPARATOR .
+            AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME;
+        $namespace = Store::class . '\\' . AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME;
+
+        $this->migrator->runSetup();
+        $this->migrator->runNonImplementedMigrationClasses($migrationsDirectory, $namespace);
+
+        $this->repository = new Repository($this->connection, $this->loggerStub);
+
+        $this->dateTimeFormat = DateTime::DEFAULT_FORMAT;
+
+        $this->idpEntityId = 'idp-entity-id';
+        $this->idpEntityIdHash = 'idp-entity-id-hash';
+
+        $this->idpMetadata = 'idp-metadata';
+        $this->idpMetadataHash = 'idp-metadata-hash';
+
+        $this->spEntityId = 'sp-entity-id';
+        $this->spEntityIdHash = 'sp-entity-id-hash';
+
+        $this->spMetadata = 'sp-metadata';
+        $this->spMetadataHash = 'sp-metadata-hash';
+
+        $this->userIdentifier = 'user-identifier';
+        $this->userIdentifierHash = 'user-identifier-hash';
+
+        $this->userAttributes = 'user-attributes';
+        $this->userAttributesHash = 'user-attributes-hash';
+
+        $this->createdAt = new DateTimeImmutable();
+        $this->clientIpAddress = '123.123.123.123';
+        $this->authenticationProtocolDesignation = Saml2::DESIGNATION;
+    }
+
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(
+            Repository::class,
+            new Repository($this->connection, $this->loggerStub)
+        );
+    }
+
+    public function testInsertAuthenticationEventThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->insertAuthenticationEvent(1, 1, $this->createdAt);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanGetActivity(): void
+    {
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spEntityIdHash,
+            $this->createdAt
+        );
+        $spResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $userResult = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+        $userId = (int)$userResult[VersionedBaseTableConstants::TABLE_USER_COLUMN_NAME_ID];
+        $this->repository
+            ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt);
+        $userVersionResult = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative();
+
+        $userVersionId = (int)$userVersionResult[VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+
+        $this->repository->insertAuthenticationEvent(
+            $spId,
+            $userVersionId,
+            $this->createdAt,
+            $this->clientIpAddress,
+            $this->authenticationProtocolDesignation,
+            $this->createdAt
+        );
+
+        $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0);
+        $this->assertCount(1, $resultArray);
+
+        $this->repository->insertAuthenticationEvent(
+            $spId,
+            $userVersionId,
+            $this->createdAt,
+            $this->clientIpAddress,
+            $this->authenticationProtocolDesignation,
+            $this->createdAt
+        );
+        $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0);
+        $this->assertCount(2, $resultArray);
+
+        $this->repository->insertAuthenticationEvent(
+            $spId,
+            $userVersionId,
+            $this->createdAt,
+            $this->clientIpAddress,
+            $this->authenticationProtocolDesignation,
+            $this->createdAt
+        );
+        $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0);
+        $this->assertCount(3, $resultArray);
+
+        // Simulate another SP
+        $spEntityIdNew = $this->spEntityId . '-new';
+        $spEntityIdHashNew = $this->spEntityIdHash . '-new';
+        $spMetadataNew = $this->spMetadata . '-new';
+        $spMetadataHashNew = $this->spMetadataHash . '-new';
+        $this->repository->insertSp(
+            $spEntityIdNew,
+            $spEntityIdHashNew,
+            $spMetadataNew,
+            $spMetadataHashNew,
+            $this->createdAt
+        );
+        $spResult = $this->repository->getSp($spEntityIdHashNew)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+
+        $this->repository->insertAuthenticationEvent(
+            $spId,
+            $userVersionId,
+            $this->createdAt,
+            $this->clientIpAddress,
+            $this->authenticationProtocolDesignation,
+            $this->createdAt
+        );
+        $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0);
+        $this->assertCount(4, $resultArray);
+
+        // Simulate a change in user attributes
+    }
+
+    public function testGetActivityThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getActivity($this->userIdentifierHash, 10, 0);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanDeleteAuthenticationEventsOlderThan(): void
+    {
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+        $spResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $userResult = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+        $userId = (int)$userResult[VersionedBaseTableConstants::TABLE_USER_COLUMN_NAME_ID];
+        $this->repository
+            ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt);
+        $userVersionResult = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative();
+        $userVersionId = (int)$userVersionResult[VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+        $this->repository->insertAuthenticationEvent(
+            $spId,
+            $userVersionId,
+            $this->createdAt,
+            $this->clientIpAddress,
+            $this->authenticationProtocolDesignation,
+            $this->createdAt
+        );
+
+        $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0);
+        $this->assertCount(1, $resultArray);
+
+        $dateTimeInFuture = $this->createdAt->add(new DateInterval('P1D'));
+
+        $this->repository->deleteAuthenticationEventsOlderThan($dateTimeInFuture);
+
+        $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0);
+        $this->assertCount(0, $resultArray);
+    }
+
+    public function testDeleteAuthenticationEventsOlderThanThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->deleteAuthenticationEventsOlderThan(new DateTimeImmutable());
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/StoreTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/StoreTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..65c81230fa50573a7bb8cd8bea1442139b4793b3
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Current/StoreTest.php
@@ -0,0 +1,325 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current;
+
+use DateTimeImmutable;
+use Doctrine\DBAL\Result;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\EntityTableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Test\Module\accounting\Constants\RawRowResult;
+use SimpleSAML\Test\Module\accounting\Constants\StateArrays;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event\State;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedTableConstants;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event
+ * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractState
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\State\Saml2
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000200CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000400CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000500CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000500CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Migrations\Version20220801000700CreateAuthenticationEventTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem
+ * @uses \SimpleSAML\Module\accounting\Helpers\Hash
+ * @uses \SimpleSAML\Module\accounting\Helpers\Arr
+ * @uses \SimpleSAML\Module\accounting\Helpers\Network
+ * @uses \SimpleSAML\Module\accounting\Entities\ConnectedService\Bag
+ * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider
+ * @uses \SimpleSAML\Module\accounting\Entities\ConnectedService
+ * @uses \SimpleSAML\Module\accounting\Entities\User
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity
+ * @uses \SimpleSAML\Module\accounting\Entities\Activity\Bag
+ * @uses \SimpleSAML\Module\accounting\Entities\Activity
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\RawActivity
+ * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Entities\Providers\Service\Saml2
+ * @uses \SimpleSAML\Module\accounting\Helpers\ProviderResolver
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2
+ */
+class StoreTest extends TestCase
+{
+    protected Stub $moduleConfigurationStub;
+    protected Migrator $migrator;
+    protected Stub $factoryStub;
+    protected Connection $connection;
+    protected State\Saml2 $state;
+    protected Event $authenticationEvent;
+    protected HashDecoratedState $hashDecoratedState;
+    /**
+     * @var MockObject
+     */
+    protected $repositoryMock;
+    /**
+     * @var Stub
+     */
+    protected $resultStub;
+    /**
+     * @var MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var MockObject
+     */
+    protected $helpersManagerMock;
+
+    /**
+     * @throws StoreException
+     */
+    protected function setUp(): void
+    {
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class);
+        $this->moduleConfigurationStub->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+        $this->moduleConfigurationStub->method('getUserIdAttributeName')
+            ->willReturn('hrEduPersonPersistentID');
+
+        $this->connection = new Connection($connectionParams);
+
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+
+        $this->migrator = new Migrator($this->connection, $this->loggerMock);
+
+        $this->factoryStub = $this->createStub(Factory::class);
+        $this->factoryStub->method('buildConnection')->willReturn($this->connection);
+        $this->factoryStub->method('buildMigrator')->willReturn($this->migrator);
+
+        $this->state = new State\Saml2(StateArrays::SAML2_FULL);
+        $this->authenticationEvent = new Event($this->state);
+
+        $this->hashDecoratedState = new HashDecoratedState($this->state);
+        $this->repositoryMock = $this->createMock(
+            Store\Repository::class
+        );
+
+        $this->resultStub = $this->createStub(Result::class);
+        $this->helpersManagerMock = $this->createMock(HelpersManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanConstructInstance(): void
+    {
+        $this->assertInstanceOf(
+            Store::class,
+            new Store(
+                $this->moduleConfigurationStub,
+                $this->loggerMock,
+                null,
+                ModuleConfiguration\ConnectionType::MASTER,
+                $this->factoryStub
+            )
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanBuildInstance(): void
+    {
+        $this->assertInstanceOf(
+            Store::class,
+            Store::build($this->moduleConfigurationStub, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     * @throws MigrationException
+     */
+    public function testCanPersistAuthenticationEvent(): void
+    {
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub
+        );
+        $store->runSetup();
+
+        $spCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $userCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $userVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $authenticationEventCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+        $spCountQueryBuilder->select('COUNT(id) as spCount')->from(
+        //'vds_sp'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_SP
+            )
+        );
+        $userCountQueryBuilder->select('COUNT(id) as userCount')->from(
+        //'vds_user'
+            $this->connection->preparePrefixedTableName(
+                VersionedTableConstants::TABLE_PREFIX . VersionedTableConstants::TABLE_NAME_USER
+            )
+        );
+        $userVersionCountQueryBuilder->select('COUNT(id) as userVersionCount')->from(
+        //'vds_user_version'
+            $this->connection->preparePrefixedTableName(
+                VersionedTableConstants::TABLE_PREFIX . VersionedTableConstants::TABLE_NAME_USER_VERSION
+            )
+        );
+        $authenticationEventCountQueryBuilder->select('COUNT(id) as authenticationEventCount')
+            ->from(
+            //'vds_authentication_event'
+                $this->connection->preparePrefixedTableName(
+                    BaseTableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_AUTHENTICATION_EVENT
+                )
+            );
+
+        $this->assertSame(0, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne());
+
+        $store->persist($this->authenticationEvent);
+
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne());
+
+        $store->persist($this->authenticationEvent);
+
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(2, (int)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetActivityReturnsEmptyBagIfNoResults(): void
+    {
+        $this->repositoryMock->method('getActivity')->willReturn([]);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $activityBag = $store->getActivity('test', 10, 0);
+
+        $this->assertEmpty($activityBag->getAll());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanGetActivityBag(): void
+    {
+        $this->repositoryMock->method('getActivity')
+            ->willReturn([RawRowResult::ACTIVITY]);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $activityBag = $store->getActivity('test', 10, 0);
+
+        $this->assertNotEmpty($activityBag->getAll());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetActivityThrowsForInvalidResult(): void
+    {
+        $rawResult = RawRowResult::ACTIVITY;
+        unset($rawResult[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]);
+
+        $this->repositoryMock->method('getActivity')
+            ->willReturn([$rawResult]);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $this->expectException(StoreException::class);
+        $store->getActivity('test', 10, 0);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanDeleteDataOlderThan(): void
+    {
+        $dateTime = new DateTimeImmutable();
+
+        $this->repositoryMock->expects($this->once())
+            ->method('deleteAuthenticationEventsOlderThan')
+            ->with($dateTime);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $store->deleteDataOlderThan($dateTime);
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivityTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/RawActivityTest.php
old mode 100644
new mode 100755
similarity index 75%
rename from tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivityTest.php
rename to tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/RawActivityTest.php
index 73f332287d4c4a83a5b22bb8fb6f7394a86a4896..a406d53fb62530be7d62ab23938aa4e87e81a774
--- a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivityTest.php
+++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/RawActivityTest.php
@@ -2,20 +2,21 @@
 
 declare(strict_types=1);
 
-namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store;
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal;
 
 use DateTimeImmutable;
 use Doctrine\DBAL\Platforms\AbstractPlatform;
 use PHPUnit\Framework\MockObject\Stub;
 use PHPUnit\Framework\TestCase;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\RawActivity;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\RawActivity;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\TableConstants;
 use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2;
 use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
 use SimpleSAML\Test\Module\accounting\Constants\DateTime;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\EntityTableConstants;
 
 /**
- * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\RawActivity
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\RawActivity
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity
  */
 class RawActivityTest extends TestCase
@@ -47,15 +48,16 @@ class RawActivityTest extends TestCase
         $this->authenticationProtocolDesignation = Saml2::DESIGNATION;
 
         $this->rawRow = [
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA => serialize($this->serviceProviderMetadata),
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES => serialize($this->userAttributes),
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT => $this->happenedAt,
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS => $this->clientIpAddress,
-            TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =>
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA => serialize($this->serviceProviderMetadata),
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES => serialize($this->userAttributes),
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT => $this->happenedAt,
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS => $this->clientIpAddress,
+            EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION =>
                 $this->authenticationProtocolDesignation,
         ];
         $this->abstractPlatformStub = $this->createStub(AbstractPlatform::class);
-        $this->abstractPlatformStub->method('getDateTimeFormatString')->willReturn(DateTime::DEFAULT_FORMAT);
+        $this->abstractPlatformStub->method('getDateTimeFormatString')
+            ->willReturn(DateTime::DEFAULT_FORMAT);
     }
 
     public function testCanCreateInstance(): void
@@ -85,7 +87,7 @@ class RawActivityTest extends TestCase
     public function testIpAddressCanBeNull(): void
     {
         $rawRow = $this->rawRow;
-        unset($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS]);
+        unset($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS]);
 
         $rawActivity = new RawActivity($rawRow, $this->abstractPlatformStub);
         $this->assertNull($rawActivity->getClientIpAddress());
@@ -94,7 +96,7 @@ class RawActivityTest extends TestCase
     public function testAuthenticationProtocolDesignationCanBeNull(): void
     {
         $rawRow = $this->rawRow;
-        unset($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION]);
+        unset($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION]);
 
         $rawActivity = new RawActivity($rawRow, $this->abstractPlatformStub);
         $this->assertNull($rawActivity->getAuthenticationProtocolDesignation());
@@ -103,7 +105,7 @@ class RawActivityTest extends TestCase
     public function testThrowsIfColumnNotPresent(): void
     {
         $rawRow = $this->rawRow;
-        unset($rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]);
+        unset($rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]);
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -113,7 +115,7 @@ class RawActivityTest extends TestCase
     public function testThrowsForNonStringServiceProviderMetadata(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA] = 1;
+        $rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -123,7 +125,7 @@ class RawActivityTest extends TestCase
     public function testThrowsForNonStringUserAttributes(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES] = 1;
+        $rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -133,7 +135,7 @@ class RawActivityTest extends TestCase
     public function testThrowsForNonStringHappenedAt(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT] = 1;
+        $rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -143,7 +145,7 @@ class RawActivityTest extends TestCase
     public function testThrowsForInvalidServiceProviderMetadata(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA] = serialize(1);
+        $rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA] = serialize(1);
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -153,7 +155,7 @@ class RawActivityTest extends TestCase
     public function testThrowsForInvalidUserAttributes(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES] = serialize(1);
+        $rawRow[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES] = serialize(1);
 
         $this->expectException(UnexpectedValueException::class);
 
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RepositoryTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RepositoryTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/StoreTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/StoreTest.php
old mode 100644
new mode 100755
index d86650ef30a2ef7892dbd7f1d904350f8a359e0e..de5ba8527963d0d921cd9b92334a1bc4d70a2a67
--- a/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/StoreTest.php
+++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/StoreTest.php
@@ -13,8 +13,8 @@ use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\TableConstants;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants
-    as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as BaseTableConstants;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
@@ -28,6 +28,7 @@ use SimpleSAML\Module\accounting\Services\HelpersManager;
 use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
 use SimpleSAML\Test\Module\accounting\Constants\RawRowResult;
 use SimpleSAML\Test\Module\accounting\Constants\StateArrays;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\EntityTableConstants;
 
 /**
  * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store
@@ -71,7 +72,7 @@ use SimpleSAML\Test\Module\accounting\Constants\StateArrays;
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity
  * @uses \SimpleSAML\Module\accounting\Entities\Activity\Bag
  * @uses \SimpleSAML\Module\accounting\Entities\Activity
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\RawActivity
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\RawActivity
  * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
  * @uses \SimpleSAML\Module\accounting\Entities\Providers\Service\Saml2
@@ -371,7 +372,7 @@ class StoreTest extends TestCase
     public function testGetActivityThrowsForInvalidResult(): void
     {
         $rawResult = RawRowResult::ACTIVITY;
-        unset($rawResult[TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]);
+        unset($rawResult[EntityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT]);
 
         $this->repositoryMock->method('getActivity')
             ->willReturn([$rawResult]);
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8c5cb183b1c9b8b0ff49194db2ac89e42d60f2d
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTableTest.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateIdpTable;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateIdpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ */
+class CreateIdpTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    protected function setUp(): void
+    {
+
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'cds_idp';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws Exception
+     * @throws MigrationException
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new CreateIdpTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsMigrationException(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')->willReturn($this->tableName);
+        $this->schemaManagerStub->method('createTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new CreateIdpTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRevertThrowsMigrationException(): void
+    {
+        $this->schemaManagerStub->method('dropTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new CreateIdpTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->revert();
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7993200725d2bc5e66d549caee2c5cf1e550c31d
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTableTest.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ */
+class CreateSpTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'cds_sp';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws Exception
+     * @throws MigrationException
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new CreateSpTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsMigrationException(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')->willReturn($this->tableName);
+        $this->schemaManagerStub->method('createTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new CreateSpTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRevertThrowsMigrationException(): void
+    {
+        $this->schemaManagerStub->method('dropTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new CreateSpTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->revert();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsOnIvalidTableNameIdp(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')
+            ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table
+
+        $migration = new CreateSpTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b42a7bd8361d1620f6d2a4ac63385abe101051af
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTableTest.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ */
+class CreateUserTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'vds_user';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     * @throws Exception
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new CreateUserTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f34201a55ae2ed91c750373726e2c32ced253e74
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTableTest.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ */
+class CreateUserVersionTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'vds_user_version';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     * @throws Exception
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new Migrations\CreateUserVersionTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/RepositoryTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/RepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7db77150a1b15c280d8423e3a4c4367c738d2819
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/RepositoryTest.php
@@ -0,0 +1,390 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store;
+
+use DateTimeImmutable;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedTableConstants;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
+use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Test\Module\accounting\Constants\DateTime;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem
+ * @uses \SimpleSAML\Module\accounting\ModuleConfiguration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateIdpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ */
+class RepositoryTest extends TestCase
+{
+    protected Connection $connection;
+    protected \Doctrine\DBAL\Connection $dbal;
+    protected Stub $loggerStub;
+    protected Migrator $migrator;
+    protected string $dateTimeFormat;
+    protected string $idpEntityId;
+    protected string $idpEntityIdHash;
+    protected string $idpMetadata;
+    protected string $idpMetadataHash;
+    protected string $spEntityId;
+    protected string $spMetadataHash;
+    protected string $userIdentifier;
+    protected string $userIdentifierHash;
+    protected string $userAttributes;
+    protected string $userAttributesHash;
+    protected Repository $repository;
+    protected DateTimeImmutable $createdAt;
+    protected Stub $connectionStub;
+    protected string $spEntityIdHash;
+    protected string $spMetadata;
+    protected string $clientIpAddress;
+    protected string $authenticationProtocolDesignation;
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    protected function setUp(): void
+    {
+        // For stubbing.
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->loggerStub = $this->createStub(LoggerInterface::class);
+
+        // For real DB testing.
+        $connectionParameters = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->connection = new Connection($connectionParameters);
+        $this->migrator = new Migrator($this->connection, $this->loggerStub);
+        $moduleConfiguration = new ModuleConfiguration();
+        $migrationsDirectory = $moduleConfiguration->getModuleSourceDirectory() . DIRECTORY_SEPARATOR .
+            'Data' . DIRECTORY_SEPARATOR .
+            'Stores' . DIRECTORY_SEPARATOR .
+            'Accounting' . DIRECTORY_SEPARATOR .
+            'Bases' . DIRECTORY_SEPARATOR .
+            'DoctrineDbal' . DIRECTORY_SEPARATOR .
+            'Current' . DIRECTORY_SEPARATOR .
+            'Store' . DIRECTORY_SEPARATOR .
+            AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME;
+        $namespace = Store::class . '\\' . AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME;
+
+        $this->migrator->runSetup();
+        $this->migrator->runNonImplementedMigrationClasses($migrationsDirectory, $namespace);
+
+        $this->repository = new Repository($this->connection, $this->loggerStub);
+
+        $this->dateTimeFormat = DateTime::DEFAULT_FORMAT;
+
+        $this->idpEntityId = 'idp-entity-id';
+        $this->idpEntityIdHash = 'idp-entity-id-hash';
+
+        $this->idpMetadata = 'idp-metadata';
+        $this->idpMetadataHash = 'idp-metadata-hash';
+
+        $this->spEntityId = 'sp-entity-id';
+        $this->spEntityIdHash = 'sp-entity-id-hash';
+
+        $this->spMetadata = 'sp-metadata';
+        $this->spMetadataHash = 'sp-metadata-hash';
+
+        $this->userIdentifier = 'user-identifier';
+        $this->userIdentifierHash = 'user-identifier-hash';
+
+        $this->userAttributes = 'user-attributes';
+        $this->userAttributesHash = 'user-attributes-hash';
+
+        $this->createdAt = new DateTimeImmutable();
+        $this->clientIpAddress = '123.123.123.123';
+        $this->authenticationProtocolDesignation = Saml2::DESIGNATION;
+    }
+
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(
+            Repository::class,
+            new Repository($this->connection, $this->loggerStub)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanInsertAndGetIdp(): array
+    {
+        $this->repository->insertIdp(
+            $this->idpEntityId,
+            $this->idpEntityIdHash,
+            $this->idpMetadata,
+            $this->idpMetadataHash,
+            $this->createdAt
+        );
+
+        $result = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative();
+
+        $this->assertSame($this->idpEntityId, $result[TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID]);
+        $this->assertSame(
+            $this->idpEntityIdHash,
+            $result[TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256]
+        );
+        $this->assertSame($this->idpMetadata, $result[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA]);
+        $this->assertSame($this->idpMetadataHash, $result[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256]);
+        $this->assertSame(
+            $this->createdAt->format($this->dateTimeFormat),
+            $result[TableConstants::TABLE_IDP_COLUMN_NAME_CREATED_AT]
+        );
+
+        return $result;
+    }
+
+    public function testInsertIdpThrowsOnNonUniqueIdpEntityIdHash(): void
+    {
+        $this->expectException(StoreException::class);
+
+        // Can't insert duplicate idp entity ID hash.
+        $this->repository->insertIdp(
+            $this->idpEntityId,
+            $this->idpEntityIdHash,
+            $this->idpMetadata,
+            $this->idpMetadataHash,
+            $this->createdAt
+        );
+        $this->repository->insertIdp(
+            $this->idpEntityId,
+            $this->idpEntityIdHash,
+            $this->idpMetadata,
+            $this->idpMetadataHash,
+            $this->createdAt
+        );
+    }
+
+    public function testGetIdpThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getIdp($this->idpEntityIdHash);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanUpdateIdp(): void
+    {
+        $this->repository->insertIdp(
+            $this->idpEntityId,
+            $this->idpEntityIdHash,
+            $this->idpMetadata,
+            $this->idpMetadataHash,
+            $this->createdAt
+        );
+
+        $result = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative();
+        $this->assertSame($this->idpMetadata, $result[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA]);
+
+        $this->repository->updateIdp(
+            $result[TableConstants::TABLE_IDP_COLUMN_NAME_ID],
+            'new-idp-metadata',
+            'new-idp-metadata-hash'
+        );
+
+        $newResult = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative();
+        $this->assertSame('new-idp-metadata', $newResult[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA]);
+        $this->assertSame(
+            'new-idp-metadata-hash',
+            $newResult[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256]
+        );
+
+        $this->assertNotSame(
+            $result[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA],
+            $newResult[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA]
+        );
+        $this->assertNotSame(
+            $this->idpMetadata,
+            $newResult[TableConstants::TABLE_IDP_COLUMN_NAME_METADATA]
+        );
+    }
+
+    public function testUpdateIdpThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->updateIdp(
+            1,
+            'new-idp-metadata',
+            'new-idp-metadata-hash'
+        );
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanInsertAndGetSp(): array
+    {
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+
+        $result = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+
+        $this->assertSame($this->spEntityId, $result[TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID]);
+        $this->assertSame(
+            $this->spEntityIdHash,
+            $result[TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256]
+        );
+        $this->assertSame($this->spMetadata, $result[TableConstants::TABLE_SP_COLUMN_NAME_METADATA]);
+        $this->assertSame($this->spMetadataHash, $result[TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256]);
+        $this->assertSame(
+            $this->createdAt->format($this->dateTimeFormat),
+            $result[TableConstants::TABLE_SP_COLUMN_NAME_CREATED_AT]
+        );
+
+        return $result;
+    }
+
+    public function testInsertSpThrowsOnNonUniqueSpEntityIdHash(): void
+    {
+        $this->expectException(StoreException::class);
+        // SP Entity ID Hash must be unique.
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+    }
+
+    public function testGetSpThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getSp($this->spEntityIdHash);
+    }
+
+    public function testCanUpdateSp(): void
+    {
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+
+        $result = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $this->assertSame($this->spMetadata, $result[TableConstants::TABLE_SP_COLUMN_NAME_METADATA]);
+
+        $this->repository->updateSp(
+            $result[TableConstants::TABLE_SP_COLUMN_NAME_ID],
+            'new-sp-metadata',
+            'new-sp-metadata-hash'
+        );
+
+        $newResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $this->assertSame('new-sp-metadata', $newResult[TableConstants::TABLE_SP_COLUMN_NAME_METADATA]);
+        $this->assertSame(
+            'new-sp-metadata-hash',
+            $newResult[TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256]
+        );
+
+        $this->assertNotSame(
+            $result[TableConstants::TABLE_SP_COLUMN_NAME_METADATA],
+            $newResult[TableConstants::TABLE_SP_COLUMN_NAME_METADATA]
+        );
+        $this->assertNotSame(
+            $this->spMetadata,
+            $newResult[TableConstants::TABLE_SP_COLUMN_NAME_METADATA]
+        );
+    }
+
+    public function testUpdateSpThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->updateSp(
+            1,
+            'new-sp-metadata',
+            'new-sp-metadata-hash'
+        );
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanInsertAndGetUser(): array
+    {
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+
+        $result = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+
+        $this->assertSame($this->userIdentifier, $result[VersionedTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER]);
+        $this->assertSame(
+            $this->userIdentifierHash,
+            $result[VersionedTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256]
+        );
+        $this->assertSame(
+            $this->createdAt->format($this->dateTimeFormat),
+            $result[VersionedTableConstants::TABLE_USER_COLUMN_NAME_CREATED_AT]
+        );
+
+        return $result;
+    }
+
+    public function testInsertUserThrowsOnNonUniqueIdentifierHash(): void
+    {
+        $this->expectException(StoreException::class);
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+    }
+
+    public function testGetUserThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getUser($this->userIdentifierHash);
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/StoreTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/StoreTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8200383dffd5aa09d0391adc6c525d3c15ff5404
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/StoreTest.php
@@ -0,0 +1,226 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current;
+
+use Doctrine\DBAL\Result;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Test\Module\accounting\Constants\StateArrays;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event\State;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\State\Saml2
+ * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractState
+ * @uses \SimpleSAML\Module\accounting\Helpers\Arr
+ * @uses \SimpleSAML\Module\accounting\Helpers\Hash
+ * @uses \SimpleSAML\Module\accounting\Helpers\Network
+ * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
+ * @uses \SimpleSAML\Module\accounting\Traits\HasUserAttributesTrait
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateIdpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem
+ */
+class StoreTest extends TestCase
+{
+    protected Stub $moduleConfigurationStub;
+    protected Migrator $migrator;
+    protected Stub $factoryStub;
+    protected Connection $connection;
+    protected State\Saml2 $state;
+    protected Event $authenticationEvent;
+    protected HashDecoratedState $hashDecoratedState;
+    protected MockObject $repositoryMock;
+    protected Stub $resultStub;
+    protected MockObject $loggerMock;
+    protected MockObject $helpersManagerMock;
+
+    /**
+     * @throws StoreException
+     */
+    protected function setUp(): void
+    {
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class);
+        $this->moduleConfigurationStub->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+        $this->moduleConfigurationStub->method('getUserIdAttributeName')
+            ->willReturn('hrEduPersonPersistentID');
+
+        $this->connection = new Connection($connectionParams);
+
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+
+        $this->migrator = new Migrator($this->connection, $this->loggerMock);
+
+        $this->factoryStub = $this->createStub(Factory::class);
+        $this->factoryStub->method('buildConnection')->willReturn($this->connection);
+        $this->factoryStub->method('buildMigrator')->willReturn($this->migrator);
+
+        $this->state = new State\Saml2(StateArrays::SAML2_FULL);
+        $this->authenticationEvent = new Event($this->state);
+
+        $this->hashDecoratedState = new HashDecoratedState($this->state);
+        $this->repositoryMock = $this->createMock(Repository::class);
+
+        $this->resultStub = $this->createStub(Result::class);
+        $this->helpersManagerMock = $this->createMock(HelpersManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanConstructInstance(): void
+    {
+        $this->assertInstanceOf(
+            Store::class,
+            new Store(
+                $this->moduleConfigurationStub,
+                $this->loggerMock,
+                null,
+                ModuleConfiguration\ConnectionType::MASTER,
+                $this->factoryStub
+            )
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanBuildInstance(): void
+    {
+        $this->assertInstanceOf(
+            Store::class,
+            Store::build($this->moduleConfigurationStub, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     * @throws MigrationException
+     */
+    public function testCanResolveSpId(): void
+    {
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub
+        );
+        $store->runSetup();
+
+        $spCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+        $spCountQueryBuilder->select('COUNT(id) as spCount')->from(
+            $this->connection->preparePrefixedTableName(
+                TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_SP
+            )
+        );
+
+        $this->assertSame(0, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+
+        $spId = $store->resolveSpId($this->hashDecoratedState);
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+
+        $newSpId = $store->resolveSpId($this->hashDecoratedState);
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame($spId, $newSpId);
+
+        // Test updated metadata
+        $updatedStateArray = StateArrays::SAML2_FULL;
+        $updatedStateArray['SPMetadata']['name'] = 'Updated name';
+        $updatedState = new State\Saml2($updatedStateArray);
+        $updatedHasDecoratedState = new HashDecoratedState($updatedState);
+        $newSpId = $store->resolveSpId($updatedHasDecoratedState);
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame($spId, $newSpId);
+
+        // Test new SP
+        $newStateArray = StateArrays::SAML2_FULL;
+        $newStateArray['SPMetadata']['entityid'] = 'new-entity-id';
+        $newState = new State\Saml2($newStateArray);
+        $newHasDecoratedState = new HashDecoratedState($newState);
+        $newSpId = $store->resolveSpId($newHasDecoratedState);
+        $this->assertSame(2, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertNotSame($spId, $newSpId);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testResolveSpIdThrowsOnFirstGetSpFailure(): void
+    {
+        $this->repositoryMock->method('getSp')->willThrowException(new Exception('test'));
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $this->expectException(StoreException::class);
+
+        $store->resolveSpId($this->hashDecoratedState);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testResolveSpIdThrowsOnInsertAndLogsWarning(): void
+    {
+        $this->resultStub->method('fetchAssociative')->willReturn(false);
+        $this->resultStub->method('fetchOne')->willReturn(false);
+        $this->repositoryMock->method('getSp')->willReturn($this->resultStub);
+        $this->repositoryMock->method('insertSp')->willThrowException(new Exception('test'));
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $this->expectException(StoreException::class);
+        $this->loggerMock->expects($this->once())->method('warning');
+
+        $store->resolveSpId($this->hashDecoratedState);
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php
old mode 100644
new mode 100755
index cba763ee83600ed71f2ea590c97f92b932f71903..87e694e8ff5527c3da2f99d4f93a835007a48b59
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php
@@ -53,6 +53,8 @@ class CreateIdpSpUserVersionTableTest extends TestCase
         $migration = new Migrations\CreateIdpSpUserVersionTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php
old mode 100644
new mode 100755
index 41792f86f6861354ed66e07c1ac2a551dc0b77f3..baa3fffe6f726ccc9a5cdc9b1def1b5193bae189
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php
@@ -53,6 +53,8 @@ class CreateIdpTableTest extends TestCase
         $migration = new CreateIdpTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php
old mode 100644
new mode 100755
index cc8c07955ca5c3cf20baf32023b0d63a33e729ef..a135b62e95f30bbe9fa5f673cea8c80457502f04
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php
@@ -53,6 +53,8 @@ class CreateIdpVersionTableTest extends TestCase
         $migration = new Migrations\CreateIdpVersionTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php
old mode 100644
new mode 100755
index a388427bcce0c087d1475f9f9e7edf861c9565a5..05d9c7dda6f241561d8b705f4f6e2fe308d6dae0
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php
@@ -53,6 +53,8 @@ class CreateSpTableTest extends TestCase
         $migration = new CreateSpTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php
old mode 100644
new mode 100755
index 44e4151b76452401805973642318f4247631f528..2bf6767580b1db4efdfdb19fc326df5565fd07ed
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php
@@ -53,6 +53,8 @@ class CreateSpVersionTableTest extends TestCase
         $migration = new Migrations\CreateSpVersionTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php
old mode 100644
new mode 100755
index 786ff8bf0450b059855cd3929d23bbc755e734ae..4d83671ac55d4a230c663e7935662822e553a5a5
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php
@@ -15,8 +15,7 @@ use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
 
 /**
- * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable
- * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
  */
@@ -54,6 +53,8 @@ class CreateUserTableTest extends TestCase
         $migration = new CreateUserTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php
old mode 100644
new mode 100755
index a1515049e9d08b38455305ffae22c037609f9b56..feef85d439e818bcb3dd297e27c7633b0d813f72
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php
@@ -53,6 +53,8 @@ class CreateUserVersionTableTest extends TestCase
         $migration = new Migrations\CreateUserVersionTable($this->connection);
         $migration->run();
         $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
         $migration->revert();
         $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
     }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/RepositoryTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/RepositoryTest.php
old mode 100644
new mode 100755
index 2007a264ba492429d3c84296034793adfe1f0adb..91edd1c4bb0fd0bf17904b41bc7c5c86ef847586
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/RepositoryTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/RepositoryTest.php
@@ -391,4 +391,98 @@ class RepositoryTest extends TestCase
 
         $repository->getUserVersion(1, $this->userIdentifierHash);
     }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanInsertAndGetIdpSpUserVersion(): void
+    {
+        $this->repository->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $this->createdAt);
+        $idpResult = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative();
+        $idpId = (int)$idpResult[BaseTableConstants::TABLE_IDP_COLUMN_NAME_ID];
+        $this->repository->insertIdpVersion($idpId, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt);
+        $idpVersionResult = $this->repository->getIdpVersion($idpId, $this->idpMetadataHash)->fetchAssociative();
+
+        $this->repository->insertSp($this->spEntityId, $this->spEntityIdHash, $this->createdAt);
+        $spResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+        $this->repository->insertSpVersion($spId, $this->spMetadata, $this->spMetadataHash, $this->createdAt);
+        $spVersionResult = $this->repository->getSpVersion($spId, $this->spMetadataHash)->fetchAssociative();
+
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $userResult = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+        $userId = (int)$userResult[BaseTableConstants::TABLE_USER_COLUMN_NAME_ID];
+        $this->repository
+            ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt);
+        $userVersionResult = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative();
+
+        $idpVersionId = (int)$idpVersionResult[BaseTableConstants::TABLE_IDP_VERSION_COLUMN_NAME_ID];
+        $spVersionId = (int)$spVersionResult[BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID];
+        $userVersionId = (int)$userVersionResult[BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+        $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId, $this->createdAt);
+        $idpSpUserVersionResult = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId)
+            ->fetchAssociative();
+
+        $this->assertSame(
+            $idpVersionId,
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID]
+        );
+        $this->assertSame(
+            $spVersionId,
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID]
+        );
+        $this->assertSame(
+            $userVersionId,
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID]
+        );
+
+        // Simulate another SP
+        $spEntityIdNew = $this->spEntityId . '-new';
+        $spEntityIdHashNew = $this->spEntityIdHash . '-new';
+        $spMetadataNew = $this->spMetadata . '-new';
+        $spMetadataHashNew = $this->spMetadataHash . '-new';
+        $this->repository->insertSp($spEntityIdNew, $spEntityIdHashNew, $this->createdAt);
+        $spResult = $this->repository->getSp($spEntityIdHashNew)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+        $this->repository->insertSpVersion($spId, $spMetadataNew, $spMetadataHashNew, $this->createdAt);
+        $spVersionResult = $this->repository->getSpVersion($spId, $spMetadataHashNew)->fetchAssociative();
+        $spVersionId = (int)$spVersionResult[BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID];
+
+        $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId, $this->createdAt);
+        $idpSpUserVersionResult = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId)
+            ->fetchAssociative();
+
+        $this->assertSame(
+            $idpVersionId,
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID]
+        );
+        $this->assertSame(
+            $spVersionId,
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID]
+        );
+        $this->assertSame(
+            $userVersionId,
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID]
+        );
+    }
+
+    public function testGetIdpSpUserVersionThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getIdpSpUserVersion(1, 1, 1);
+    }
+
+    public function testInsertIdpSpUserVersionThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->insertIdpSpUserVersion(1, 1, 1);
+    }
 }
diff --git a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php
old mode 100644
new mode 100755
index 10c9068482d3bea1fb5755285ead9c118cba32fa..281456c72d49e5ffa04de2e572c44898fc75da41
--- a/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php
+++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php
@@ -5,12 +5,12 @@ declare(strict_types=1);
 namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned;
 
 use Doctrine\DBAL\Result;
-use Exception;
 use PHPUnit\Framework\MockObject\MockObject;
 use PHPUnit\Framework\MockObject\Stub;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
@@ -18,6 +18,7 @@ use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event\State;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
@@ -113,9 +114,7 @@ class StoreTest extends TestCase
         $this->authenticationEvent = new Event($this->state);
 
         $this->hashDecoratedState = new HashDecoratedState($this->state);
-        $this->repositoryMock = $this->createMock(
-            \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository::class
-        );
+        $this->repositoryMock = $this->createMock(Repository::class);
 
         $this->resultStub = $this->createStub(Result::class);
         $this->helpersManagerMock = $this->createMock(HelpersManager::class);
@@ -170,6 +169,7 @@ class StoreTest extends TestCase
         $spCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
         $spVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
         $userCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $idpSpUserVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
         $userVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
 
         $idpCountQueryBuilder->select('COUNT(id) as idpCount')->from(
@@ -208,40 +208,52 @@ class StoreTest extends TestCase
                 TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_USER_VERSION
             )
         );
+        $idpSpUserVersionCountQueryBuilder->select('COUNT(id) as idpSpUserVersionCount')
+            ->from(
+            //'vds_idp_sp_user_version'
+                $this->connection->preparePrefixedTableName(
+                    TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_IDP_SP_USER_VERSION
+                )
+            );
 
         $this->assertSame(0, (int)$idpCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(0, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(0, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(0, (int)$spVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(0, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$idpSpUserVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(0, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
 
         $idpId = $store->resolveIdpId($this->hashDecoratedState);
-        $store->resolveIdpVersionId($idpId, $this->hashDecoratedState);
+        $idpVersionId = $store->resolveIdpVersionId($idpId, $this->hashDecoratedState);
         $spId = $store->resolveSpId($this->hashDecoratedState);
-        $store->resolveSpVersionId($spId, $this->hashDecoratedState);
+        $spVersionId = $store->resolveSpVersionId($spId, $this->hashDecoratedState);
         $userId = $store->resolveUserId($this->hashDecoratedState);
-        $store->resolveUserVersionId($userId, $this->hashDecoratedState);
+        $userVersionId = $store->resolveUserVersionId($userId, $this->hashDecoratedState);
+        $store->resolveIdpSpUserVersionId($idpVersionId, $spVersionId, $userVersionId);
 
         $this->assertSame(1, (int)$idpCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$spVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$idpSpUserVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
 
         $idpId = $store->resolveIdpId($this->hashDecoratedState);
-        $store->resolveIdpVersionId($idpId, $this->hashDecoratedState);
+        $idpVersionId = $store->resolveIdpVersionId($idpId, $this->hashDecoratedState);
         $spId = $store->resolveSpId($this->hashDecoratedState);
-        $store->resolveSpVersionId($spId, $this->hashDecoratedState);
+        $spVersionId = $store->resolveSpVersionId($spId, $this->hashDecoratedState);
         $userId = $store->resolveUserId($this->hashDecoratedState);
-        $store->resolveUserVersionId($userId, $this->hashDecoratedState);
+        $userVersionId = $store->resolveUserVersionId($userId, $this->hashDecoratedState);
+        $store->resolveIdpSpUserVersionId($idpVersionId, $spVersionId, $userVersionId);
 
         $this->assertSame(1, (int)$idpCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$spVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$idpSpUserVersionCountQueryBuilder->executeQuery()->fetchOne());
         $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
     }
 
@@ -549,4 +561,51 @@ class StoreTest extends TestCase
         $userId = $store->resolveUserId($this->hashDecoratedState);
         $store->resolveUserVersionId($userId, $this->hashDecoratedState);
     }
+
+    /**
+     * @throws StoreException
+     */
+    public function testResolveIdpSpUserVersionThrowsOnGet(): void
+    {
+        $this->repositoryMock->method('getIdpSpUserVersion')->willThrowException(new Exception('test'));
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $this->expectException(StoreException::class);
+
+        $store->resolveIdpSpUserVersionId(1, 1, 1);
+    }
+
+
+
+    /**
+     * @throws StoreException
+     */
+    public function testResolveIdpSpUserVersionLogsWarningAndThrowsOnFailure(): void
+    {
+        $this->resultStub->method('fetchOne')->willReturn(false);
+        $this->repositoryMock->method('getIdpSpUserVersion')->willReturn($this->resultStub);
+        $this->repositoryMock->method('insertIdpSpUserVersion')
+            ->willThrowException(new Exception('test'));
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $this->expectException(StoreException::class);
+        $this->loggerMock->expects($this->once())->method('warning');
+        $store->resolveIdpSpUserVersionId(1, 1, 1);
+    }
 }
diff --git a/tests/src/Data/Stores/Accounting/Bases/HashDecoratedStateTest.php b/tests/src/Data/Stores/Accounting/Bases/HashDecoratedStateTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTableTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..f33a81d470a68200dc620ab827b8bdebf1894eab
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTableTest.php
@@ -0,0 +1,102 @@
+<?php
+
+// phpcs:ignore
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations\Version20240505400CreateConnectedServiceTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ */
+class Version20240505400CreateConnectedServiceTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'cds_connected_service';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     * @throws Exception
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new Migrations\Version20240505400CreateConnectedServiceTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsMigrationException(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')->willReturn($this->tableName);
+        $this->schemaManagerStub->method('createTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new Migrations\Version20240505400CreateConnectedServiceTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRevertThrowsMigrationException(): void
+    {
+        $this->schemaManagerStub->method('dropTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new Migrations\Version20240505400CreateConnectedServiceTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->revert();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsOnIvalidTableName(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')
+            ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table
+
+        $migration = new Migrations\Version20240505400CreateConnectedServiceTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/RepositoryTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/RepositoryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b485d74b1292c303698ee9f15992fe166cbb93b
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/RepositoryTest.php
@@ -0,0 +1,363 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store;
+
+use DateInterval;
+use DateTimeImmutable;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Test\Module\accounting\Constants\DateTime;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedBaseTableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations\Version20240505400CreateConnectedServiceTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem
+ * @uses \SimpleSAML\Module\accounting\ModuleConfiguration
+ * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
+ *
+ */
+class RepositoryTest extends TestCase
+{
+    protected Connection $connection;
+    protected \Doctrine\DBAL\Connection $dbal;
+    protected Stub $loggerStub;
+    protected Migrator $migrator;
+    protected string $dateTimeFormat;
+    protected string $idpEntityId;
+    protected string $idpEntityIdHash;
+    protected string $idpMetadata;
+    protected string $idpMetadataHash;
+    protected string $spEntityId;
+    protected string $spMetadataHash;
+    protected string $userIdentifier;
+    protected string $userIdentifierHash;
+    protected string $userAttributes;
+    protected string $userAttributesHash;
+    protected Repository $repository;
+    protected DateTimeImmutable $createdAt;
+    protected Stub $connectionStub;
+    protected string $spEntityIdHash;
+    protected string $spMetadata;
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     */
+    protected function setUp(): void
+    {
+        // For stubbing.
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->loggerStub = $this->createStub(LoggerInterface::class);
+
+        // For real DB testing.
+        $connectionParameters = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->connection = new Connection($connectionParameters);
+        $this->migrator = new Migrator($this->connection, $this->loggerStub);
+        $moduleConfiguration = new ModuleConfiguration();
+        $migrationsDirectory = $moduleConfiguration->getModuleSourceDirectory() . DIRECTORY_SEPARATOR .
+            'Data' . DIRECTORY_SEPARATOR .
+            'Stores' . DIRECTORY_SEPARATOR .
+            'Accounting' . DIRECTORY_SEPARATOR .
+            'ConnectedServices' . DIRECTORY_SEPARATOR .
+            'DoctrineDbal' . DIRECTORY_SEPARATOR .
+            'Current' . DIRECTORY_SEPARATOR .
+            'Store' . DIRECTORY_SEPARATOR .
+            AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME;
+        $namespace = Store::class . '\\' . AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME;
+
+        $this->migrator->runSetup();
+        $this->migrator->runNonImplementedMigrationClasses($migrationsDirectory, $namespace);
+
+        $this->repository = new Repository($this->connection, $this->loggerStub);
+
+        $this->dateTimeFormat = DateTime::DEFAULT_FORMAT;
+
+        $this->idpEntityId = 'idp-entity-id';
+        $this->idpEntityIdHash = 'idp-entity-id-hash';
+
+        $this->idpMetadata = 'idp-metadata';
+        $this->idpMetadataHash = 'idp-metadata-hash';
+
+        $this->spEntityId = 'sp-entity-id';
+        $this->spEntityIdHash = 'sp-entity-id-hash';
+
+        $this->spMetadata = 'sp-metadata';
+        $this->spMetadataHash = 'sp-metadata-hash';
+
+        $this->userIdentifier = 'user-identifier';
+        $this->userIdentifierHash = 'user-identifier-hash';
+
+        $this->userAttributes = 'user-attributes';
+        $this->userAttributesHash = 'user-attributes-hash';
+
+        $this->createdAt = new DateTimeImmutable();
+    }
+
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(
+            Repository::class,
+            new Repository($this->connection, $this->loggerStub)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanGetConnectedServices(): void
+    {
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+        $spResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $userResult = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+        $userId = (int)$userResult[VersionedBaseTableConstants::TABLE_USER_COLUMN_NAME_ID];
+        $this->repository
+            ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt);
+        $userVersionResult = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative();
+        $userVersionId = (int)$userVersionResult[VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+        $this->assertCount(0, $resultArray);
+
+        $this->repository->insertConnectedService($spId, $userId, $userVersionId);
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+        $this->assertCount(1, $resultArray);
+
+        $this->assertEquals(
+            '1',
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+        );
+        $this->assertSame(
+            $this->spMetadata,
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+        );
+        $this->assertSame(
+            $this->userAttributes,
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+        );
+
+        $connectedServiceId = (int)$this->repository->getConnectedService($spId, $userId)->fetchOne();
+
+        $this->repository->updateConnectedServiceVersionCount(
+            $connectedServiceId,
+            $userVersionId,
+            new DateTimeImmutable()
+        );
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+        $this->assertCount(1, $resultArray);
+        $this->assertEquals(
+            '2',
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+        );
+        $this->assertSame(
+            $this->spMetadata,
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+        );
+        $this->assertSame(
+            $this->userAttributes,
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+        );
+
+        // Simulate another SP
+        $spEntityIdNew = $this->spEntityId . '-new';
+        $spEntityIdHashNew = $this->spEntityIdHash . '-new';
+        $spMetadataNew = $this->spMetadata . '-new';
+        $spMetadataHashNew = $this->spMetadataHash . '-new';
+        $this->repository->insertSp(
+            $spEntityIdNew,
+            $spEntityIdHashNew,
+            $spMetadataNew,
+            $spMetadataHashNew,
+            $this->createdAt
+        );
+        $spResult = $this->repository->getSp($spEntityIdHashNew)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+
+        $this->repository->insertConnectedService($spId, $userId, $userVersionId);
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+        $this->assertCount(2, $resultArray);
+        $this->assertEquals(
+            '1',
+            $resultArray[1]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+        );
+        $this->assertSame(
+            $spMetadataNew,
+            $resultArray[1]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+        );
+        $this->assertSame(
+            $this->userAttributes,
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+        );
+
+        // Simulate change in user attributes
+        $userAttributesNew = $this->userAttributes . '-new';
+        $userAttributesHashNew = $this->userAttributesHash . '-new';
+        $this->repository->insertUserVersion($userId, $userAttributesNew, $userAttributesHashNew, $this->createdAt);
+        $userVersionResult = $this->repository->getUserVersion($userId, $userAttributesHashNew)->fetchAssociative();
+        $userVersionId = (int)$userVersionResult[VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+        $connectedServiceId = (int)$this->repository->getConnectedService($spId, $userId)->fetchOne();
+
+        $this->repository->updateConnectedServiceVersionCount(
+            $connectedServiceId,
+            $userVersionId,
+            new DateTimeImmutable()
+        );
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+
+        $this->assertCount(2, $resultArray);
+        $this->assertEquals(
+            '2',
+            $resultArray[1]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+        );
+        $this->assertSame(
+            $spMetadataNew,
+            $resultArray[1]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+        );
+        // New SP with new user attributes version..
+        $this->assertSame(
+            $userAttributesNew,
+            $resultArray[1]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+        );
+
+        // First SP still has old user attributes version...
+        $this->assertSame(
+            $this->userAttributes,
+            $resultArray[0]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+        );
+    }
+
+    public function testInsertConnectedServiceThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->insertConnectedService(1, 1, 1);
+    }
+
+    public function testGetConnectedServiceThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getConnectedService(1, 1);
+    }
+
+    public function testGetConnectedServicesThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getConnectedServices($this->userIdentifierHash);
+    }
+
+    public function testUpdateConnectedServiceVersionCountThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->updateConnectedServiceVersionCount(1, 1, new DateTimeImmutable());
+    }
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     */
+    public function testCanDeleteConnectedServicesOlderThan(): void
+    {
+        $this->repository->insertSp(
+            $this->spEntityId,
+            $this->spEntityIdHash,
+            $this->spMetadata,
+            $this->spMetadataHash,
+            $this->createdAt
+        );
+        $spResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $userResult = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+        $userId = (int)$userResult[VersionedBaseTableConstants::TABLE_USER_COLUMN_NAME_ID];
+        $this->repository
+            ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt);
+        $userVersionResult = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative();
+        $userVersionId = (int)$userVersionResult[VersionedBaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+        $this->repository->insertConnectedService($spId, $userId, $userVersionId);
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+        $this->assertCount(1, $resultArray);
+
+        $dateTimeInFuture = $this->createdAt->add(new DateInterval('P1D'));
+
+        $this->repository->deleteConnectedServicesOlderThan($dateTimeInFuture);
+
+        $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash);
+        $this->assertCount(0, $resultArray);
+    }
+
+    public function testDeleteAuthenticationEventsOlderThanThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->deleteConnectedServicesOlderThan(new DateTimeImmutable());
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/StoreTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/StoreTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..16de975ec7ca64e219427c43bad2bc337d786c10
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/StoreTest.php
@@ -0,0 +1,325 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current;
+
+use DateTimeImmutable;
+use Doctrine\DBAL\Result;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\TableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Test\Module\accounting\Constants\RawRowResult;
+use SimpleSAML\Test\Module\accounting\Constants\StateArrays;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event\State;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as VersionedBaseTableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event
+ * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\State\Saml2
+ * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractState
+ * @uses \SimpleSAML\Module\accounting\Helpers\Arr
+ * @uses \SimpleSAML\Module\accounting\Helpers\Hash
+ * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
+ * @uses \SimpleSAML\Module\accounting\Helpers\Network
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Entities\ConnectedService\Bag
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity
+ * @uses \SimpleSAML\Module\accounting\Entities\ConnectedService
+ * @uses \SimpleSAML\Module\accounting\Entities\User
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations\Version20240505400CreateConnectedServiceTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem
+ */
+class StoreTest extends TestCase
+{
+    protected Stub $moduleConfigurationStub;
+    protected Migrator $migrator;
+    protected Stub $factoryStub;
+    protected Connection $connection;
+    protected State\Saml2 $state;
+    protected Event $authenticationEvent;
+    protected HashDecoratedState $hashDecoratedState;
+    /**
+     * @var MockObject
+     */
+    protected $repositoryMock;
+    /**
+     * @var Stub
+     */
+    protected $resultStub;
+    /**
+     * @var MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var MockObject
+     */
+    protected $helpersManagerMock;
+
+    /**
+     * @throws StoreException
+     */
+    protected function setUp(): void
+    {
+        $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY;
+        $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class);
+        $this->moduleConfigurationStub->method('getConnectionParameters')
+            ->willReturn($connectionParams);
+        $this->moduleConfigurationStub->method('getUserIdAttributeName')
+            ->willReturn('hrEduPersonPersistentID');
+
+        $this->connection = new Connection($connectionParams);
+
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+
+        $this->migrator = new Migrator($this->connection, $this->loggerMock);
+
+        $this->factoryStub = $this->createStub(Factory::class);
+        $this->factoryStub->method('buildConnection')->willReturn($this->connection);
+        $this->factoryStub->method('buildMigrator')->willReturn($this->migrator);
+
+        $this->state = new State\Saml2(StateArrays::SAML2_FULL);
+        $this->authenticationEvent = new Event($this->state);
+
+        $this->hashDecoratedState = new HashDecoratedState($this->state);
+        $this->repositoryMock = $this->createMock(Repository::class);
+
+        $this->resultStub = $this->createStub(Result::class);
+        $this->helpersManagerMock = $this->createMock(HelpersManager::class);
+    }
+    /**
+     * @throws StoreException
+     */
+    public function testCanConstructInstance(): void
+    {
+        $this->assertInstanceOf(
+            Store::class,
+            new Store(
+                $this->moduleConfigurationStub,
+                $this->loggerMock,
+                null,
+                ModuleConfiguration\ConnectionType::MASTER,
+                $this->factoryStub
+            )
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanBuildInstance(): void
+    {
+        $this->assertInstanceOf(
+            Store::class,
+            Store::build($this->moduleConfigurationStub, $this->loggerMock)
+        );
+    }
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     * @throws MigrationException
+     */
+    public function testCanPersistAuthenticationEvent(): void
+    {
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub
+        );
+        $store->runSetup();
+
+        $spCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $userCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $userVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $connectedServicesQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+        $spCountQueryBuilder->select('COUNT(id) as spCount')->from(
+        //'cds_sp'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_SP
+            )
+        );
+
+        $userCountQueryBuilder->select('COUNT(id) as userCount')->from(
+        //'vds_user'
+            $this->connection->preparePrefixedTableName(
+                VersionedBaseTableConstants::TABLE_PREFIX . VersionedBaseTableConstants::TABLE_NAME_USER
+            )
+        );
+        $userVersionCountQueryBuilder->select('COUNT(id) as userVersionCount')->from(
+        //'vds_user_version'
+            $this->connection->preparePrefixedTableName(
+                VersionedBaseTableConstants::TABLE_PREFIX . VersionedBaseTableConstants::TABLE_NAME_USER_VERSION
+            )
+        );
+
+        $connectedServicesQueryBuilder->select('COUNT(id) as connectedServiceCount')
+            ->from(
+            //'cds_connected_service'
+                $this->connection->preparePrefixedTableName(
+                    BaseTableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_CONNECTED_SERVICE
+                )
+            );
+
+        $this->assertSame(0, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$connectedServicesQueryBuilder->executeQuery()->fetchOne());
+
+        $store->persist($this->authenticationEvent);
+
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$connectedServicesQueryBuilder->executeQuery()->fetchOne());
+
+        $store->persist($this->authenticationEvent);
+
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$connectedServicesQueryBuilder->executeQuery()->fetchOne());
+
+        // New SP
+        $stateArray = StateArrays::SAML2_FULL;
+        $stateArray['SPMetadata']['entityid'] = 'new-entity-id';
+        $state = new State\Saml2($stateArray);
+        $authenticationEvent = new Event($state);
+
+        $store->persist($authenticationEvent);
+
+        $this->assertSame(2, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(2, (int)$connectedServicesQueryBuilder->executeQuery()->fetchOne());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetConnectedOrganizationsReturnsEmptyBagIfNoResults(): void
+    {
+        $this->repositoryMock->method('getConnectedServices')->willReturn([]);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $connectedServiceProviderBag = $store->getConnectedServices('test');
+
+        $this->assertEmpty($connectedServiceProviderBag->getAll());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanGetConnectedOrganizationsBag(): void
+    {
+        $this->repositoryMock->method('getConnectedServices')
+            ->willReturn([RawRowResult::CONNECTED_SERVICE]);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $connectedServiceProviderBag = $store->getConnectedServices('test');
+
+        $this->assertNotEmpty($connectedServiceProviderBag->getAll());
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetConnectedOrganizationsThrowsForInvalidResult(): void
+    {
+        $rawResult = RawRowResult::CONNECTED_SERVICE;
+        unset($rawResult[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]);
+
+        $this->repositoryMock->method('getConnectedServices')
+            ->willReturn([$rawResult]);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $this->expectException(StoreException::class);
+        $store->getConnectedServices('test');
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanDeleteDataOlderThan(): void
+    {
+        $dateTime = new DateTimeImmutable();
+
+        $this->repositoryMock->expects($this->once())
+            ->method('deleteConnectedServicesOlderThan')
+            ->with($dateTime);
+
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub,
+            $this->helpersManagerMock,
+            $this->repositoryMock
+        );
+
+        $store->deleteDataOlderThan($dateTime);
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php
old mode 100644
new mode 100755
index a9dc48e2fd26ebd50f97919fb8ac6dfb62376984..c1559de628598defe651ab5ca4f19126c2d5e7d6
--- a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php
@@ -12,6 +12,7 @@ use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\Doctri
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\TableConstants;
 use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException;
 use SimpleSAML\Test\Module\accounting\Constants\DateTime;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
 
 /**
  * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService
@@ -45,15 +46,15 @@ class RawConnectedServiceTest extends TestCase
         $this->serviceProviderMetadata = ['sp' => 'metadata'];
         $this->userAttributes = ['user' => 'attribute'];
         $this->rawRow = [
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS =>
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS =>
                 $this->numberOfAuthentications,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT =>
                 $this->lastAuthenticationAt,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT =>
                 $this->firstAuthenticationAt,
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA =>
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA =>
                 serialize($this->serviceProviderMetadata),
-            TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES =>
+            EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES =>
                 serialize($this->userAttributes),
         ];
         $this->dateTimeFormat = DateTime::DEFAULT_FORMAT;
@@ -95,7 +96,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfColumnNotSet(): void
     {
         $rawRow = $this->rawRow;
-        unset($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]);
+        unset($rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]);
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -105,7 +106,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfNumberOfAuthenticationsNotNumeric(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] = 'a';
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] = 'a';
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -115,7 +116,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfLastAuthenticationAtNotString(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT] = 1;
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -125,7 +126,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfFirstAuthenticationAtNotString(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT] = 1;
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -135,7 +136,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfSpMetadataNotString(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] = 1;
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -145,7 +146,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfUserAttributesNotString(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] = 1;
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] = 1;
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -155,7 +156,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfSpMetadataNotValid(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] = serialize(1);
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] = serialize(1);
 
         $this->expectException(UnexpectedValueException::class);
 
@@ -165,7 +166,7 @@ class RawConnectedServiceTest extends TestCase
     public function testThrowsIfUserAttributesNotValid(): void
     {
         $rawRow = $this->rawRow;
-        $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] = serialize(1);
+        $rawRow[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] = serialize(1);
 
         $this->expectException(UnexpectedValueException::class);
 
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateConnectedServiceTableTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateConnectedServiceTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..21c2ee813d9b81babc023dfca8169edfdd3bc3e9
--- /dev/null
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateConnectedServiceTableTest.php
@@ -0,0 +1,103 @@
+<?php
+
+//phpcs:ignore
+namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations;
+
+use Doctrine\DBAL\Exception;
+use Doctrine\DBAL\Schema\AbstractSchemaManager;
+use PHPUnit\Framework\MockObject\Stub;
+//phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateConnectedServiceTable;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+
+/**
+ * @covers SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateConnectedServiceTable
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ */
+class Version20220801000700CreateConnectedServiceTableTest extends TestCase
+{
+    protected Connection $connection;
+    protected AbstractSchemaManager $schemaManager;
+    protected string $tableName;
+    protected Stub $connectionStub;
+    protected Stub $dbalStub;
+    protected Stub $schemaManagerStub;
+
+    /**
+     * @throws Exception
+     */
+    protected function setUp(): void
+    {
+        $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->schemaManager = $this->connection->dbal()->createSchemaManager();
+        $this->tableName = 'vds_connected_service';
+
+        $this->connectionStub = $this->createStub(Connection::class);
+        $this->dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class);
+        $this->schemaManagerStub = $this->createStub(AbstractSchemaManager::class);
+    }
+
+    /**
+     * @throws StoreException
+     * @throws MigrationException
+     * @throws Exception
+     */
+    public function testCanRunMigration(): void
+    {
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+        $migration = new Version20220801000700CreateConnectedServiceTable($this->connection);
+        $migration->run();
+        $this->assertTrue($this->schemaManager->tablesExist($this->tableName));
+        $migration->revert();
+        $this->assertFalse($this->schemaManager->tablesExist($this->tableName));
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsMigrationException(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')->willReturn($this->tableName);
+        $this->schemaManagerStub->method('createTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new Version20220801000700CreateConnectedServiceTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRevertThrowsMigrationException(): void
+    {
+        $this->schemaManagerStub->method('dropTable')
+            ->willThrowException(new Exception('test'));
+        $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub);
+        $this->connectionStub->method('dbal')->willReturn($this->dbalStub);
+
+        $migration = new Version20220801000700CreateConnectedServiceTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->revert();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunThrowsOnIvalidTableName(): void
+    {
+        $this->connectionStub->method('preparePrefixedTableName')
+            ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table
+
+        $migration = new Version20220801000700CreateConnectedServiceTable($this->connectionStub);
+        $this->expectException(MigrationException::class);
+        $migration->run();
+    }
+}
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/RepositoryTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/RepositoryTest.php
old mode 100644
new mode 100755
index aac918695cb0dd8c438fe6de50b1dba813b47445..acac97cbbf8a71c1e676dc551e7de5d1278ed52d
--- a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/RepositoryTest.php
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/RepositoryTest.php
@@ -10,20 +10,20 @@ use Exception;
 use PHPUnit\Framework\MockObject\Stub;
 use PHPUnit\Framework\TestCase;
 use Psr\Log\LoggerInterface;
-use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants
-    as BaseTableConstants;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as BaseTableConstants;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Repository;
 use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\TableConstants;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection;
 use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
-use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
 use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
 use SimpleSAML\Test\Module\accounting\Constants\DateTime;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
 
 /**
  * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Repository
@@ -78,8 +78,6 @@ class RepositoryTest extends TestCase
     protected $connectionStub;
     protected string $spEntityIdHash;
     protected string $spMetadata;
-    protected string $clientIpAddress;
-    protected string $authenticationProtocolDesignation;
 
     /**
      * @throws StoreException
@@ -133,8 +131,6 @@ class RepositoryTest extends TestCase
         $this->userAttributesHash = 'user-attributes-hash';
 
         $this->createdAt = new DateTimeImmutable();
-        $this->clientIpAddress = '123.123.123.123';
-        $this->authenticationProtocolDesignation = Saml2::DESIGNATION;
     }
 
     public function testCanCreateInstance(): void
@@ -194,17 +190,17 @@ class RepositoryTest extends TestCase
         $this->assertEquals(
             '1',
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
         );
         $this->assertSame(
             $this->spMetadata,
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
         );
         $this->assertSame(
             $this->userAttributes,
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
         );
 
         $connectedServiceId = (int)$this->repository->getConnectedService($idpSpUserVersionId)->fetchOne();
@@ -219,17 +215,17 @@ class RepositoryTest extends TestCase
         $this->assertEquals(
             '2',
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
         );
         $this->assertSame(
             $this->spMetadata,
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
         );
         $this->assertSame(
             $this->userAttributes,
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
         );
 
         // Simulate another SP
@@ -258,17 +254,17 @@ class RepositoryTest extends TestCase
         $this->assertEquals(
             '1',
             $resultArray[$spEntityIdNew]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
         );
         $this->assertSame(
             $spMetadataNew,
             $resultArray[$spEntityIdNew]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
         );
         $this->assertSame(
             $this->userAttributes,
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
         );
 
         // Simulate change in user attributes
@@ -291,29 +287,29 @@ class RepositoryTest extends TestCase
         $this->assertEquals(
             '2',
             $resultArray[$spEntityIdNew]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]
         );
         $this->assertSame(
             $spMetadataNew,
             $resultArray[$spEntityIdNew]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA]
         );
         // New SP with new user attributes version..
         $this->assertSame(
             $userAttributesNew,
             $resultArray[$spEntityIdNew]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
         );
 
         // First SP still has old user attributes version...
         $this->assertSame(
             $this->userAttributes,
             $resultArray[$this->spEntityId]
-            [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
+            [EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]
         );
     }
 
-    public function testGetConnectedServiceProvidersThrowsOnInvalidDbal(): void
+    public function testGetConnectedServicesThrowsOnInvalidDbal(): void
     {
         $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
         $repository = new Repository($this->connectionStub, $this->loggerStub);
@@ -322,6 +318,33 @@ class RepositoryTest extends TestCase
         $repository->getConnectedServices($this->userIdentifierHash);
     }
 
+    public function testGetConnectedServiceThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->getConnectedService(1);
+    }
+
+    public function testInsertConnectedServiceThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->insertConnectedService(1);
+    }
+
+    public function testUpdatetConnectedServiceCountThrowsOnInvalidDbal(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->updateConnectedServiceVersionCount(1, new DateTimeImmutable());
+    }
+
     /**
      * @throws StoreException
      * @throws \Doctrine\DBAL\Exception
@@ -380,4 +403,72 @@ class RepositoryTest extends TestCase
 
         $repository->deleteConnectedServicesOlderThan(new DateTimeImmutable());
     }
+
+    public function testCanTouchConnectedServiceVersionsTimestamp(): void
+    {
+        $this->repository->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $this->createdAt);
+        $idpResult = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative();
+        $idpId = (int)$idpResult[BaseTableConstants::TABLE_IDP_COLUMN_NAME_ID];
+        $this->repository->insertIdpVersion($idpId, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt);
+
+        $this->repository->insertSp($this->spEntityId, $this->spEntityIdHash, $this->createdAt);
+        $spResult = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative();
+        $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID];
+        $this->repository->insertSpVersion($spId, $this->spMetadata, $this->spMetadataHash, $this->createdAt);
+
+        $this->repository->insertUser($this->userIdentifier, $this->userIdentifierHash, $this->createdAt);
+        $userResult = $this->repository->getUser($this->userIdentifierHash)->fetchAssociative();
+        $userId = (int)$userResult[BaseTableConstants::TABLE_USER_COLUMN_NAME_ID];
+        $this->repository
+            ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt);
+
+        $idpVersionResult = $this->repository->getIdpVersion($idpId, $this->idpMetadataHash)->fetchAssociative();
+        $spVersionResult = $this->repository->getSpVersion($spId, $this->spMetadataHash)->fetchAssociative();
+        $userVersionResult = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative();
+
+        $idpVersionId = (int)$idpVersionResult[BaseTableConstants::TABLE_IDP_VERSION_COLUMN_NAME_ID];
+        $spVersionId = (int)$spVersionResult[BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID];
+        $userVersionId = (int)$userVersionResult[BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID];
+
+        $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId, $this->createdAt);
+        $idpSpUserVersionResult = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId)
+            ->fetchAssociative();
+
+        $idpSpUserVersionId =
+            (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID];
+
+        $authenticationAt = new DateTimeImmutable();
+
+        $this->repository->insertConnectedService($idpSpUserVersionId, $authenticationAt, $authenticationAt);
+
+        $resultArray = $this->repository->getConnectedService($idpSpUserVersionId)->fetchAssociative();
+        $this->assertSame(
+            $authenticationAt->format(DateTime::DEFAULT_FORMAT),
+            $resultArray[TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT]
+        );
+
+        $newAuthenticationAt = $authenticationAt->add(new DateInterval('P1D'));
+
+        $this->repository->touchConnectedServiceVersionsTimestamp($userId, $spId, $newAuthenticationAt);
+
+        $resultArray = $this->repository->getConnectedService($idpSpUserVersionId)->fetchAssociative();
+
+        $this->assertNotSame(
+            $authenticationAt->format(DateTime::DEFAULT_FORMAT),
+            $resultArray[TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT]
+        );
+        $this->assertSame(
+            $newAuthenticationAt->format(DateTime::DEFAULT_FORMAT),
+            $resultArray[TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT]
+        );
+    }
+
+    public function testTouchConnectedServiceVersionsTimestampThrowsOnInvalidDbalForSelect(): void
+    {
+        $this->connectionStub->method('dbal')->willThrowException(new Exception('test'));
+        $repository = new Repository($this->connectionStub, $this->loggerStub);
+        $this->expectException(StoreException::class);
+
+        $repository->touchConnectedServiceVersionsTimestamp(1, 1);
+    }
 }
diff --git a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/StoreTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/StoreTest.php
old mode 100644
new mode 100755
index 3e810e1698ad9eee4d79d8af17f0d6fdefb1bbac..4dd1c1b7e54ad1d8704193d07be3fa574cb127ec
--- a/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/StoreTest.php
+++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/StoreTest.php
@@ -20,11 +20,15 @@ use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event;
 use SimpleSAML\Module\accounting\Entities\Authentication\Event\State;
 use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException;
 use SimpleSAML\Module\accounting\ModuleConfiguration;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
 use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
 use SimpleSAML\Test\Module\accounting\Constants\RawRowResult;
 use SimpleSAML\Test\Module\accounting\Constants\StateArrays;
+// phpcs:ignore
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants as BaseTableConstants;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\EntityTableConstants;
 
 /**
  * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store
@@ -161,6 +165,115 @@ class StoreTest extends TestCase
         );
     }
 
+
+    /**
+     * @throws StoreException
+     * @throws \Doctrine\DBAL\Exception
+     * @throws MigrationException
+     */
+    public function testCanPersistAuthenticationEvent(): void
+    {
+        $store = new Store(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            null,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->factoryStub
+        );
+        $store->runSetup();
+
+        $idpCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $idpVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $spCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $spVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $userCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $userVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $idpSpUserVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+        $connectedServiceCountQueryBuilder = $this->connection->dbal()->createQueryBuilder();
+
+        $idpCountQueryBuilder->select('COUNT(id) as idpCount')->from(
+        //'vds_idp'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_IDP
+            )
+        );
+        $idpVersionCountQueryBuilder->select('COUNT(id) as idpVersionCount')->from(
+        //'vds_idp_version'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_IDP_VERSION
+            )
+        );
+        $spCountQueryBuilder->select('COUNT(id) as spCount')->from(
+        //'vds_sp'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_SP
+            )
+        );
+        $spVersionCountQueryBuilder->select('COUNT(id) as spVersionCount')->from(
+        //'vds_sp_version'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_SP_VERSION
+            )
+        );
+        $userCountQueryBuilder->select('COUNT(id) as userCount')->from(
+        //'vds_user'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_USER
+            )
+        );
+        $userVersionCountQueryBuilder->select('COUNT(id) as userVersionCount')->from(
+        //'vds_user_version'
+            $this->connection->preparePrefixedTableName(
+                BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_USER_VERSION
+            )
+        );
+        $idpSpUserVersionCountQueryBuilder->select('COUNT(id) as idpSpUserVersionCount')
+            ->from(
+            //'vds_idp_sp_user_version'
+                $this->connection->preparePrefixedTableName(
+                    BaseTableConstants::TABLE_PREFIX . BaseTableConstants::TABLE_NAME_IDP_SP_USER_VERSION
+                )
+            );
+        $connectedServiceCountQueryBuilder->select('COUNT(id) as connectedServiceCount')
+            ->from(
+            //'vds_connected_service'
+                $this->connection->preparePrefixedTableName(
+                    BaseTableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_CONNECTED_SERVICE
+                )
+            );
+
+        $this->assertSame(0, (int)$idpCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$spVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$idpSpUserVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(0, (int)$connectedServiceCountQueryBuilder->executeQuery()->fetchOne());
+
+        $store->persist($this->authenticationEvent);
+
+        $this->assertSame(1, (int)$idpCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$spVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$idpSpUserVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$connectedServiceCountQueryBuilder->executeQuery()->fetchOne());
+
+        $store->persist($this->authenticationEvent);
+
+        $this->assertSame(1, (int)$idpCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$spCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$spVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$userVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$idpSpUserVersionCountQueryBuilder->executeQuery()->fetchOne());
+        $this->assertSame(1, (int)$connectedServiceCountQueryBuilder->executeQuery()->fetchOne());
+    }
+
     /**
      * @throws StoreException
      */
@@ -212,7 +325,7 @@ class StoreTest extends TestCase
     public function testGetConnectedOrganizationsThrowsForInvalidResult(): void
     {
         $rawResult = RawRowResult::CONNECTED_SERVICE;
-        unset($rawResult[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]);
+        unset($rawResult[EntityTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]);
 
         $this->repositoryMock->method('getConnectedServices')
             ->willReturn([$rawResult]);
diff --git a/tests/src/Data/Stores/Bases/AbstractStoreTest.php b/tests/src/Data/Stores/Bases/AbstractStoreTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php b/tests/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Builders/DataStoreBuilderTest.php b/tests/src/Data/Stores/Builders/DataStoreBuilderTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Builders/JobsStoreBuilderTest.php b/tests/src/Data/Stores/Builders/JobsStoreBuilderTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Connections/Bases/AbstractMigratorTest.php b/tests/src/Data/Stores/Connections/Bases/AbstractMigratorTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Connections/DoctrineDbal/ConnectionTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/ConnectionTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Connections/DoctrineDbal/FactoryTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/FactoryTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Connections/DoctrineDbal/MigratorTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/MigratorTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Jobs/DoctrineDbal/StoreTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/StoreTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Stores/Jobs/PhpRedis/RedisStoreTest.php b/tests/src/Data/Stores/Jobs/PhpRedis/RedisStoreTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Data/Trackers/Activity/DoctrineDbal/CurrentDataTrackerTest.php b/tests/src/Data/Trackers/Activity/DoctrineDbal/CurrentDataTrackerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a9ef384472e7f624b1cf9167c76dddc42d273bb
--- /dev/null
+++ b/tests/src/Data/Trackers/Activity/DoctrineDbal/CurrentDataTrackerTest.php
@@ -0,0 +1,190 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Trackers\Activity\DoctrineDbal;
+
+use DateInterval;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\CurrentDataTracker;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Module\accounting\Entities\Activity;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\CurrentDataTracker
+ * @uses \SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\CurrentDataProvider
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ */
+class CurrentDataTrackerTest extends TestCase
+{
+    /**
+     * @var Stub
+     */
+    protected $moduleConfigurationStub;
+    /**
+     * @var MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var MockObject
+     */
+    protected $store;
+
+    protected function setUp(): void
+    {
+        $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class);
+        $this->moduleConfigurationStub->method('getConnectionParameters')
+            ->willReturn(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->store = $this->createMock(Store::class);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            new CurrentDataTracker(
+                $this->moduleConfigurationStub,
+                $this->loggerMock,
+                ModuleConfiguration\ConnectionType::MASTER,
+                $this->store
+            )
+        );
+
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            new CurrentDataTracker($this->moduleConfigurationStub, $this->loggerMock)
+        );
+
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            CurrentDataTracker::build($this->moduleConfigurationStub, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testProcessCallsPersistOnDataStore(): void
+    {
+        $authenticationEventStub = $this->createStub(Event::class);
+
+        $this->store->expects($this->once())
+            ->method('persist')
+            ->with($authenticationEventStub);
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $tracker->process($authenticationEventStub);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testSetupDependsOnDataStore(): void
+    {
+        $this->store->expects($this->exactly(2))
+            ->method('needsSetup')
+            ->willReturn(true);
+
+        $this->store->expects($this->once())
+            ->method('runSetup');
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $this->assertTrue($tracker->needsSetup());
+
+        $tracker->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunningSetupIfNotNeededLogsWarning(): void
+    {
+        $this->store->method('needsSetup')
+            ->willReturn(false);
+
+        $this->loggerMock->expects($this->once())
+            ->method('warning');
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $tracker->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetActivity(): void
+    {
+        $activityBag = $this->createStub(Activity\Bag::class);
+        $this->store->expects($this->once())
+            ->method('getActivity')
+            ->willReturn($activityBag);
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $this->assertInstanceOf(
+            Activity\Bag::class,
+            $tracker->getActivity('test', 10, 0)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanEnforceDataRetentionPolicy(): void
+    {
+        $retentionPolicy = new DateInterval('P10D');
+
+        $this->store->expects($this->once())
+            ->method('deleteDataOlderThan');
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $tracker->enforceDataRetentionPolicy($retentionPolicy);
+    }
+}
diff --git a/tests/src/Data/Trackers/Activity/DoctrineDbal/Versioned/DataTrackerTest.php b/tests/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTrackerTest.php
old mode 100644
new mode 100755
similarity index 98%
rename from tests/src/Data/Trackers/Activity/DoctrineDbal/Versioned/DataTrackerTest.php
rename to tests/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTrackerTest.php
index 4a9caee04e1f2b4be41883299e6275cb1b08674f..beea81a97e8617f40054c1b5bc63e10910936227
--- a/tests/src/Data/Trackers/Activity/DoctrineDbal/Versioned/DataTrackerTest.php
+++ b/tests/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTrackerTest.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace SimpleSAML\Test\Module\accounting\Data\Trackers\Activity\DoctrineDbal\Versioned;
+namespace SimpleSAML\Test\Module\accounting\Data\Trackers\Activity\DoctrineDbal;
 
 use DateInterval;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -36,7 +36,7 @@ use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository
  */
-class DataTrackerTest extends TestCase
+class VersionedDataTrackerTest extends TestCase
 {
     /**
      * @var Stub
diff --git a/tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php b/tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php
old mode 100644
new mode 100755
index ed5c86b7e479e56fe662e9bc38332178924b2b8d..05aa2ec802a94806bedcd3b36f93e3207237266f
--- a/tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php
+++ b/tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php
@@ -21,7 +21,7 @@ use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
  * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration
  * @uses \SimpleSAML\Module\accounting\Services\HelpersManager
  */
-class AuthenticationDataTrackerBuilderTest extends TestCase
+class DataTrackerBuilderTest extends TestCase
 {
     /**
      * @var MockObject
diff --git a/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTrackerTest.php b/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTrackerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3913e789ce613b922b202437e55bd777193c49d0
--- /dev/null
+++ b/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTrackerTest.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal;
+
+use DateInterval;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store;
+use SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\CurrentDataTracker;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Entities\Authentication\Event;
+use SimpleSAML\Module\accounting\Exceptions\StoreException;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
+use SimpleSAML\Module\accounting\Entities\ConnectedService;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\CurrentDataTracker
+ * @uses \SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Repository
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator
+ * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator
+ */
+class CurrentDataTrackerTest extends TestCase
+{
+    /**
+     * @var Stub
+     */
+    protected $moduleConfigurationStub;
+    /**
+     * @var MockObject
+     */
+    protected $loggerMock;
+    /**
+     * @var MockObject
+     */
+    protected $store;
+
+    protected function setUp(): void
+    {
+        $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class);
+        $this->moduleConfigurationStub->method('getConnectionParameters')
+            ->willReturn(ConnectionParameters::DBAL_SQLITE_MEMORY);
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->store = $this->createMock(
+            Store::class
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            new CurrentDataTracker(
+                $this->moduleConfigurationStub,
+                $this->loggerMock,
+                ModuleConfiguration\ConnectionType::MASTER,
+                $this->store
+            )
+        );
+
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            new CurrentDataTracker($this->moduleConfigurationStub, $this->loggerMock)
+        );
+
+        $this->assertInstanceOf(
+            CurrentDataTracker::class,
+            CurrentDataTracker::build($this->moduleConfigurationStub, $this->loggerMock)
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testProcessCallsPersistOnDataStore(): void
+    {
+        $authenticationEventStub = $this->createStub(Event::class);
+
+        $this->store->expects($this->once())
+            ->method('persist')
+            ->with($authenticationEventStub);
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $tracker->process($authenticationEventStub);
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testSetupDependsOnDataStore(): void
+    {
+        $this->store->expects($this->exactly(2))
+            ->method('needsSetup')
+            ->willReturn(true);
+
+        $this->store->expects($this->once())
+            ->method('runSetup');
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $this->assertTrue($tracker->needsSetup());
+
+        $tracker->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testRunningSetupIfNotNeededLogsWarning(): void
+    {
+        $this->store->method('needsSetup')
+            ->willReturn(false);
+
+        $this->loggerMock->expects($this->once())
+            ->method('warning');
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $tracker->runSetup();
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testGetConnectedServices(): void
+    {
+        $connectedOrganizationsBagStub = $this->createStub(ConnectedService\Bag::class);
+        $this->store->expects($this->once())
+            ->method('getConnectedServices')
+            ->willReturn($connectedOrganizationsBagStub);
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $this->assertInstanceOf(
+            ConnectedService\Bag::class,
+            $tracker->getConnectedServices('test')
+        );
+    }
+
+    /**
+     * @throws StoreException
+     */
+    public function testCanEnforceDataRetentionPolicy(): void
+    {
+        $retentionPolicy = new DateInterval('P10D');
+
+        $this->store->expects($this->once())
+            ->method('deleteDataOlderThan');
+
+        $tracker = new CurrentDataTracker(
+            $this->moduleConfigurationStub,
+            $this->loggerMock,
+            ModuleConfiguration\ConnectionType::MASTER,
+            $this->store
+        );
+
+        $tracker->enforceDataRetentionPolicy($retentionPolicy);
+    }
+}
diff --git a/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/Versioned/DataTrackerTest.php b/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTrackerTest.php
old mode 100644
new mode 100755
similarity index 98%
rename from tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/Versioned/DataTrackerTest.php
rename to tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTrackerTest.php
index 29fef34efba4c9110df8eed522144d690e0423f2..5ab6125500ea29064538eda2c3868c23586853ac
--- a/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/Versioned/DataTrackerTest.php
+++ b/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTrackerTest.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace SimpleSAML\Test\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\Versioned;
+namespace SimpleSAML\Test\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal;
 
 use DateInterval;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -36,7 +36,7 @@ use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters;
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store
  * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository
  */
-class DataTrackerTest extends TestCase
+class VersionedDataTrackerTest extends TestCase
 {
     /**
      * @var Stub
diff --git a/tests/src/Entities/Activity/BagTest.php b/tests/src/Entities/Activity/BagTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/ActivityTest.php b/tests/src/Entities/ActivityTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/Event/JobTest.php b/tests/src/Entities/Authentication/Event/JobTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/Event/State/OidcTest.php b/tests/src/Entities/Authentication/Event/State/OidcTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/Event/State/Saml2Test.php b/tests/src/Entities/Authentication/Event/State/Saml2Test.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/EventTest.php b/tests/src/Entities/Authentication/EventTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/Protocol/BagTest.php b/tests/src/Entities/Authentication/Protocol/BagTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/Protocol/OidcTest.php b/tests/src/Entities/Authentication/Protocol/OidcTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Authentication/Protocol/Saml2Test.php b/tests/src/Entities/Authentication/Protocol/Saml2Test.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Bases/AbstractJobTest.php b/tests/src/Entities/Bases/AbstractJobTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Bases/AbstractProviderTest.php b/tests/src/Entities/Bases/AbstractProviderTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Bases/AbstractStateTest.php b/tests/src/Entities/Bases/AbstractStateTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/ConnectedService/BagTest.php b/tests/src/Entities/ConnectedService/BagTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/ConnectedServiceTest.php b/tests/src/Entities/ConnectedServiceTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/GenericJobTest.php b/tests/src/Entities/GenericJobTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Entities/Providers/Identity/OidcTest.php b/tests/src/Entities/Providers/Identity/OidcTest.php
old mode 100644
new mode 100755
index 450a0cd0af491775138cf2d9f0c305244ebcf136..91ab3e260936ecdbb74d3c17d9da8a6130916141
--- a/tests/src/Entities/Providers/Identity/OidcTest.php
+++ b/tests/src/Entities/Providers/Identity/OidcTest.php
@@ -44,4 +44,12 @@ class OidcTest extends TestCase
 
         new Oidc($metadata);
     }
+
+    public function testCanGetProtocol(): void
+    {
+        $this->assertInstanceOf(
+            \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc::class,
+            (new Oidc($this->metadata))->getProtocol()
+        );
+    }
 }
diff --git a/tests/src/Entities/Providers/Identity/Saml2Test.php b/tests/src/Entities/Providers/Identity/Saml2Test.php
old mode 100644
new mode 100755
index f2214d846018171f658301194aec02319013fe2e..7c995952a1fee1d1a238a78853682c393d655c6f
--- a/tests/src/Entities/Providers/Identity/Saml2Test.php
+++ b/tests/src/Entities/Providers/Identity/Saml2Test.php
@@ -46,4 +46,12 @@ class Saml2Test extends TestCase
 
         new Saml2($metadata);
     }
+
+    public function testCanGetProtocol(): void
+    {
+        $this->assertInstanceOf(
+            \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2::class,
+            (new Saml2($this->metadata))->getProtocol()
+        );
+    }
 }
diff --git a/tests/src/Entities/Providers/Service/OidcTest.php b/tests/src/Entities/Providers/Service/OidcTest.php
old mode 100644
new mode 100755
index 4827e5cff1c7f6cb285e603446e570cda3a42739..db62ad41c0a6337284d72342876b4e478248d3bc
--- a/tests/src/Entities/Providers/Service/OidcTest.php
+++ b/tests/src/Entities/Providers/Service/OidcTest.php
@@ -46,4 +46,12 @@ class OidcTest extends TestCase
 
         new Oidc($metadata);
     }
+
+    public function testCanGetProtocol(): void
+    {
+        $this->assertInstanceOf(
+            \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc::class,
+            (new Oidc($this->metadata))->getProtocol()
+        );
+    }
 }
diff --git a/tests/src/Entities/Providers/Service/Saml2Test.php b/tests/src/Entities/Providers/Service/Saml2Test.php
old mode 100644
new mode 100755
index f8565c18bc919e4edf58e540ca54c5adc47aa4c0..c4475d7089d65d70742a6b2b14d65b0860f72316
--- a/tests/src/Entities/Providers/Service/Saml2Test.php
+++ b/tests/src/Entities/Providers/Service/Saml2Test.php
@@ -41,4 +41,12 @@ class Saml2Test extends TestCase
 
         new Saml2($metadata);
     }
+
+    public function testCanGetProtocol(): void
+    {
+        $this->assertInstanceOf(
+            \SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2::class,
+            (new Saml2($this->metadata))->getProtocol()
+        );
+    }
 }
diff --git a/tests/src/Entities/UserTest.php b/tests/src/Entities/UserTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/ArrayTest.php b/tests/src/Helpers/ArrayTest.php
old mode 100644
new mode 100755
index 0aeb6dfe523374800a057bc2bfefaa54b5e89903..dc35b097da155765d5f25dc94fa382e18091e05a
--- a/tests/src/Helpers/ArrayTest.php
+++ b/tests/src/Helpers/ArrayTest.php
@@ -30,4 +30,44 @@ class ArrayTest extends TestCase
 
         $this->assertSame($unsorted, $sorted);
     }
+
+    public function testCanGroupByValue(): void
+    {
+        $ungrouped = [
+            ['a' => '1', 'b' => '1'],
+            ['a' => '1', 'b' => '2'],
+            ['a' => '2', 'b' => '3'],
+            ['a' => '2', 'b' => '4'],
+            ['a' => '3', 'b' => '5'],
+        ];
+
+        $expected = [
+            '1' => [
+                ['a' => '1', 'b' => '1'],
+                ['a' => '1', 'b' => '2'],
+            ],
+            '2' => [
+                ['a' => '2', 'b' => '3'],
+                ['a' => '2', 'b' => '4'],
+            ],
+            '3' => [
+                ['a' => '3', 'b' => '5'],
+            ],
+        ];
+
+        $this->assertNotSame($ungrouped, $expected);
+        $grouped = (new Arr())->groupByValue($ungrouped, 'a');
+        $this->assertSame($grouped, $expected);
+    }
+
+    public function testIsAssociative(): void
+    {
+        $nonAssociative = ['a', 'b', 'c'];
+        $associative = ['a' => 1, 'b' => 2, 'c' => 3];
+        $mixed = ['a', 'b' => 2, 'c'];
+
+        $this->assertFalse((new Arr())->isAssociative($nonAssociative));
+        $this->assertTrue((new Arr())->isAssociative($associative));
+        $this->assertTrue((new Arr())->isAssociative($mixed));
+    }
 }
diff --git a/tests/src/Helpers/AttributesTest.php b/tests/src/Helpers/AttributesTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/AuthenticationEventStateResolverTest.php b/tests/src/Helpers/AuthenticationEventStateResolverTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/DateTimeTest.php b/tests/src/Helpers/DateTimeTest.php
old mode 100644
new mode 100755
index e4bbb57f3efa6fb143d47333a2978749b94a9137..d84163fc2afeeac875380b900d79efde59ab6a28
--- a/tests/src/Helpers/DateTimeTest.php
+++ b/tests/src/Helpers/DateTimeTest.php
@@ -26,4 +26,14 @@ class DateTimeTest extends TestCase
 
         $this->assertSame(1, (new DateTime())->convertDateIntervalToSeconds($interval));
     }
+
+    public function testToFormattedString(): void
+    {
+        $dateTime = new \DateTimeImmutable();
+
+        $this->assertSame(
+            $dateTime->format(DateTime::FORMAT_MYSQL),
+            (new DateTime())->toFormattedString($dateTime)
+        );
+    }
 }
diff --git a/tests/src/Helpers/EnvironmentTest.php b/tests/src/Helpers/EnvironmentTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/FilesystemTest.php b/tests/src/Helpers/FilesystemTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/HashTest.php b/tests/src/Helpers/HashTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/InstanceBuilderUsingModuleConfigurationTest.php b/tests/src/Helpers/InstanceBuilderUsingModuleConfigurationTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/NetworkTest.php b/tests/src/Helpers/NetworkTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/ProviderResolverTest.php b/tests/src/Helpers/ProviderResolverTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Helpers/RandomTest.php b/tests/src/Helpers/RandomTest.php
old mode 100644
new mode 100755
index 29fa51e895c02c3a17435abc33c2ef3060d5951c..5afe1cf4eb1eb652a166a739acf56dd8ade3f5f3
--- a/tests/src/Helpers/RandomTest.php
+++ b/tests/src/Helpers/RandomTest.php
@@ -16,4 +16,11 @@ class RandomTest extends TestCase
     {
         $this->assertIsInt((new Random())->getInt());
     }
+
+    public function testCanGetRandomString(): void
+    {
+        $this->assertIsString((new Random())->getString());
+
+        $this->assertSame(5, mb_strlen((new Random())->getString(5)));
+    }
 }
diff --git a/tests/src/Helpers/RoutesTest.php b/tests/src/Helpers/RoutesTest.php
old mode 100644
new mode 100755
index b649a1458867d538d1e2af76888772ed6031569e..3912741a65c6c6897e0f1de4777bf95a85c837dd
--- a/tests/src/Helpers/RoutesTest.php
+++ b/tests/src/Helpers/RoutesTest.php
@@ -14,6 +14,7 @@ use SimpleSAML\Utils\HTTP;
 
 /**
  * @covers \SimpleSAML\Module\accounting\Helpers\Routes
+ * @uses \SimpleSAML\Module\accounting\Helpers\Arr
  */
 class RoutesTest extends TestCase
 {
@@ -53,4 +54,22 @@ class RoutesTest extends TestCase
 
         $this->assertSame($fullUrl, $moduleRoutesHelper->getUrl($path, $params));
     }
+
+    public function testCanAppendFragmentParameters(): void
+    {
+        $associativeFragments = ['a' => 'b'];
+        $indexedFragments = ['a', 'b'];
+
+        $expectedUrlAssociative = self::BASE_URL . 'module.php/' . ModuleConfiguration::MODULE_NAME . '/path#a=b';
+        $expectedUrlIndexed = self::BASE_URL . 'module.php/' . ModuleConfiguration::MODULE_NAME . '/path#a&b';
+
+        $this->assertSame(
+            $expectedUrlAssociative,
+            (new Routes($this->sspHttpUtilsStub))->getUrl('path', [], $associativeFragments)
+        );
+        $this->assertSame(
+            $expectedUrlIndexed,
+            (new Routes($this->sspHttpUtilsStub))->getUrl('path', [], $indexedFragments)
+        );
+    }
 }
diff --git a/tests/src/Helpers/SspModuleTest.php b/tests/src/Helpers/SspModuleTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d1e90c31c6fe52ea9e2891f153d7baf9df8d6c7
--- /dev/null
+++ b/tests/src/Helpers/SspModuleTest.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Helpers;
+
+use SimpleSAML\Module\accounting\Helpers\SspModule;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Helpers\SspModule
+ */
+class SspModuleTest extends TestCase
+{
+    public function testIsEnabled(): void
+    {
+        // Config file is at tests/config-templates/config.php
+        $this->assertFalse((new SspModule())->isEnabled('invalid'));
+        $this->assertTrue((new SspModule())->isEnabled('admin'));
+    }
+}
diff --git a/tests/src/ModuleConfigurationTest.php b/tests/src/ModuleConfigurationTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Services/AlertsBag/AlertTest.php b/tests/src/Services/AlertsBag/AlertTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5fc6af81022fa65043d29a527ec916c21a936433
--- /dev/null
+++ b/tests/src/Services/AlertsBag/AlertTest.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services\AlertsBag;
+
+use SimpleSAML\Module\accounting\Services\AlertsBag\Alert;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\AlertsBag\Alert
+ */
+class AlertTest extends TestCase
+{
+    public function testCanInstantiateAlert(): void
+    {
+        $alert = new Alert('message', 'level');
+        $this->assertSame('message', $alert->getMessage());
+        $this->assertSame('level', $alert->getLevel());
+    }
+}
diff --git a/tests/src/Services/AlertsBagTest.php b/tests/src/Services/AlertsBagTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a165b73aa88c23bce88ddb7fb23e8871bce8752
--- /dev/null
+++ b/tests/src/Services/AlertsBagTest.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Services\AlertsBag;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Session;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\AlertsBag
+ */
+class AlertsBagTest extends TestCase
+{
+    protected Stub $alertStub;
+    protected MockObject $sessionMock;
+
+    protected function setUp(): void
+    {
+        $this->alertStub = $this->createStub(AlertsBag\Alert::class);
+        $this->sessionMock = $this->createMock(Session::class);
+    }
+
+    public function testCanConstruct(): void
+    {
+        $this->assertInstanceOf(AlertsBag::class, new AlertsBag($this->sessionMock));
+    }
+
+    public function testIsEmpty(): void
+    {
+        $this->sessionMock->method('getData')->willReturn([]);
+        $this->sessionMock->expects($this->never())->method('setData');
+
+        $this->assertFalse((new AlertsBag($this->sessionMock))->isNotEmpty());
+    }
+
+    public function testIsNotEmpty(): void
+    {
+        $this->sessionMock->method('getData')->willReturn([$this->alertStub]);
+        $this->sessionMock->expects($this->never())->method('setData');
+
+        $this->assertTrue((new AlertsBag($this->sessionMock))->isNotEmpty());
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testGetAll(): void
+    {
+        $alerts = [$this->alertStub];
+        $this->sessionMock->method('getData')->willReturn($alerts);
+        $this->sessionMock->expects($this->once())->method('setData');
+
+        $this->assertSame($alerts, (new AlertsBag($this->sessionMock))->getAll());
+    }
+
+    public function testGetAllThrowsForInvalidData(): void
+    {
+        $this->sessionMock->method('getData')->willReturn('invalid');
+        $this->sessionMock->expects($this->never())->method('setData');
+
+        $this->expectException(Exception::class);
+        (new AlertsBag($this->sessionMock))->getAll();
+    }
+
+    public function testPutCallsSetDataOnSession(): void
+    {
+        $this->sessionMock->expects($this->once())->method('setData');
+        (new AlertsBag($this->sessionMock))->put($this->alertStub);
+    }
+}
diff --git a/tests/src/Services/CsrfTokenTest.php b/tests/src/Services/CsrfTokenTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d05b570bc89651ebfe28880e94d5be5c04df7788
--- /dev/null
+++ b/tests/src/Services/CsrfTokenTest.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Services\CsrfToken;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Session;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\CsrfToken
+ */
+class CsrfTokenTest extends TestCase
+{
+    protected MockObject $sessionMock;
+    protected Stub $helpersManagerStub;
+
+    protected function setUp(): void
+    {
+        $this->sessionMock = $this->createMock(Session::class);
+        $this->helpersManagerStub = $this->createStub(HelpersManager::class);
+    }
+
+    protected function prepareMockedInstance(): CsrfToken
+    {
+        return new CsrfToken($this->sessionMock, $this->helpersManagerStub);
+    }
+
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(CsrfToken::class, $this->prepareMockedInstance());
+    }
+
+    public function testCanGet(): void
+    {
+        $this->sessionMock->method('getData')->willReturn('sample');
+
+        $this->assertSame($this->prepareMockedInstance()->get(), 'sample');
+    }
+
+    public function testCanValidate(): void
+    {
+        $this->sessionMock->method('getData')->willReturn('sample');
+
+        $this->assertTrue($this->prepareMockedInstance()->validate('sample'));
+        $this->assertFalse($this->prepareMockedInstance()->validate('invalid'));
+    }
+}
diff --git a/tests/src/Services/HelpersManagerTest.php b/tests/src/Services/HelpersManagerTest.php
old mode 100644
new mode 100755
index aad618084fdde23b22033c93d8930b73a1f8a8e8..5d9803de93ea5b905bae248795e4d78969171512
--- a/tests/src/Services/HelpersManagerTest.php
+++ b/tests/src/Services/HelpersManagerTest.php
@@ -16,6 +16,7 @@ use SimpleSAML\Module\accounting\Helpers\Routes;
 use SimpleSAML\Module\accounting\Helpers\Network;
 use SimpleSAML\Module\accounting\Helpers\ProviderResolver;
 use SimpleSAML\Module\accounting\Helpers\Random;
+use SimpleSAML\Module\accounting\Helpers\SspModule;
 use SimpleSAML\Module\accounting\Services\HelpersManager;
 use PHPUnit\Framework\TestCase;
 
@@ -48,5 +49,6 @@ class HelpersManagerTest extends TestCase
             $helpersManager->getAuthenticationEventStateResolver()
         );
         $this->assertInstanceOf(ProviderResolver::class, $helpersManager->getProviderResolver());
+        $this->assertInstanceOf(SspModule::class, $helpersManager->getSspModule());
     }
 }
diff --git a/tests/src/Services/JobRunner/RateLimiterTest.php b/tests/src/Services/JobRunner/RateLimiterTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Services/JobRunner/StateTest.php b/tests/src/Services/JobRunner/StateTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Services/JobRunnerTest.php b/tests/src/Services/JobRunnerTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Services/LoggerServiceTest.php b/tests/src/Services/LoggerServiceTest.php
old mode 100644
new mode 100755
diff --git a/tests/src/Services/MenuManager/MenuItemTest.php b/tests/src/Services/MenuManager/MenuItemTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c0bdc97b28209174dc91f4f9a3ef8123ecb1499
--- /dev/null
+++ b/tests/src/Services/MenuManager/MenuItemTest.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services\MenuManager;
+
+use SimpleSAML\Module\accounting\Services\MenuManager\MenuItem;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\MenuManager\MenuItem
+ */
+class MenuItemTest extends TestCase
+{
+    public function testCanInstantiateMenuItem(): void
+    {
+        $menuItem = new MenuItem('hrefPath', 'label', 'iconAssetPath');
+        $this->assertSame('hrefPath', $menuItem->getHrefPath());
+        $this->assertSame('label', $menuItem->getLabel());
+        $this->assertSame('iconAssetPath', $menuItem->getIconAssetPath());
+    }
+}
diff --git a/tests/src/Services/MenuManagerTest.php b/tests/src/Services/MenuManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..219ba1c3a329637c2075a3a1b58271342393ed4f
--- /dev/null
+++ b/tests/src/Services/MenuManagerTest.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services;
+
+use PHPUnit\Framework\MockObject\Stub;
+use SimpleSAML\Module\accounting\Services\MenuManager;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\MenuManager
+ */
+class MenuManagerTest extends TestCase
+{
+    protected Stub $menuItemStub;
+
+    protected function setUp(): void
+    {
+        $this->menuItemStub = $this->createStub(MenuManager\MenuItem::class);
+    }
+
+    public function testCanWorkWithItems(): void
+    {
+        $menuManager = new MenuManager();
+
+        $this->assertEmpty($menuManager->getItems());
+        $menuManager->addItem($this->menuItemStub);
+        $this->assertNotEmpty($menuManager->getItems());
+        $this->assertCount(1, $menuManager->getItems());
+        $menuManager->addItems($this->menuItemStub, $this->menuItemStub);
+        $this->assertCount(3, $menuManager->getItems());
+    }
+}
diff --git a/tests/src/Services/SspModuleManagerTest.php b/tests/src/Services/SspModuleManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..caa8be1e054742e2acb5fbac7c258b8fcedb4447
--- /dev/null
+++ b/tests/src/Services/SspModuleManagerTest.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services;
+
+use PHPUnit\Framework\MockObject\Stub;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Module\accounting\Services\SspModuleManager;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\accounting\SspModule\Oidc;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\SspModuleManager
+ * @uses \SimpleSAML\Module\accounting\SspModule\Oidc
+ */
+class SspModuleManagerTest extends TestCase
+{
+    protected Stub $loggerStub;
+    protected Stub $helpersMangerStub;
+
+    protected function setUp(): void
+    {
+        $this->loggerStub = $this->createStub(LoggerInterface::class);
+        $this->helpersMangerStub = $this->createStub(HelpersManager::class);
+    }
+
+    public function testGet(): void
+    {
+        $sspModuleManager = new SspModuleManager($this->loggerStub, $this->helpersMangerStub);
+
+        // By default, OIDC module will try to connect to the database, which we will not mock in module manager.
+        $this->expectException(\Throwable::class);
+        $sspModuleManager->getOidc();
+    }
+}
diff --git a/tests/src/Services/TrackerResolverTest.php b/tests/src/Services/TrackerResolverTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c20273f9dc3136e1e831315dffa662e7c46cf89c
--- /dev/null
+++ b/tests/src/Services/TrackerResolverTest.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\Services;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder;
+use SimpleSAML\Module\accounting\Data\Providers\Interfaces\DataProviderInterface;
+use SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder;
+use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface;
+use SimpleSAML\Module\accounting\Exceptions\Exception;
+use SimpleSAML\Module\accounting\ModuleConfiguration;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Module\accounting\Services\TrackerResolver;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\Services\TrackerResolver
+ */
+class TrackerResolverTest extends TestCase
+{
+    protected MockObject $moduleConfigurationMock;
+    protected MockObject $loggerMock;
+    protected MockObject $helpersManagerMock;
+    protected MockObject $dataProviderBuilderMock;
+    protected MockObject $dataTrackerBuilderMock;
+    protected MockObject $dataProviderMock;
+    protected MockObject $dataTrackerMock;
+
+    protected function setUp(): void
+    {
+        $this->moduleConfigurationMock = $this->createMock(ModuleConfiguration::class);
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->helpersManagerMock = $this->createMock(HelpersManager::class);
+        $this->dataProviderBuilderMock = $this->createMock(DataProviderBuilder::class);
+        $this->dataTrackerBuilderMock = $this->createMock(DataTrackerBuilder::class);
+
+        $this->dataProviderMock = $this->createMock(DataProviderInterface::class);
+        $this->dataTrackerMock = $this->createMock(DataTrackerInterface::class);
+    }
+
+    public function testCanConstruct(): void
+    {
+        $this->assertInstanceOf(
+            TrackerResolver::class,
+            new TrackerResolver(
+                $this->moduleConfigurationMock,
+                $this->loggerMock,
+                $this->helpersManagerMock,
+                $this->dataProviderBuilderMock,
+                $this->dataTrackerBuilderMock
+            )
+        );
+    }
+
+    /**
+     * @throws Exception
+     */
+    public function testFromModuleConfiguration(): void
+    {
+        $this->moduleConfigurationMock->method('getProviderClasses')->willReturn(
+            [DataProviderInterface::class]
+        );
+        $this->dataProviderMock->method('getTracker')->willReturn($this->dataTrackerMock);
+        $this->dataProviderBuilderMock->method('build')->willReturn($this->dataProviderMock);
+        $this->moduleConfigurationMock->method('getAdditionalTrackers')->willReturn(
+            [DataTrackerInterface::class]
+        );
+        $this->dataTrackerBuilderMock->method('build')->willReturn($this->dataTrackerMock);
+
+        $trackerResolver = new TrackerResolver(
+            $this->moduleConfigurationMock,
+            $this->loggerMock,
+            $this->helpersManagerMock,
+            $this->dataProviderBuilderMock,
+            $this->dataTrackerBuilderMock
+        );
+
+        $this->assertCount(2, $trackerResolver->fromModuleConfiguration());
+    }
+}
diff --git a/tests/src/SspModule/OidcTest.php b/tests/src/SspModule/OidcTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd3b72ccadb4bd348a30b1801de5fa42e7adc3c9
--- /dev/null
+++ b/tests/src/SspModule/OidcTest.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace SimpleSAML\Test\Module\accounting\SspModule;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use SimpleSAML\Database;
+use SimpleSAML\Module\accounting\Helpers\DateTime;
+use SimpleSAML\Module\accounting\Helpers\SspModule;
+use SimpleSAML\Module\accounting\Services\HelpersManager;
+use SimpleSAML\Module\accounting\SspModule\Oidc;
+use PHPUnit\Framework\TestCase;
+use SimpleSAML\Module\oidc\Services\Container;
+
+/**
+ * @covers \SimpleSAML\Module\accounting\SspModule\Oidc
+ */
+class OidcTest extends TestCase
+{
+    protected MockObject $loggerMock;
+    protected MockObject $helpersManagerMock;
+    protected MockObject $containerMock;
+    protected MockObject $databaseMock;
+    protected MockObject $sspModuleMock;
+    protected MockObject $dateTimeMock;
+    protected MockObject $pdoStatementMock;
+
+    protected function setUp(): void
+    {
+        $this->loggerMock = $this->createMock(LoggerInterface::class);
+        $this->helpersManagerMock = $this->createMock(HelpersManager::class);
+        $this->containerMock = $this->createMock(Container::class);
+        $this->databaseMock = $this->createMock(Database::class);
+
+        $this->sspModuleMock = $this->createMock(SspModule::class);
+        $this->dateTimeMock = $this->createMock(DateTime::class);
+        $this->pdoStatementMock = $this->createMock(\PDOStatement::class);
+    }
+
+    protected function prepareMockedInstance(): Oidc
+    {
+        $this->helpersManagerMock->method('getSspModule')->willReturn($this->sspModuleMock);
+        $this->helpersManagerMock->method('getDateTime')->willReturn($this->dateTimeMock);
+
+        return new Oidc(
+            $this->loggerMock,
+            $this->helpersManagerMock,
+            $this->containerMock,
+            $this->databaseMock
+        );
+    }
+
+    public function testCanCreateInstance(): void
+    {
+        $this->assertInstanceOf(
+            Oidc::class,
+            $this->prepareMockedInstance()
+        );
+    }
+
+    public function testIsEnabled(): void
+    {
+        $this->sspModuleMock->method('isEnabled')->willReturn(true);
+        $this->assertTrue($this->prepareMockedInstance()->isEnabled());
+    }
+
+    public function testIsNotEnabled(): void
+    {
+        $this->sspModuleMock->method('isEnabled')->willReturn(false);
+        $this->assertFalse($this->prepareMockedInstance()->isEnabled());
+    }
+
+    public function testCanGetContainer(): void
+    {
+        $this->assertInstanceOf(
+            Container::class,
+            $this->prepareMockedInstance()->getContainer()
+        );
+    }
+
+    public function testGetUsersAccessTokens(): void
+    {
+        $this->pdoStatementMock->method('fetchAll')->willReturn(['sample']);
+        $this->databaseMock->method('read')->willReturn($this->pdoStatementMock);
+
+        $this->assertNotEmpty(
+            $this->prepareMockedInstance()->getUsersAccessTokens('userId', ['clientId'])
+        );
+    }
+
+    public function testGetUsersAccessTokensEmpty(): void
+    {
+        $this->databaseMock->expects($this->once())->method('read');
+        $this->assertEmpty(
+            $this->prepareMockedInstance()->getUsersAccessTokens('userId', ['clientId'])
+        );
+    }
+
+    public function testRevokeUsersAccessToken(): void
+    {
+        $this->databaseMock->expects($this->once())->method('write');
+        $this->prepareMockedInstance()->revokeUsersAccessToken('userId', 'accessTokenId');
+    }
+
+    public function testGetUsersRefreshTokens(): void
+    {
+        $this->pdoStatementMock->method('fetchAll')->willReturn(['sample']);
+        $this->databaseMock->method('read')->willReturn($this->pdoStatementMock);
+
+        $this->assertNotEmpty(
+            $this->prepareMockedInstance()->getUsersRefreshTokens('userId', ['clientId'])
+        );
+    }
+
+    public function testGetUsersRefreshTokensEmpty(): void
+    {
+        $this->databaseMock->expects($this->once())->method('read');
+        $this->assertEmpty(
+            $this->prepareMockedInstance()->getUsersRefreshTokens('userId', ['clientId'])
+        );
+    }
+
+    public function testRevokeUsersRefreshToken(): void
+    {
+        $this->databaseMock->expects($this->once())->method('write');
+        $this->prepareMockedInstance()->revokeUsersRefreshToken('userId', 'refreshTokenId');
+    }
+
+    public function testGetClients(): void
+    {
+        $this->pdoStatementMock->method('fetchAll')->willReturn(['sample']);
+        $this->databaseMock->method('read')->willReturn($this->pdoStatementMock);
+
+        $this->assertNotEmpty(
+            $this->prepareMockedInstance()->getClients(['clientId'])
+        );
+    }
+
+    public function testGetClientsEmpty(): void
+    {
+        $this->databaseMock->expects($this->once())->method('read');
+        $this->assertEmpty(
+            $this->prepareMockedInstance()->getClients(['clientId'])
+        );
+    }
+}