diff --git a/README.md b/README.md index 7061c715d625a16274298c2bdaad36671942c575..4856b532858b44194678bd11f70aa5ecf6dadcdd 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,10 @@ [](https://github.com/cicnavi/simplesamlphp-module-accounting/actions/workflows/test.yml) -* TODO -* Consider config to enable / disable storing attribute values -* IG Binary - consider using for data serialization - # simplesamlphp-module-accounting -SimpleSAMLphp module providing user accounting functionality using SimpleSAMLphp authentication processing -filters feature. +SimpleSAMLphp module providing user "Profile Page" and accounting functionality using SimpleSAMLphp authentication +processing filters feature. ## Features - Enables tracking of authentication events, synchronously (during authentication event) or @@ -21,10 +17,9 @@ backend storages can be added by following proper interfaces. - Comes with setup procedure which sets up backend storage. In case of Doctrine DBAL this means running SQL migrations which create proper tables in configured database. - Each backend storage connection can have master and slave configuration (master for writing, slave for reading) -- Has "trackers" which persist authentication data to backend storage. Currently, there is one default Doctrine DBAL -compatible tracker which stores authentication events, versioned Idp and SP metadata, and versioned user attributes. -Other trackers can be added by following proper interfaces. -- Trackers can run in two ways: +- Has tracking functionality available which persist authentication data to backend storage. Currently, module can +track connected services and authentication events. Other trackers can be added by following proper interfaces. +- Tracking can run in two ways: - synchronously - authentication data persisted during authentication event typically with multiple queries / inserts / updates to backend storage. - asynchronously - only authentication event job is persisted during authentication event @@ -62,24 +57,33 @@ to the SimpleSAMLphp config directory: cp modules/accounting/config-templates/module_accounting.php config/ ``` -Next step is configuring available options in file config/module_accounting.php. Each option has an explanation, +Next step is to configure available options in file config/module_accounting.php. Each option has an explanation, however, the description of the overall concept follows. -For accounting processing, the default data tracker and data provider class must be set. This tracker will be used -to persist tracking data and also to show data in the SimpleSAMLphp user interface. Here is an example excerpt -of setting the Doctrine DBAL compatible tracker class which will store authentication events, versioned Idp -and SP metadata, and versioned user attributes in a relational database: +Module can be configured to only show current user data, with no accounting taking place. However, module can be +configured to track the following data: +* Connected organizations - by setting the class ModuleConfiguration::OPTION_PROVIDER_FOR_CONNECTED_SERVICES option. +* Activity - by setting the class for ModuleConfiguration::OPTION_PROVIDER_FOR_ACTIVITY option. + +Module comes with some Doctrine DBAL capable classes which can be used for those purposes. Here is an example config +excerpt which will enable storing current (latest) data for connected services and versioned data +for authentication events, including versioned Idp and SP metadata, and versioned user attributes: ```php use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Trackers; +use SimpleSAML\Module\accounting\Data\Trackers; +use SimpleSAML\Module\accounting\Data\Providers; // ... -ModuleConfiguration::OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER => - Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class, +ModuleConfiguration::OPTION_PROVIDER_FOR_CONNECTED_SERVICES => + Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider::class, +ModuleConfiguration::OPTION_PROVIDER_FOR_ACTIVITY => + Providers\Activity\DoctrineDbal\VersionedDataProvider::class, // ... ``` +### Processing type + The deployer can choose if the accounting processing will be performed during authentication event (synchronously), or in a separate process (asynchronously), for example: @@ -145,8 +149,14 @@ Only one job runner instance can run at given point in time. By maintaining inte if there is another job runner active. If yes, the latter will simply exit and let the active job runner do its work. This way one is free to invoke the cron tag at any time, since only one job runner will ever be active. -## TODO -- [ ] Translation +## OpendID Connect integration +This module can also be used as an authentication processing filter for OIDC module +https://github.com/simplesamlphp/simplesamlphp-module-oidc, meaning it can also track OIDC authentication events, +Also, if connected services option is enabled, a user will be able to revoke any active access / refresh tokens +for particular service in the user interface. + +Accounting authentication processing filter can be added in the OIDC module configuration, as per OIDC module +documentation. ## Tests To run phpcs, psalm and phpunit: diff --git a/composer.lock b/composer.lock index 76ddc7a1a19e4007287036be7a8c1e191051092a..49f607c8ab938bb25311187cf7d9682bfd92beab 100644 --- a/composer.lock +++ b/composer.lock @@ -154,16 +154,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.1", + "version": "3.6.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "57815c7bbcda3cd18871d253c1dd8cbe56f8526e" + "reference": "b4bd1cfbd2b916951696d82e57d054394d84864c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/57815c7bbcda3cd18871d253c1dd8cbe56f8526e", - "reference": "57815c7bbcda3cd18871d253c1dd8cbe56f8526e", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/b4bd1cfbd2b916951696d82e57d054394d84864c", + "reference": "b4bd1cfbd2b916951696d82e57d054394d84864c", "shasum": "" }, "require": { @@ -179,9 +179,9 @@ "doctrine/coding-standard": "11.1.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.3", + "phpstan/phpstan": "1.10.9", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.4", + "phpunit/phpunit": "9.6.6", "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", @@ -246,7 +246,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.1" + "source": "https://github.com/doctrine/dbal/tree/3.6.2" }, "funding": [ { @@ -262,7 +262,7 @@ "type": "tidelift" } ], - "time": "2023-03-02T19:26:24+00:00" + "time": "2023-04-14T07:25:38+00:00" }, { "name": "doctrine/deprecations", @@ -2196,22 +2196,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -2304,7 +2304,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.5.1" }, "funding": [ { @@ -2320,7 +2320,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2023-04-17T16:30:08+00:00" }, { "name": "guzzlehttp/promises", @@ -2408,22 +2408,22 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.4", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf" + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", - "reference": "3cf1b6d4f0c820a2cf8bcaec39fc698f3443b5cf", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "provide": { @@ -2443,9 +2443,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { @@ -2507,7 +2504,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.4" + "source": "https://github.com/guzzle/psr7/tree/2.5.0" }, "funding": [ { @@ -2523,7 +2520,7 @@ "type": "tidelift" } ], - "time": "2023-03-09T13:19:02+00:00" + "time": "2023-04-17T16:11:26+00:00" }, { "name": "justinrainbow/json-schema", @@ -3333,16 +3330,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v4.1.0", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f" + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", - "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956", + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956", "shasum": "" }, "require": { @@ -3378,9 +3375,9 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0" }, - "time": "2022-12-08T20:46:14+00:00" + "time": "2023-04-09T17:37:40+00:00" }, { "name": "nette/component-model", @@ -4149,16 +4146,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.17.1", + "version": "1.20.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "d3753fcb3abc6f78f5de6f72153d4b9c99c72dee" + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/d3753fcb3abc6f78f5de6f72153d4b9c99c72dee", - "reference": "d3753fcb3abc6f78f5de6f72153d4b9c99c72dee", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2", + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2", "shasum": "" }, "require": { @@ -4188,9 +4185,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.17.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3" }, - "time": "2023-04-04T11:11:22+00:00" + "time": "2023-04-25T09:01:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4512,16 +4509,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.6", + "version": "9.6.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115" + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115", - "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", + "reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2", "shasum": "" }, "require": { @@ -4595,7 +4592,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.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7" }, "funding": [ { @@ -4611,7 +4608,7 @@ "type": "tidelift" } ], - "time": "2023-03-27T11:43:46+00:00" + "time": "2023-04-14T08:58:40+00:00" }, { "name": "psr/container", @@ -4713,21 +4710,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4747,7 +4744,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -4759,27 +4756,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client/tree/1.0.2" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-04-10T20:12:12+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4799,7 +4796,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -4814,9 +4811,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", @@ -4873,21 +4870,21 @@ }, { "name": "psr/http-server-handler", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-server-handler.git", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7" + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/aff2f80e33b7f026ec96bb42f63242dc50ffcae7", - "reference": "aff2f80e33b7f026ec96bb42f63242dc50ffcae7", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", "shasum": "" }, "require": { "php": ">=7.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4907,7 +4904,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP server-side request handler", @@ -4923,10 +4920,9 @@ "server" ], "support": { - "issues": "https://github.com/php-fig/http-server-handler/issues", - "source": "https://github.com/php-fig/http-server-handler/tree/master" + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" }, - "time": "2018-10-30T16:46:14+00:00" + "time": "2023-04-10T20:06:20+00:00" }, { "name": "ralouphie/getallheaders", diff --git a/config-templates/module_accounting.php b/config-templates/module_accounting.php index e1755cd560347486bb979db7e4450007ef53d6c0..a1cc2e7bdc502571235898285f7ba1c48616c2b9 100644 --- a/config-templates/module_accounting.php +++ b/config-templates/module_accounting.php @@ -2,10 +2,10 @@ declare(strict_types=1); +use SimpleSAML\Module\accounting\Data\Providers; +use SimpleSAML\Module\accounting\Data\Stores; +use SimpleSAML\Module\accounting\Data\Trackers; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Providers; -use SimpleSAML\Module\accounting\Stores; -use SimpleSAML\Module\accounting\Trackers; $config = [ /** @@ -56,28 +56,53 @@ $config = [ //Stores\Jobs\PhpRedis\RedisStore::class, /** - * Default data tracker and provider to be used for accounting and as a source for data display in SSP UI. - * This class must implement Trackers\Interfaces\AuthenticationDataTrackerInterface and - * Providers\Interfaces\AuthenticationDataProviderInterface + * Providers + * + * VersionedDataProvider classes are used to fetch data about users in order to show it in users profile page UI. + * Each provider can also include tracking capability, which will be triggered / used automatically. + * + * Connected services provider is a class which will be used to provide summary data about services that user + * has authenticated at, including authentication count for particular service, and the first and last + * authentication time. For OIDC services (Relying Parties, RPs), user can also revoke active tokens. + * + * Given class must implement interface Providers\Interfaces\ConnectedServicesInterface. + * + * This option can be set to null, meaning no connected services tracking will take place. + */ + 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. + * + */ + Providers\ConnectedServices\DoctrineDbal\CurrentDataProvider::class, + //Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider::class, + + /** + * Activity provider is a class which will be used to provide list of authentication events which includes info + * about the services, user data sent to the service, and the time of authentication. + * + * Given class must implement interface Providers\Interfaces\ActivityInterfaceData. + * + * This option can be set to null, meaning no activity tracking will take place. */ - ModuleConfiguration::OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER => + ModuleConfiguration::OPTION_PROVIDER_FOR_ACTIVITY => /** - * Track each authentication event for idp / sp / user combination, and any change in idp / sp metadata or - * released user attributes. Each authentication event record will have data used and released at the - * time of the authentication event (versioned idp / sp / user data). This tracker can also be - * used as an authentication data provider. It expects Doctrine DBAL compatible connection - * to be set below. Internally it uses store class - * Stores\Data\DoctrineDbal\DoctrineDbal\Versioned\Store::class. + * 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. */ - Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class, + Providers\Activity\DoctrineDbal\VersionedDataProvider::class, /** - * Additional trackers to run besides default data tracker. These trackers will typically only process and - * persist authentication data to proper data store, and won't be used to display data in SSP UI. - * These tracker classes must implement Trackers\Interfaces\AuthenticationDataTrackerInterface. + * Trackers + * + * List of additional tracker classes to be used for accounting (processing and persisting authentication data). + * These classes must implement Trackers\Interfaces\DataTrackerInterface */ ModuleConfiguration::OPTION_ADDITIONAL_TRACKERS => [ - // tracker-class + // some-tracker-class ], /** @@ -90,14 +115,26 @@ $config = [ */ ModuleConfiguration::OPTION_CLASS_TO_CONNECTION_MAP => [ /** - * Connection key to be used by jobs store class. + * Jobs store connection keys. */ Stores\Jobs\DoctrineDbal\Store::class => 'doctrine_dbal_pdo_mysql', Stores\Jobs\PhpRedis\RedisStore::class => 'phpredis_class_redis', /** - * Connection key to be used by this data tracker and provider. + * Data provider connection keys. */ - Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class => [ + Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider::class => [ + ModuleConfiguration\ConnectionType::MASTER => 'doctrine_dbal_pdo_mysql', + ModuleConfiguration\ConnectionType::SLAVE => [ + 'doctrine_dbal_pdo_mysql', + ], + ], + Providers\ConnectedServices\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 => [ 'doctrine_dbal_pdo_mysql', @@ -177,7 +214,7 @@ $config = [ ModuleConfiguration::OPTION_JOB_RUNNER_SHOULD_PAUSE_AFTER_NUMBER_OF_JOBS_PROCESSED => 10, /** - * Tracker data retention policy. + * VersionedDataTracker data retention policy. * * Determines how long the tracked data will be stored. If null, data will be stored indefinitely. Otherwise, it * can be set as a duration for DateInterval, examples being below. For this to work, a cron tag must also @@ -198,8 +235,9 @@ $config = [ ModuleConfiguration::OPTION_CRON_TAG_FOR_JOB_RUNNER => 'accounting_job_runner', /** - * Tracker data retention policy tag designates the cron tag to use for enforcing data retention policy. Make sure - * to add this tag to the cron module configuration if data retention policy is different from null. + * VersionedDataTracker data retention policy tag designates the cron tag to use for enforcing data retention + * policy. Make sure to add this tag to the cron module configuration if data retention policy is different + * from null. */ ModuleConfiguration::OPTION_CRON_TAG_FOR_TRACKER_DATA_RETENTION_POLICY => 'accounting_tracker_data_retention_policy', diff --git a/hooks/hook_cron.php b/hooks/hook_cron.php index faf0f652a4dd9f244c2b993b8142b9d10776935c..d045e20d903bbf7886e36f378e7b8a5aefcc1c2b 100644 --- a/hooks/hook_cron.php +++ b/hooks/hook_cron.php @@ -4,11 +4,11 @@ declare(strict_types=1); use Psr\Log\LoggerInterface; use SimpleSAML\Configuration; +use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; use SimpleSAML\Module\accounting\Services\JobRunner; -use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; +use SimpleSAML\Module\accounting\Services\TrackerResolver; /** * @param array $cronInfo @@ -51,7 +51,7 @@ function accounting_hook_cron(array &$cronInfo): void } /** - * Tracker data retention policy handling. + * VersionedDataTracker data retention policy handling. */ $cronTagForTrackerDataRetentionPolicy = $moduleConfiguration->getCronTagForTrackerDataRetentionPolicy(); try { @@ -82,16 +82,11 @@ function handleDataRetentionPolicy( HelpersManager $helpersManager, DateInterval $retentionPolicy ): void { - // Handle default data tracker and provider - (new AuthenticationDataTrackerBuilder($moduleConfiguration, $logger, $helpersManager)) - ->build($moduleConfiguration->getDefaultDataTrackerAndProviderClass()) - ->enforceDataRetentionPolicy($retentionPolicy); - $additionalTrackers = $moduleConfiguration->getAdditionalTrackers(); + $trackers = (new TrackerResolver($moduleConfiguration, $logger, $helpersManager))->fromModuleConfiguration(); - foreach ($additionalTrackers as $tracker) { - (new AuthenticationDataTrackerBuilder($moduleConfiguration, $logger, $helpersManager)) - ->build($tracker) - ->enforceDataRetentionPolicy($retentionPolicy); + foreach ($trackers as $trackerClass => $tracker) { + $logger->info('Applying data retention policy for class ' . $trackerClass); + $tracker->enforceDataRetentionPolicy($retentionPolicy); } } \ No newline at end of file diff --git a/routing/routes/routes.yml b/routing/routes/routes.yml index 1d2d429e693ff02b3564bab9426e50b047603859..dcd41bc49956a6cac60dee067200cdddf49aab1e 100644 --- a/routing/routes/routes.yml +++ b/routing/routes/routes.yml @@ -15,15 +15,6 @@ accounting-user-activity: path: /user/activity controller: SimpleSAML\Module\accounting\Http\Controllers\User\Profile::activity -accounting-user-oidc-tokens: - path: /user/oidc-tokens - controller: SimpleSAML\Module\accounting\Http\Controllers\User\Profile::oidcTokens - -accounting-user-oidc-token-revoke: - path: /user/oidc-tokens/revoke - controller: SimpleSAML\Module\accounting\Http\Controllers\User\Profile::oidcTokenRevoke - methods: POST - accounting-user-oidc-token-revoke-xhr: path: /user/oidc-tokens/revoke-xhr controller: SimpleSAML\Module\accounting\Http\Controllers\User\Profile::oidcTokenRevokeXhr diff --git a/src/Auth/Process/Accounting.php b/src/Auth/Process/Accounting.php index ecb6102d078a9ff9ba8ec86099b79afb0105ad00..c731907ef560a343eafc753b1d3f033bab4ad8c5 100644 --- a/src/Auth/Process/Accounting.php +++ b/src/Auth/Process/Accounting.php @@ -6,13 +6,13 @@ namespace SimpleSAML\Module\accounting\Auth\Process; use Psr\Log\LoggerInterface; use SimpleSAML\Auth\ProcessingFilter; +use SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder; use SimpleSAML\Module\accounting\Entities\Authentication\Event; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; +use SimpleSAML\Module\accounting\Services\TrackerResolver; use Throwable; class Accounting extends ProcessingFilter @@ -20,8 +20,8 @@ class Accounting extends ProcessingFilter protected ModuleConfiguration $moduleConfiguration; protected JobsStoreBuilder $jobsStoreBuilder; protected LoggerInterface $logger; - protected AuthenticationDataTrackerBuilder $authenticationDataTrackerBuilder; protected HelpersManager $helpersManager; + protected TrackerResolver $trackerResolver; /** * @param array $config @@ -30,7 +30,7 @@ class Accounting extends ProcessingFilter * @param LoggerInterface|null $logger * @param HelpersManager|null $helpersManager * @param JobsStoreBuilder|null $jobsStoreBuilder - * @param AuthenticationDataTrackerBuilder|null $authenticationDataTrackerBuilder + * @param TrackerResolver|null $trackerResolver */ public function __construct( array &$config, @@ -39,7 +39,7 @@ class Accounting extends ProcessingFilter LoggerInterface $logger = null, HelpersManager $helpersManager = null, JobsStoreBuilder $jobsStoreBuilder = null, - AuthenticationDataTrackerBuilder $authenticationDataTrackerBuilder = null + TrackerResolver $trackerResolver = null ) { parent::__construct($config, $reserved); @@ -49,8 +49,11 @@ class Accounting extends ProcessingFilter $this->jobsStoreBuilder = $jobsStoreBuilder ?? new JobsStoreBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager); - $this->authenticationDataTrackerBuilder = $authenticationDataTrackerBuilder ?? - new AuthenticationDataTrackerBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager); + $this->trackerResolver = $trackerResolver ?? new TrackerResolver( + $this->moduleConfiguration, + $this->logger, + $this->helpersManager + ); } /** @@ -58,26 +61,28 @@ class Accounting extends ProcessingFilter */ public function process(array &$state): void { - try { - $resolvedState = $this->helpersManager->getAuthenticationEventStateResolver()->fromStateArray($state); + $this->logger->debug('Accounting started.', $state); - $authenticationEvent = new Event($resolvedState); + try { + $authenticationEvent = new Event( + $this->helpersManager->getAuthenticationEventStateResolver()->fromStateArray($state) + ); if ($this->isAccountingProcessingTypeAsynchronous()) { // Only create authentication event job for later processing... + $this->logger->debug('Accounting type is asynchronous, creating job for later processing.'); $this->createAuthenticationEventJob($authenticationEvent); return; } - // Accounting type is synchronous, so do the processing right away... - $configuredTrackers = array_merge( - [$this->moduleConfiguration->getDefaultDataTrackerAndProviderClass()], - $this->moduleConfiguration->getAdditionalTrackers() - ); + $this->logger->debug('Accounting type is synchronous, processing now.'); - foreach ($configuredTrackers as $tracker) { - ($this->authenticationDataTrackerBuilder->build($tracker))->process($authenticationEvent); + foreach ($this->trackerResolver->fromModuleConfiguration() as $trackerClass => $tracker) { + $this->logger->debug(sprintf('Processing tracker for class %s.', $trackerClass)); + $tracker->process($authenticationEvent); } + + $this->logger->debug('Finished with tracker processing.'); } catch (Throwable $exception) { $message = sprintf('Accounting error, skipping... Error was: %s.', $exception->getMessage()); $this->logger->error($message, $state); diff --git a/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProvider.php b/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..e0b6f63d5b6abcceb4f281f203c7332374a7d6e3 --- /dev/null +++ b/src/Data/Providers/Activity/DoctrineDbal/VersionedDataProvider.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\Versioned\Store; +use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker; +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 VersionedDataProvider 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 VersionedDataTracker( + $this->moduleConfiguration, + $this->logger, + ModuleConfiguration\ConnectionType::MASTER, + ); + } +} diff --git a/src/Data/Providers/Builders/DataProviderBuilder.php b/src/Data/Providers/Builders/DataProviderBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..7bba072c97be2b569875315bab0c6ea85af885bf --- /dev/null +++ b/src/Data/Providers/Builders/DataProviderBuilder.php @@ -0,0 +1,105 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Providers\Builders; + +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\ActivityInterface; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\ConnectedServicesInterface; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\DataProviderInterface; +use SimpleSAML\Module\accounting\Exceptions\Exception; +use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; +use SimpleSAML\Module\accounting\ModuleConfiguration; +use SimpleSAML\Module\accounting\Services\HelpersManager; +use Throwable; + +class DataProviderBuilder +{ + protected ModuleConfiguration $moduleConfiguration; + protected LoggerInterface $logger; + protected HelpersManager $helpersManager; + + public function __construct( + ModuleConfiguration $moduleConfiguration, + LoggerInterface $logger, + HelpersManager $helpersManager + ) { + $this->moduleConfiguration = $moduleConfiguration; + $this->logger = $logger; + $this->helpersManager = $helpersManager; + } + + /** + * @throws Exception + */ + public function build( + string $class, + string $connectionType = ModuleConfiguration\ConnectionType::SLAVE + ): DataProviderInterface { + try { + // Make sure that the class implements proper interface + if (!is_subclass_of($class, DataProviderInterface::class)) { + $message = sprintf( + 'Class %s does not implement interface %s.', + $class, + DataProviderInterface::class + ); + throw new UnexpectedValueException($message); + } + + // Build... + /** @var DataProviderInterface $provider */ + $provider = $this->helpersManager->getInstanceBuilderUsingModuleConfiguration()->build( + $class, + $this->moduleConfiguration, + $this->logger, + [$connectionType] + ); + } catch (Throwable $exception) { + $message = sprintf('Error building instance for class %s. Error was: %s', $class, $exception->getMessage()); + throw new Exception($message, (int)$exception->getCode(), $exception); + } + + return $provider; + } + + public function buildActivityProvider( + string $class, + string $connectionType = ModuleConfiguration\ConnectionType::SLAVE + ): ActivityInterface { + $activityProvider = $this->build($class, $connectionType); + + if (!is_subclass_of($activityProvider, ActivityInterface::class)) { + $message = sprintf( + 'Class %s does not implement interface %s.', + $class, + ActivityInterface::class + ); + throw new UnexpectedValueException($message); + } + + return $activityProvider; + } + + /** + * @throws Exception + */ + public function buildConnectedServicesProvider( + string $class, + string $connectionType = ModuleConfiguration\ConnectionType::SLAVE + ): ConnectedServicesInterface { + $connectedServicesProvider = $this->build($class, $connectionType); + + if (!is_subclass_of($connectedServicesProvider, ConnectedServicesInterface::class)) { + $message = sprintf( + 'Class %s does not implement interface %s.', + $class, + ActivityInterface::class + ); + throw new UnexpectedValueException($message); + } + + return $connectedServicesProvider; + } +} diff --git a/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProvider.php b/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..323c867f28d892d77cfb0d912b82ab25f3a68867 --- /dev/null +++ b/src/Data/Providers/ConnectedServices/DoctrineDbal/CurrentDataProvider.php @@ -0,0 +1,95 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal; + +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\ConnectedServicesInterface; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store; +use SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\CurrentDataTracker; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; +use SimpleSAML\Module\accounting\Entities\ConnectedService; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; +use SimpleSAML\Module\accounting\ModuleConfiguration; + +class CurrentDataProvider implements ConnectedServicesInterface +{ + 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 getConnectedServices(string $userIdentifier): ConnectedService\Bag + { + return $this->store->getConnectedServices($userIdentifier); + } + + /** + * @throws StoreException + */ + public function getTracker(): ?DataTrackerInterface + { + return new CurrentDataTracker( + $this->moduleConfiguration, + $this->logger, + ModuleConfiguration\ConnectionType::MASTER, + ); + } +} diff --git a/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProvider.php b/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..788da2731d09dc013ab59f1e2ec4e631730d03b2 --- /dev/null +++ b/src/Data/Providers/ConnectedServices/DoctrineDbal/VersionedDataProvider.php @@ -0,0 +1,95 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal; + +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\ConnectedServicesInterface; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store; +use SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\VersionedDataTracker; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; +use SimpleSAML\Module\accounting\Entities\ConnectedService; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; +use SimpleSAML\Module\accounting\ModuleConfiguration; + +class VersionedDataProvider implements ConnectedServicesInterface +{ + 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 getConnectedServices(string $userIdentifier): ConnectedService\Bag + { + return $this->store->getConnectedServices($userIdentifier); + } + + /** + * @throws StoreException + */ + public function getTracker(): ?DataTrackerInterface + { + return new VersionedDataTracker( + $this->moduleConfiguration, + $this->logger, + ModuleConfiguration\ConnectionType::MASTER, + ); + } +} diff --git a/src/Providers/Interfaces/AuthenticationDataProviderInterface.php b/src/Data/Providers/Interfaces/ActivityInterface.php similarity index 63% rename from src/Providers/Interfaces/AuthenticationDataProviderInterface.php rename to src/Data/Providers/Interfaces/ActivityInterface.php index 8c60297dcefb758df57af9fae386a56963cf58ef..0102c89b48cd3cde707c11769b5c0251b2f7cffd 100644 --- a/src/Providers/Interfaces/AuthenticationDataProviderInterface.php +++ b/src/Data/Providers/Interfaces/ActivityInterface.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Providers\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Providers\Interfaces; use Psr\Log\LoggerInterface; use SimpleSAML\Module\accounting\Entities\Activity; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; use SimpleSAML\Module\accounting\Interfaces\BuildableUsingModuleConfigurationInterface; +use SimpleSAML\Module\accounting\Interfaces\SetupableInterface; use SimpleSAML\Module\accounting\ModuleConfiguration; -interface AuthenticationDataProviderInterface extends BuildableUsingModuleConfigurationInterface +interface ActivityInterface extends DataProviderInterface { public static function build( ModuleConfiguration $moduleConfiguration, @@ -18,7 +18,5 @@ interface AuthenticationDataProviderInterface extends BuildableUsingModuleConfig string $connectionType = ModuleConfiguration\ConnectionType::MASTER ): self; - public function getConnectedServiceProviders(string $userIdentifier): ConnectedServiceProvider\Bag; - public function getActivity(string $userIdentifier, int $maxResults, int $firstResult): Activity\Bag; } diff --git a/src/Data/Providers/Interfaces/ConnectedServicesInterface.php b/src/Data/Providers/Interfaces/ConnectedServicesInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..fcd56afdc1acaccfe1fe64be8655793c2290f956 --- /dev/null +++ b/src/Data/Providers/Interfaces/ConnectedServicesInterface.php @@ -0,0 +1,20 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Providers\Interfaces; + +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Entities\ConnectedService; +use SimpleSAML\Module\accounting\ModuleConfiguration; + +interface ConnectedServicesInterface extends DataProviderInterface +{ + public static function build( + ModuleConfiguration $moduleConfiguration, + LoggerInterface $logger, + string $connectionType = ModuleConfiguration\ConnectionType::SLAVE + ): self; + + public function getConnectedServices(string $userIdentifier): ConnectedService\Bag; +} diff --git a/src/Data/Providers/Interfaces/DataProviderInterface.php b/src/Data/Providers/Interfaces/DataProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..cc42946278ed39796b923ab8bbf2e8b3ff007ac9 --- /dev/null +++ b/src/Data/Providers/Interfaces/DataProviderInterface.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Providers\Interfaces; + +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; +use SimpleSAML\Module\accounting\Interfaces\BuildableUsingModuleConfigurationInterface; +use SimpleSAML\Module\accounting\Interfaces\SetupableInterface; +use SimpleSAML\Module\accounting\ModuleConfiguration; + +interface DataProviderInterface extends BuildableUsingModuleConfigurationInterface, SetupableInterface +{ + public static function build( + ModuleConfiguration $moduleConfiguration, + LoggerInterface $logger, + string $connectionType = ModuleConfiguration\ConnectionType::SLAVE + ): self; + + public function getTracker(): ?DataTrackerInterface; +} diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php new file mode 100644 index 0000000000000000000000000000000000000000..149b8bca2b4f74c4707bdbdbeb1afd7e7864e7a0 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store.php @@ -0,0 +1,146 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned; + +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\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 +{ + 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()); + + $idpId = $this->resolveIdpId($hashDecoratedState); + $idpVersionId = $this->resolveIdpVersionId($idpId, $hashDecoratedState); + $spId = $this->resolveSpId($hashDecoratedState); + $spVersionId = $this->resolveSpVersionId($spId, $hashDecoratedState); + $userId = $this->resolveUserId($hashDecoratedState); + $userVersionId = $this->resolveUserVersionId($userId, $hashDecoratedState); + $idpSpUserVersionId = $this->resolveIdpSpUserVersionId($idpVersionId, $spVersionId, $userVersionId); + + $this->repository->insertAuthenticationEvent( + $idpSpUserVersionId, + $authenticationEvent->getHappenedAt(), + $authenticationEvent->getState()->getClientIpAddress(), + $authenticationEvent->getState()->getAuthenticationProtocol()->getDesignation() + ); + } + + /** + * @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 + */ + public function deleteDataOlderThan(DateTimeImmutable $dateTime): void + { + // Only delete authentication events. VersionedDataProvider data (IdP / SP metadata, user attributes) remain. + $this->repository->deleteAuthenticationEventsOlderThan($dateTime); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..ad3f84c692e40dacdf99f7224d2686f537d81475 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable; + +class Version20220801000000CreateIdpTable extends CreateIdpTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..31bb2d4188dd14eea75114169b41ad50e72004cc --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000100CreateIdpVersionTable extends Migrations\CreateIdpVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..a3405603163cdaf4219bd0ef97ea94f1c149b9b7 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable; + +class Version20220801000200CreateSpTable extends CreateSpTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..846629b48927de0e1372f68376d31bfd555a5a53 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000300CreateSpVersionTable extends Migrations\CreateSpVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..de24d65d8b8ae9658aaf15e7c276ebc67f1375c3 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable; + +class Version20220801000400CreateUserTable extends CreateUserTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..bd429eefab28380449f6228ec96d42628e606d0f --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000500CreateUserVersionTable extends Migrations\CreateUserVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..4ac4a698e8cc96e2d877fc7ee3367aa7ce26c7a8 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000600CreateIdpSpUserVersionTable extends Migrations\CreateIdpSpUserVersionTable +{ +} diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php similarity index 88% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php rename to src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php index 65940b56534d04ead2e05120a8f0b64af2073464..d477b56c40326e5cc546c33aaba8b098c4a7b839 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTable.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; use function sprintf; diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawActivity.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivity.php similarity index 96% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawActivity.php rename to src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivity.php index b9433114aeb01dc3175df211a3d1368682697f21..ed7b42cba9e6d162f0f6b194f92508eb186fdc98 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawActivity.php +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivity.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; +use SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity; use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity; class RawActivity extends AbstractRawEntity { diff --git a/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php new file mode 100644 index 0000000000000000000000000000000000000000..7071f0095aae17cde4e04b0d3c709c56d64f1da9 --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Repository.php @@ -0,0 +1,243 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store; + +use DateTimeImmutable; +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; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use Throwable; + +class Repository extends BaseRepository +{ + 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 $idpSpUserVersionId, + 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_IDP_SP_USER_VERSION_ID => ':' . + TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_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_IDP_SP_USER_VERSION_ID => + $idpSpUserVersionId, + 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_IDP_SP_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('idpSpUserVersionId')); + 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( + //'vae.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, + //'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, + //'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 + )->from( + //'vds_authentication_event', 'vae' + $this->tableNameAuthenticationEvent, + //'vae' + TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT + ) + ->leftJoin( + //'vae', + TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT, + //'vds_idp_sp_user_version', + $this->tableNameIdpSpUserVersion, + //'visuv', + BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, + //'vae.idp_sp_user_version_id = visuv.id' + TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . + TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_USER_VERSION_ID . ' = ' . + BaseTableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . + BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID + ) + ->leftJoin( + //'visuv', + 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 + ) + ->leftJoin( + //'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 + ) + ->leftJoin( + //'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 + ) + ->leftJoin( + //'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( + //'vu.identifier_hash_sha256 = ' . + BaseTableConstants::TABLE_ALIAS_USER . '.' . + BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . + $authenticationEventsQueryBuilder->createNamedParameter($userIdentifierHashSha256) + ) + ->orderBy( + //'vae.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); + } + } + + /** + * @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 new file mode 100644 index 0000000000000000000000000000000000000000..df5b36c75d946c921b31f98f26f0358903be457b --- /dev/null +++ b/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/TableConstants.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\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_IDP_SP_USER_VERSION_ID = 'idp_sp_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'; + + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..0090783a3631b033a6cf8aa1589180587cdd7066 --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store.php @@ -0,0 +1,242 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current; + +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\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 +{ + protected HelpersManager $helpersManager; + private 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); + + $this->helpersManager = $helpersManager ?? new HelpersManager(); + $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 resolveSpId(HashDecoratedState $hashDecoratedState): int + { + $spEntityIdHashSha256 = $hashDecoratedState->getServiceProviderEntityIdHashSha256(); + + // Check if it already exists. + try { + $result = $this->repository->getSp($spEntityIdHashSha256); + $sp = $result->fetchAssociative(); + + if ($sp !== false) { + $spId = (int)$sp[TableConstants::TABLE_SP_COLUMN_NAME_ID]; + // If metadata hash is different, update metadata. + if ( + $sp[TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256] !== + $hashDecoratedState->getServiceProviderMetadataArrayHashSha256() + ) { + $this->repository->updateSp( + $spId, + serialize($hashDecoratedState->getState()->getServiceProviderMetadata()), + $hashDecoratedState->getServiceProviderMetadataArrayHashSha256() + ); + } + return $spId; + } + } catch (Throwable $exception) { + $message = sprintf('Error resolving SP ID. Error was: %s.', $exception->getMessage()); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + + // Create new + try { + $this->repository->insertSp( + $hashDecoratedState->getState()->getServiceProviderEntityId(), + $spEntityIdHashSha256, + serialize($hashDecoratedState->getState()->getServiceProviderMetadata()), + $hashDecoratedState->getServiceProviderMetadataArrayHashSha256() + ); + } catch (Throwable $exception) { + $message = sprintf( + 'Error inserting new SP, 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->getSp($spEntityIdHashSha256); + $spIdNew = $result->fetchOne(); + + if ($spIdNew !== false) { + return (int)$spIdNew; + } + + $message = sprintf( + 'Error fetching SP ID even after insertion for entity ID hash SHA256 %s.', + $spEntityIdHashSha256 + ); + throw new StoreException($message); + } catch (Throwable $exception) { + $message = sprintf('Error resolving SP ID. Error was: %s.', $exception->getMessage()); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..840cd8fbe8c9b98870bb6b36ff8f035ea6a4f314 --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateIdpTable.php @@ -0,0 +1,87 @@ +<?php + +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 function sprintf; + +class CreateIdpTable extends AbstractMigration +{ + protected function getLocalTablePrefix(): string + { + return 'cds_'; + } + + /** + * @inheritDoc + * @throws MigrationException + */ + public function run(): void + { + $tableName = $this->preparePrefixedTableName('idp'); + + /** @noinspection DuplicatedCode */ + try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + + $table = new Table($tableName); + + $table->addColumn('id', Types::BIGINT) + ->setUnsigned(true) + ->setAutoincrement(true); + + $table->addColumn('entity_id', Types::STRING) + ->setLength(TableConstants::COLUMN_ENTITY_ID_LENGTH); + + $table->addColumn('entity_id_hash_sha256', Types::STRING) + ->setLength(TableConstants::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) + ->setFixed(true); + + $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE); + $table->addColumn('updated_at', Types::DATETIMETZ_IMMUTABLE); + + $table->setPrimaryKey(['id']); + + $table->addUniqueConstraint(['entity_id_hash_sha256']); + $table->addIndex(['metadata_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('idp'); + + 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/CreateSpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTable.php new file mode 100644 index 0000000000000000000000000000000000000000..5551faf0e6b12907fbb4b1d8d0e02e6b53174a30 --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateSpTable.php @@ -0,0 +1,87 @@ +<?php + +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 function sprintf; + +class CreateSpTable extends AbstractMigration +{ + protected function getLocalTablePrefix(): string + { + return 'cds_'; + } + + /** + * @inheritDoc + * @throws MigrationException + */ + public function run(): void + { + $tableName = $this->preparePrefixedTableName('sp'); + + /** @noinspection DuplicatedCode */ + try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + + $table = new Table($tableName); + + $table->addColumn('id', Types::BIGINT) + ->setUnsigned(true) + ->setAutoincrement(true); + + $table->addColumn('entity_id', Types::STRING) + ->setLength(TableConstants::COLUMN_ENTITY_ID_LENGTH); + + $table->addColumn('entity_id_hash_sha256', Types::STRING) + ->setLength(TableConstants::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) + ->setFixed(true); + + $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE); + $table->addColumn('updated_at', Types::DATETIMETZ_IMMUTABLE); + + $table->setPrimaryKey(['id']); + + $table->addUniqueConstraint(['entity_id_hash_sha256']); + $table->addIndex(['metadata_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('sp'); + + 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/CreateUserTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTable.php new file mode 100644 index 0000000000000000000000000000000000000000..090357d69e9484499309c44a5ca737c4f62b8496 --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserTable.php @@ -0,0 +1,78 @@ +<?php + +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 function sprintf; + +class CreateUserTable extends AbstractMigration +{ + 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 new file mode 100644 index 0000000000000000000000000000000000000000..11f7926b19a3dadbfcc8fcd74917f90fb8304311 --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Migrations/CreateUserVersionTable.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +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 new file mode 100644 index 0000000000000000000000000000000000000000..7ec179ccefcacbee5ca94ad50a9c9afa03dd3ecf --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/Repository.php @@ -0,0 +1,530 @@ +<?php + +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; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use Throwable; + +class Repository +{ + protected Connection $connection; + protected LoggerInterface $logger; + protected string $tableNameIdp; + protected string $tableNameSp; + protected string $tableNameUser; + protected string $tableNameUserVersion; + + public function __construct(Connection $connection, LoggerInterface $logger) + { + $this->connection = $connection; + $this->logger = $logger; + + $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); + } + + protected function preparePrefixedTableName(string $tableName): string + { + return $this->connection->preparePrefixedTableName(TableConstants::TABLE_PREFIX . $tableName); + } + + /** + * @throws StoreException + */ + public function getIdp(string $entityIdHashSha256): Result + { + try { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + /** @psalm-suppress TooManyArguments */ + $queryBuilder->select( + TableConstants::TABLE_IDP_COLUMN_NAME_ID, + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID, + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256, + TableConstants::TABLE_IDP_COLUMN_NAME_CREATED_AT, + TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT, + ) + ->from($this->tableNameIdp) + ->where( + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 . ' = ' . + $queryBuilder->createNamedParameter($entityIdHashSha256) + )->setMaxResults(1); + + return $queryBuilder->executeQuery(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing query to get IdP by entity ID hash SHA256 \'%s\'. Error was: %s.', + $entityIdHashSha256, + $exception->getMessage() + ); + $this->logger->error($message, compact('entityIdHashSha256')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function insertIdp( + string $entityId, + string $entityIdHashSha256, + string $metadata, + string $metadataHashSha256, + DateTimeImmutable $createdAt = null + ): void { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + $createdAt = $createdAt ?? new DateTimeImmutable(); + + $queryBuilder->insert($this->tableNameIdp) + ->values( + [ + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID => ':' . + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID, + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 => ':' . + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA => ':' . + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256 => ':' . + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256, + TableConstants::TABLE_IDP_COLUMN_NAME_CREATED_AT => ':' . + TableConstants::TABLE_IDP_COLUMN_NAME_CREATED_AT, + TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT => ':' . + TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT, + ] + ) + ->setParameters( + [ + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID => $entityId, + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 => $entityIdHashSha256, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA => $metadata, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256 => $metadataHashSha256, + TableConstants::TABLE_IDP_COLUMN_NAME_CREATED_AT => $createdAt, + TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT => $createdAt, + ], + [ + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID => Types::STRING, + TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 => Types::STRING, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA => Types::STRING, + TableConstants::TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256 => Types::STRING, + TableConstants::TABLE_IDP_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE, + TableConstants::TABLE_IDP_COLUMN_NAME_UPDATED_AT => Types::DATETIMETZ_IMMUTABLE, + ] + ); + + try { + $queryBuilder->executeStatement(); + } catch (Throwable $exception) { + $message = sprintf('Error executing query to insert IdP. Error was: %s.', $exception->getMessage()); + $this->logger->error($message, compact('entityId', 'entityIdHashSha256')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function getSp(string $entityIdHashSha256): Result + { + try { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + /** @psalm-suppress TooManyArguments */ + $queryBuilder->select( + TableConstants::TABLE_SP_COLUMN_NAME_ID, + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID, + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256, + TableConstants::TABLE_SP_COLUMN_NAME_CREATED_AT, + TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT, + ) + ->from($this->tableNameSp) + ->where( + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 . ' = ' . + $queryBuilder->createNamedParameter($entityIdHashSha256) + )->setMaxResults(1); + + return $queryBuilder->executeQuery(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing query to get SP by entity ID hash SHA256 \'%s\'. Error was: %s.', + $entityIdHashSha256, + $exception->getMessage() + ); + $this->logger->error($message, compact('entityIdHashSha256')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function updateIdp( + string $idpId, + string $metadata, + string $metadataHashSha256, + DateTimeImmutable $updatedAt = null + ): void { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + $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) + ) + ) + ); + + try { + $queryBuilder->executeStatement(); + } catch (Throwable $exception) { + $message = sprintf('Error executing query to update IdP. Error was: %s.', $exception->getMessage()); + $this->logger->error($message, compact('idpId', 'metadata', 'metadataHashSha256')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function insertSp( + string $entityId, + string $entityIdHashSha256, + string $metadata, + string $metadataHashSha256, + DateTimeImmutable $createdAt = null + ): void { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + $createdAt = $createdAt ?? new DateTimeImmutable(); + + $queryBuilder->insert($this->tableNameSp) + ->values( + [ + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID => ':' . + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID, + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 => ':' . + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA => ':' . + TableConstants::TABLE_SP_COLUMN_NAME_METADATA, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256 => ':' . + TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256, + TableConstants::TABLE_SP_COLUMN_NAME_CREATED_AT => ':' . + TableConstants::TABLE_SP_COLUMN_NAME_CREATED_AT, + TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT => ':' . + TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT, + ] + ) + ->setParameters( + [ + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID => $entityId, + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 => $entityIdHashSha256, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA => $metadata, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256 => $metadataHashSha256, + TableConstants::TABLE_SP_COLUMN_NAME_CREATED_AT => $createdAt, + TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT => $createdAt, + ], + [ + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID => Types::STRING, + TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 => Types::STRING, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA => Types::STRING, + TableConstants::TABLE_SP_COLUMN_NAME_METADATA_HASH_SHA256 => Types::STRING, + TableConstants::TABLE_SP_COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE, + TableConstants::TABLE_SP_COLUMN_NAME_UPDATED_AT => Types::DATETIMETZ_IMMUTABLE, + ] + ); + + try { + $queryBuilder->executeStatement(); + } catch (Throwable $exception) { + $message = sprintf('Error executing query to insert SP. Error was: %s.', $exception->getMessage()); + $this->logger->error($message, compact('entityId', 'entityIdHashSha256')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function updateSp( + int $spId, + string $metadata, + 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(); + + $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/Current/Store/TableConstants.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/TableConstants.php new file mode 100644 index 0000000000000000000000000000000000000000..40b45a00b61dc6f2ec2ea4d6119d7e01c6544d8e --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Current/Store/TableConstants.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstants; + +class TableConstants extends BaseTableConstants +{ + public const TABLE_PREFIX = 'cds_'; // current data store + + // Table 'idp' + public const TABLE_NAME_IDP = 'idp'; + public const TABLE_ALIAS_IDP = self::TABLE_PREFIX . 'i'; + public const TABLE_IDP_COLUMN_NAME_ID = 'id'; // int + public const TABLE_IDP_COLUMN_NAME_ENTITY_ID = 'entity_id'; // Entity ID value, string, varchar(1024) + public const TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 = 'entity_id_hash_sha256'; // ha256 hash hexits, char(64) + public const TABLE_IDP_COLUMN_NAME_METADATA = 'metadata'; // Serialized IdP metadata version + public const TABLE_IDP_COLUMN_NAME_METADATA_HASH_SHA256 = 'metadata_hash_sha256'; // Metadata sha256 hash + public const TABLE_IDP_COLUMN_NAME_CREATED_AT = 'created_at'; // First time IdP usage, datetime + public const TABLE_IDP_COLUMN_NAME_UPDATED_AT = 'updated_at'; // First time IdP usage, datetime + + // Table 'sp', same structure as in 'idp' + public const TABLE_NAME_SP = 'sp'; + public const TABLE_ALIAS_SP = self::TABLE_PREFIX . 's'; + public const TABLE_SP_COLUMN_NAME_ID = 'id'; + public const TABLE_SP_COLUMN_NAME_ENTITY_ID = 'entity_id'; + public const TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256 = 'entity_id_hash_sha256'; + public const TABLE_SP_COLUMN_NAME_METADATA = 'metadata'; + 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/Stores/Data/Authentication/DoctrineDbal/Versioned/Store.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php similarity index 70% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php index dbee374d3bea5b1e875b833612cb0b01dbec535f..6c3214b6565d5629f0d4a7d272159b63a325688e 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store.php @@ -2,31 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned; -use DateTimeImmutable; use Psr\Log\LoggerInterface; -use SimpleSAML\Module\accounting\Entities\Activity; -use SimpleSAML\Module\accounting\Entities\Authentication\Event; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; -use SimpleSAML\Module\accounting\Entities\User; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository; +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 SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\HashDecoratedState; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawActivity; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawConnectedServiceProvider; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository; -use SimpleSAML\Module\accounting\Stores\Interfaces\DataStoreInterface; use Throwable; -class Store extends AbstractStore implements DataStoreInterface +class Store extends AbstractStore implements StoreInterface { - protected Repository $repository; protected HelpersManager $helpersManager; + private Repository $repository; /** * @throws StoreException @@ -37,13 +30,13 @@ class Store extends AbstractStore implements DataStoreInterface string $connectionKey = null, string $connectionType = ModuleConfiguration\ConnectionType::MASTER, Factory $connectionFactory = null, - Repository $repository = null, - HelpersManager $helpersManager = null + HelpersManager $helpersManager = null, + Repository $repository = null ) { parent::__construct($moduleConfiguration, $logger, $connectionKey, $connectionType, $connectionFactory); - $this->repository = $repository ?? new Repository($this->connection, $this->logger); $this->helpersManager = $helpersManager ?? new HelpersManager(); + $this->repository = $repository ?? new Repository($this->connection, $this->logger); } /** @@ -67,30 +60,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - public function persist(Event $authenticationEvent): void - { - $hashDecoratedState = new HashDecoratedState($authenticationEvent->getState()); - - $idpId = $this->resolveIdpId($hashDecoratedState); - $idpVersionId = $this->resolveIdpVersionId($idpId, $hashDecoratedState); - $spId = $this->resolveSpId($hashDecoratedState); - $spVersionId = $this->resolveSpVersionId($spId, $hashDecoratedState); - $userId = $this->resolveUserId($hashDecoratedState); - $userVersionId = $this->resolveUserVersionId($userId, $hashDecoratedState); - $idpSpUserVersionId = $this->resolveIdpSpUserVersionId($idpVersionId, $spVersionId, $userVersionId); - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $authenticationEvent->getHappenedAt(), - $authenticationEvent->getState()->getClientIpAddress(), - $authenticationEvent->getState()->getAuthenticationProtocol()->getDesignation() - ); - } - - /** - * @throws StoreException - */ - protected function resolveIdpId(HashDecoratedState $hashDecoratedState): int + public function resolveIdpId(HashDecoratedState $hashDecoratedState): int { $idpEntityIdHashSha256 = $hashDecoratedState->getIdentityProviderEntityIdHashSha256(); @@ -144,7 +114,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - protected function resolveIdpVersionId(int $idpId, HashDecoratedState $hashDecoratedState): int + public function resolveIdpVersionId(int $idpId, HashDecoratedState $hashDecoratedState): int { // Check if it already exists. $idpMetadataArrayHashSha256 = $hashDecoratedState->getIdentityProviderMetadataArrayHashSha256(); @@ -199,7 +169,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - protected function resolveSpId(HashDecoratedState $hashDecoratedState): int + public function resolveSpId(HashDecoratedState $hashDecoratedState): int { $spEntityIdHashSha256 = $hashDecoratedState->getServiceProviderEntityIdHashSha256(); @@ -253,7 +223,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - protected function resolveSpVersionId(int $spId, HashDecoratedState $hashDecoratedState): int + public function resolveSpVersionId(int $spId, HashDecoratedState $hashDecoratedState): int { // Check if it already exists. $spMetadataArrayHashSha256 = $hashDecoratedState->getServiceProviderMetadataArrayHashSha256(); @@ -308,7 +278,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - protected function resolveUserId(HashDecoratedState $hashDecoratedState): int + public function resolveUserId(HashDecoratedState $hashDecoratedState): int { $userIdentifierAttributeName = $this->moduleConfiguration->getUserIdAttributeName(); @@ -367,7 +337,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - protected function resolveUserVersionId(int $userId, HashDecoratedState $hashDecoratedState): int + public function resolveUserVersionId(int $userId, HashDecoratedState $hashDecoratedState): int { $attributeArrayHashSha256 = $hashDecoratedState->getAttributesArrayHashSha256(); @@ -422,7 +392,7 @@ class Store extends AbstractStore implements DataStoreInterface /** * @throws StoreException */ - protected function resolveIdpSpUserVersionId(int $idpVersionId, int $spVersionId, int $userVersionId): int + public function resolveIdpSpUserVersionId(int $idpVersionId, int $spVersionId, int $userVersionId): int { // Check if it already exists. try { @@ -471,103 +441,4 @@ class Store extends AbstractStore implements DataStoreInterface throw new StoreException($message, (int)$exception->getCode(), $exception); } } - - /** - * @throws StoreException - */ - public function getConnectedOrganizations(string $userIdentifierHashSha256): ConnectedServiceProvider\Bag - { - $connectedServiceProviderBag = new ConnectedServiceProvider\Bag(); - - $results = $this->repository->getConnectedServiceProviders($userIdentifierHashSha256); - - if (empty($results)) { - return $connectedServiceProviderBag; - } - - try { - $databasePlatform = $this->connection->dbal()->getDatabasePlatform(); - - /** @var array $result */ - foreach ($results as $result) { - $rawConnectedServiceProvider = new RawConnectedServiceProvider($result, $databasePlatform); - - $serviceProvider = $this->helpersManager - ->getProviderResolver() - ->forServiceFromMetadataArray($rawConnectedServiceProvider->getServiceProviderMetadata()); - $user = new User($rawConnectedServiceProvider->getUserAttributes()); - - $connectedServiceProviderBag->addOrReplace( - new ConnectedServiceProvider( - $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 - */ - public function getActivity(string $userIdentifierHashSha256, int $maxResults, int $firstResult): Activity\Bag - { - $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 - */ - public function deleteDataOlderThan(DateTimeImmutable $dateTime): void - { - // Only delete authentication events. Versioned data (IdP / SP metadata, user attributes) remain. - $this->repository->deleteAuthenticationEventsOlderThan($dateTime); - } } diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTable.php similarity index 87% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTable.php index 5f23e4ecdec92d8dd71f171845ef4ae0ae8b7221..dd05d1d3396f692e71464b975054a7493975980b 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTable.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Types; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; use Throwable; use function sprintf; -class Version20220801000600CreateIdpSpUserVersionTable extends AbstractMigration +class CreateIdpSpUserVersionTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -28,6 +28,10 @@ class Version20220801000600CreateIdpSpUserVersionTable extends AbstractMigration $tableName = $this->preparePrefixedTableName('idp_sp_user_version'); try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php similarity index 80% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php index 4bddd2ae4c7eef75c74e323167a1a6e6902a3a28..7a37729835d79bc1dc1269406081ffd19215cf91 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTable.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; use function sprintf; -class Version20220801000000CreateIdpTable extends AbstractMigration +class CreateIdpTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -30,6 +30,10 @@ class Version20220801000000CreateIdpTable extends AbstractMigration /** @noinspection DuplicatedCode */ try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php similarity index 80% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php index 1151b3a2694efdb00e5b8599674aec830c25175b..fdbd7c5240b20734e02756c8940db0ab2717d70d 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTable.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; -class Version20220801000100CreateIdpVersionTable extends AbstractMigration +class CreateIdpVersionTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -27,6 +27,10 @@ class Version20220801000100CreateIdpVersionTable extends AbstractMigration $tableName = $this->preparePrefixedTableName('idp_version'); try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php similarity index 80% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php index bee93ca8560670d1082ddba1af61f8454b528606..a168bdfc1f1dd0c3225cf92a7196f0f07504de3e 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTable.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; use function sprintf; -class Version20220801000200CreateSpTable extends AbstractMigration +class CreateSpTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -30,6 +30,10 @@ class Version20220801000200CreateSpTable extends AbstractMigration /** @noinspection DuplicatedCode */ try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php similarity index 81% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php index e2e0028a57699ffb83d771e0a4bee7c47b223442..6f7a4c097d3bcd08b5b46885c50af2cbf8fa4260 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTable.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; use function sprintf; -class Version20220801000300CreateSpVersionTable extends AbstractMigration +class CreateSpVersionTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -29,6 +29,10 @@ class Version20220801000300CreateSpVersionTable extends AbstractMigration $tableName = $this->preparePrefixedTableName('sp_version'); try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php similarity index 79% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php index ff14736d758230e7837ee4ae80d7c5e561d53645..511bf5b71e3b3dddb0b7cafdb1964055d1fe638a 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTable.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; use function sprintf; -class Version20220801000400CreateUserTable extends AbstractMigration +class CreateUserTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -29,6 +29,10 @@ class Version20220801000400CreateUserTable extends AbstractMigration $tableName = $this->preparePrefixedTableName('user'); try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php similarity index 81% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php index a351c6a3c141c2fd13fe41cb09ff4e6102716593..54b2693c37cdccdd75c9e33ae3c721b29c64ad7c 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTable.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; 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\Connections\DoctrineDbal\Bases\AbstractMigration; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use Throwable; use function sprintf; -class Version20220801000500CreateUserVersionTable extends AbstractMigration +class CreateUserVersionTable extends AbstractMigration { protected function getLocalTablePrefix(): string { @@ -29,6 +29,10 @@ class Version20220801000500CreateUserVersionTable extends AbstractMigration $tableName = $this->preparePrefixedTableName('user_version'); try { + if ($this->schemaManager->tablesExist($tableName)) { + return; + } + $table = new Table($tableName); $table->addColumn('id', Types::BIGINT) diff --git a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Repository.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php similarity index 54% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Repository.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php index 239adaff34a0171df4ffef834e5b2e1b4aa6fb0f..0284c4e585714741f9582fbbf404e52d4501c0ab 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Repository.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Repository.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store; use DateTimeImmutable; use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\Result; use Doctrine\DBAL\Types\Types; use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; use Throwable; class Repository @@ -24,7 +24,6 @@ class Repository protected string $tableNameUser; protected string $tableNameUserVersion; protected string $tableNameIdpSpUserVersion; - protected string $tableNameAuthenticationEvent; public function __construct(Connection $connection, LoggerInterface $logger) { @@ -37,10 +36,9 @@ class Repository $this->tableNameSpVersion = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_SP_VERSION); $this->tableNameUser = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_USER); $this->tableNameUserVersion = $this->preparePrefixedTableName(TableConstants::TABLE_NAME_USER_VERSION); - $this->tableNameIdpSpUserVersion = - $this->preparePrefixedTableName(TableConstants::TABLE_NAME_IDP_SP_USER_VERSION); - $this->tableNameAuthenticationEvent = - $this->preparePrefixedTableName(TableConstants::TABLE_NAME_AUTHENTICATION_EVENT); + $this->tableNameIdpSpUserVersion = $this->preparePrefixedTableName( + TableConstants::TABLE_NAME_IDP_SP_USER_VERSION + ); } protected function preparePrefixedTableName(string $tableName): string @@ -665,461 +663,4 @@ class Repository throw new StoreException($message, (int)$exception->getCode(), $exception); } } - - /** - * @throws StoreException - */ - public function insertAuthenticationEvent( - int $idpSpUserVersionId, - 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_IDP_SP_USER_VERSION_ID => ':' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_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_IDP_SP_USER_VERSION_ID => - $idpSpUserVersionId, - 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_IDP_SP_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('idpSpUserVersionId')); - throw new StoreException($message, (int)$exception->getCode(), $exception); - } - } - - /** - * @throws StoreException - */ - public function getConnectedServiceProviders(string $userIdentifierHashSha256): array - { - try { - $authenticationEventsQueryBuilder = $this->connection->dbal()->createQueryBuilder(); - $lastMetadataAndAttributesQueryBuilder = $this->connection->dbal()->createQueryBuilder(); - - /** @psalm-suppress TooManyArguments */ - $authenticationEventsQueryBuilder->select( - //'vs.entity_id AS sp_entity_id', - TableConstants::TABLE_ALIAS_SP . '.' . - TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID . ' AS ' . - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_ENTITY_ID, - //'COUNT(vae.id) AS number_of_authentications', - 'COUNT(' . TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_ID . ') AS ' . - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS, - //'MAX(vae.happened_at) AS last_authentication_at', - 'MAX(' . TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT . ') AS ' . - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT, - //'MIN(vae.happened_at) AS first_authentication_at', - 'MIN(' . TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_HAPPENED_AT . ') AS ' . - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT, - )->from($this->tableNameAuthenticationEvent, TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT) - ->leftJoin( - //'vae', - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT, - //'vds_idp_sp_user_version', - $this->tableNameIdpSpUserVersion, - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vae.idp_sp_user_version_id = visuv.id' - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_USER_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vds_sp_version', - $this->tableNameSpVersion, - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'visuv.sp_version_id = vsv.id' - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'vds_sp', - $this->tableNameSp, - //'vs', - TableConstants::TABLE_ALIAS_SP, - //'vsv.sp_id = vs.id' - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . - TableConstants::TABLE_SP_VERSION_COLUMN_NAME_SP_ID . ' = ' . - TableConstants::TABLE_ALIAS_SP . '.' . TableConstants::TABLE_SP_COLUMN_NAME_ID - ) - ->leftJoin( - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vds_user_version', - $this->tableNameUserVersion, - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'visuv.user_version_id = vuv.id' - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'vds_user', - $this->tableNameUser, - //'vu', - TableConstants::TABLE_ALIAS_USER, - //'vuv.user_id = vu.id' - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID . ' = ' . - TableConstants::TABLE_ALIAS_USER . '.' . TableConstants::TABLE_USER_COLUMN_NAME_ID - ) - ->where( - //'vu.identifier_hash_sha256 = ' . - TableConstants::TABLE_ALIAS_USER . '.' . - TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . - $authenticationEventsQueryBuilder->createNamedParameter($userIdentifierHashSha256) - ) - ->groupBy( - //'vs.id' - TableConstants::TABLE_ALIAS_SP . '.' . TableConstants::TABLE_SP_COLUMN_NAME_ID - ) - ->orderBy( - //'number_of_authentications', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS, - 'DESC' - ); - - - /** @psalm-suppress TooManyArguments */ - $lastMetadataAndAttributesQueryBuilder->select( - //'vs.entity_id AS sp_entity_id', - TableConstants::TABLE_ALIAS_SP . '.' . TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID . ' AS ' . - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_ENTITY_ID, - //'vsv.metadata AS sp_metadata', - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA . - ' AS ' . TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA, - //'vuv.attributes AS user_attributes', - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' . - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES - // 'vsv.id AS sp_version_id', - // 'vuv.id AS user_version_id', - )->from( - //'vds_authentication_event', - $this->tableNameAuthenticationEvent, - //'vae' - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT - ) - ->leftJoin( - //'vae', - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT, - //'vds_idp_sp_user_version', - $this->tableNameIdpSpUserVersion, - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vae.idp_sp_user_version_id = visuv.id' - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_USER_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vds_sp_version', - $this->tableNameSpVersion, - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'visuv.sp_version_id = vsv.id' - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'vds_sp', - $this->tableNameSp, - //'vs', - TableConstants::TABLE_ALIAS_SP, - //'vsv.sp_id = vs.id' - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . - TableConstants::TABLE_SP_VERSION_COLUMN_NAME_SP_ID . ' = ' . TableConstants::TABLE_ALIAS_SP . '.' . - TableConstants::TABLE_SP_COLUMN_NAME_ID - ) - ->leftJoin( - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vds_user_version', - $this->tableNameUserVersion, - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'visuv.user_version_id = vuv.id' - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'vds_user', - $this->tableNameUser, - //'vu', - TableConstants::TABLE_ALIAS_USER, - //'vuv.user_id = vu.id' - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID . ' = ' . TableConstants::TABLE_ALIAS_USER . - '.' . TableConstants::TABLE_USER_COLUMN_NAME_ID - ) - ->leftJoin( - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'vds_sp_version', - $this->tableNameSpVersion, - //'vsv2', - TableConstants::TABLE_ALIAS_SP_VERSION_2, // Another alias for self joining... - //'vsv.id = vsv2.id AND vsv.id < vsv2.id' // To be able to get latest one... - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . - TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . ' = ' . TableConstants::TABLE_ALIAS_SP_VERSION_2 . - '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . ' AND ' . - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . - ' < ' . TableConstants::TABLE_ALIAS_SP_VERSION_2 . '.' . - TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'vds_user_version', - $this->tableNameUserVersion, - //'vuv2', - TableConstants::TABLE_ALIAS_USER_VERSION_2, // Another alias for self joining... - //'vuv.id = vuv2.id AND vuv.id < vuv2.id' // To be able to get latest one... - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' = ' . - TableConstants::TABLE_ALIAS_USER_VERSION_2 - . '.' . TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' AND ' . - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . - ' < ' . TableConstants::TABLE_ALIAS_USER_VERSION_2 . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID - ) - ->where( - //'vu.identifier_hash_sha256 = ' . - TableConstants::TABLE_ALIAS_USER . '.' . - TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . - $lastMetadataAndAttributesQueryBuilder->createNamedParameter($userIdentifierHashSha256) - ) - ->andWhere( - //'vsv2.id IS NULL' - TableConstants::TABLE_ALIAS_SP_VERSION_2 . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID - . ' IS NULL' - ) - ->andWhere( - //'vuv2.id IS NULL' - TableConstants::TABLE_ALIAS_USER_VERSION_2 . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' IS NULL' - ); - - $numberOfAuthentications = $authenticationEventsQueryBuilder->executeQuery()->fetchAllAssociativeIndexed(); - $lastMetadataAndAttributes = - $lastMetadataAndAttributesQueryBuilder->executeQuery()->fetchAllAssociativeIndexed(); - - return array_merge_recursive($numberOfAuthentications, $lastMetadataAndAttributes); - } 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); - } - } - - /** - * @throws StoreException - */ - public function getActivity(string $userIdentifierHashSha256, int $maxResults, int $firstResult): array - { - try { - $authenticationEventsQueryBuilder = $this->connection->dbal()->createQueryBuilder(); - - /** @psalm-suppress TooManyArguments */ - $authenticationEventsQueryBuilder->select( - //'vae.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, - //'vsv.metadata AS sp_metadata', - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA . - ' AS ' . TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA, - //'vuv.attributes AS user_attributes' - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES . ' AS ' . - TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES - )->from( - //'vds_authentication_event', 'vae' - $this->tableNameAuthenticationEvent, - //'vae' - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT - ) - ->leftJoin( - //'vae', - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT, - //'vds_idp_sp_user_version', - $this->tableNameIdpSpUserVersion, - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vae.idp_sp_user_version_id = visuv.id' - TableConstants::TABLE_ALIAS_AUTHENTICATION_EVENT . '.' . - TableConstants::TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_USER_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vds_sp_version', - $this->tableNameSpVersion, - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'visuv.sp_version_id = vsv.id' - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vsv', - TableConstants::TABLE_ALIAS_SP_VERSION, - //'vds_sp', - $this->tableNameSp, - //'vs', - TableConstants::TABLE_ALIAS_SP, - //'vsv.sp_id = vs.id' - TableConstants::TABLE_ALIAS_SP_VERSION . '.' . TableConstants::TABLE_SP_VERSION_COLUMN_NAME_SP_ID . - ' = ' . TableConstants::TABLE_ALIAS_SP . '.' . TableConstants::TABLE_SP_COLUMN_NAME_ID - ) - ->leftJoin( - //'visuv', - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION, - //'vds_user_version', - $this->tableNameUserVersion, - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'visuv.user_version_id = vuv.id' - TableConstants::TABLE_ALIAS_IDP_SP_USER_VERSION . '.' . - TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID . ' = ' . - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID - ) - ->leftJoin( - //'vuv', - TableConstants::TABLE_ALIAS_USER_VERSION, - //'vds_user', - $this->tableNameUser, - //'vu', - TableConstants::TABLE_ALIAS_USER, - //'vuv.user_id = vu.id' - TableConstants::TABLE_ALIAS_USER_VERSION . '.' . - TableConstants::TABLE_USER_VERSION_COLUMN_NAME_USER_ID . ' = ' . TableConstants::TABLE_ALIAS_USER . - '.' . TableConstants::TABLE_USER_COLUMN_NAME_ID - ) - ->where( - //'vu.identifier_hash_sha256 = ' . - TableConstants::TABLE_ALIAS_USER . '.' . - TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . - $authenticationEventsQueryBuilder->createNamedParameter($userIdentifierHashSha256) - ) - ->orderBy( - //'vae.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); - } - } - - /** - * @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/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/TableConstants.php b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/TableConstants.php similarity index 61% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/TableConstants.php rename to src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/TableConstants.php index df6eb7afbf3bbfbe67fab8e335414923ef06ef9c..72da4602674a14fae4187777f9c9ff45f759938c 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/TableConstants.php +++ b/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/TableConstants.php @@ -2,20 +2,14 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store; -class TableConstants +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\TableConstants as BaseTableConstants; + +class TableConstants extends BaseTableConstants { public const TABLE_PREFIX = 'vds_'; // versioned data store - // Any SAML entity ID should have maximum 1024 chars per - // https://stackoverflow.com/questions/24196369/what-to-present-at-saml-entityid-url - public const COLUMN_ENTITY_ID_LENGTH = 1024; - public const COLUMN_HASH_SHA265_HEXITS_LENGTH = 64; - public const COLUMN_IP_ADDRESS_LENGTH = 45; - public const COLUMN_AUTHENTICATION_PROTOCOL_DESIGNATION_LENGTH = 16; - - // Table 'idp' public const TABLE_NAME_IDP = 'idp'; public const TABLE_ALIAS_IDP = self::TABLE_PREFIX . 'i'; @@ -69,7 +63,7 @@ class TableConstants public const TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256 = 'attributes_hash_sha256'; public const TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT = 'created_at'; - // Attribute versions released to SP version + // Attribute versions released to SP version by IdP version public const TABLE_NAME_IDP_SP_USER_VERSION = 'idp_sp_user_version'; public const TABLE_ALIAS_IDP_SP_USER_VERSION = self::TABLE_PREFIX . 'isuv'; public const TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID = 'id'; @@ -77,31 +71,4 @@ class TableConstants public const TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID = 'sp_version_id'; public const TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_USER_VERSION_ID = 'user_version_id'; public const TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT = 'created_at'; - - // Table 'authentication_event'. - public const TABLE_NAME_AUTHENTICATION_EVENT = 'authentication_event'; - public const TABLE_ALIAS_AUTHENTICATION_EVENT = self::TABLE_PREFIX . 'ae'; - public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_ID = 'id'; - public const TABLE_AUTHENTICATION_EVENT_COLUMN_NAME_IDP_SP_USER_VERSION_ID = 'idp_sp_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'; - - // Entity 'ConnectedOrganization' (service provider) related. - public const ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_ENTITY_ID = 'sp_entity_id'; - public const ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS = 'number_of_authentications'; - public const ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT = 'last_authentication_at'; - public const ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT = 'first_authentication_at'; - public const ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA = 'sp_metadata'; - public const ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES = 'user_attributes'; - - // 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/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/HashDecoratedState.php b/src/Data/Stores/Accounting/Bases/HashDecoratedState.php similarity index 96% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/HashDecoratedState.php rename to src/Data/Stores/Accounting/Bases/HashDecoratedState.php index 07c60cf37227d24bfd2d5d3673ca5a094cec88e5..39aced9a2d93e17bf7886fd2c6e37b504dd5180c 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/HashDecoratedState.php +++ b/src/Data/Stores/Accounting/Bases/HashDecoratedState.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases; use SimpleSAML\Module\accounting\Entities\Interfaces\StateInterface; use SimpleSAML\Module\accounting\Services\HelpersManager; diff --git a/src/Data/Stores/Accounting/Bases/TableConstants.php b/src/Data/Stores/Accounting/Bases/TableConstants.php new file mode 100644 index 0000000000000000000000000000000000000000..5f5d656ce9f24ed0223294a69fd1d0530ef638d2 --- /dev/null +++ b/src/Data/Stores/Accounting/Bases/TableConstants.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases; + +class TableConstants +{ + // Any SAML entity ID should have maximum 1024 chars per + // https://stackoverflow.com/questions/24196369/what-to-present-at-saml-entityid-url + public const COLUMN_ENTITY_ID_LENGTH = 1024; + public const COLUMN_HASH_SHA265_HEXITS_LENGTH = 64; + public const COLUMN_IP_ADDRESS_LENGTH = 45; + public const COLUMN_AUTHENTICATION_PROTOCOL_DESIGNATION_LENGTH = 16; +} diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php new file mode 100644 index 0000000000000000000000000000000000000000..8e84d9cd328ef6c07bee3931c53064d738eb49bf --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store.php @@ -0,0 +1,154 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current; + +use DateTimeImmutable; +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; +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 +{ + 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 + ); + + $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); + + /** @psalm-suppress MixedAssignment */ + $connectedServiceId = $this->repository->getConnectedService($spId, $userId)->fetchOne(); + + if ($connectedServiceId !== false) { + $this->repository->updateConnectedServiceVersionCount( + (int)$connectedServiceId, + $userVersionId, + $authenticationEvent->getHappenedAt() + ); + } else { + $this->repository->insertConnectedService( + $spId, + $userId, + $userVersionId, + $authenticationEvent->getHappenedAt() + ); + } + } + + /** + * @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 + */ + public function deleteDataOlderThan(DateTimeImmutable $dateTime): void + { + $this->repository->deleteConnectedServicesOlderThan($dateTime); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..8f5d2ee52b349a4424a247ba17b988dc6ef14345 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505100CreateSpTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateSpTable; + +class Version20240505100CreateSpTable extends CreateSpTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..2120b20dc5c4b7d231c0410a1b6a56fd321c43a1 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505200CreateUserTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations\CreateUserTable; + +class Version20240505200CreateUserTable extends CreateUserTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..f3a60f29c4618a965ef55ee4d58a03b9873a0168 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505300CreateUserVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\Migrations; + +class Version20240505300CreateUserVersionTable extends Migrations\CreateUserVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..bd61e6d0cf4172fe63f397f7e314d5887941c211 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Migrations/Version20240505400CreateConnectedServiceTable.php @@ -0,0 +1,104 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store\Migrations; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Types; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; +use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; +use Throwable; + +use function sprintf; + +class Version20240505400CreateConnectedServiceTable extends AbstractMigration +{ + protected function getLocalTablePrefix(): string + { + return 'cds_'; + } + + /** + * @inheritDoc + * @throws MigrationException + */ + public function run(): void + { + $tableName = $this->preparePrefixedTableName('connected_service'); + + 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_id', Types::BIGINT) + ->setUnsigned(true); + + $table->addColumn('user_version_id', Types::BIGINT) + ->setUnsigned(true); + + $table->addColumn('first_authentication_at', Types::DATETIMETZ_IMMUTABLE); + + $table->addColumn('last_authentication_at', Types::DATETIMETZ_IMMUTABLE); + + $table->addColumn('count', Types::BIGINT)->setUnsigned(true); + + $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE); + + $table->addColumn('updated_at', Types::DATETIMETZ_IMMUTABLE); + + $table->setPrimaryKey(['id']); + + $table->addForeignKeyConstraint( + $this->preparePrefixedTableName('sp'), + ['sp_id'], + ['id'] + ); + + $table->addForeignKeyConstraint( + $this->preparePrefixedTableName('user'), + ['user_id'], + ['id'] + ); + + $table->addForeignKeyConstraint( + $this->preparePrefixedTableName('user_version'), + ['user_version_id'], + ['id'] + ); + + // Old data can be deleted using last_authentication_at column, so add index for it. + $table->addIndex(['last_authentication_at']); + + $table->addUniqueConstraint(['sp_id', 'user_id']); + + $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('connected_service'); + + 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/ConnectedServices/DoctrineDbal/Current/Store/Repository.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Repository.php new file mode 100644 index 0000000000000000000000000000000000000000..74a8e6264d64dd2f33bbe01513e1315b9acf8120 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/Repository.php @@ -0,0 +1,332 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store; + +use DateTimeImmutable; +use Doctrine\DBAL\ParameterType; +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; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use Throwable; + +class Repository extends BaseRepository +{ + protected string $tableNameConnectedService; + + public function __construct(Connection $connection, LoggerInterface $logger) + { + parent::__construct($connection, $logger); + + $this->tableNameConnectedService = $this->preparePrefixedTableName( + TableConstants::TABLE_NAME_CONNECTED_SERVICE + ); + } + + /** + * @throws StoreException + */ + public function getConnectedService(int $spId, int $userId): Result + { + try { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + /** @psalm-suppress TooManyArguments */ + $queryBuilder->select( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_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_LAST_AUTHENTICATION_AT, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT, + ) + ->from($this->tableNameConnectedService) + ->where( + $queryBuilder->expr()->and( + $queryBuilder->expr()->eq( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID, + $queryBuilder->createNamedParameter($spId, ParameterType::INTEGER) + ), + $queryBuilder->expr()->eq( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_USER_ID, + $queryBuilder->createNamedParameter($userId, ParameterType::INTEGER) + ) + ) + )->setMaxResults(1); + + return $queryBuilder->executeQuery(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing query to get connected service. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error($message, compact('spId', 'userId')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function insertConnectedService( + int $spId, + int $userId, + int $userVersionId, + DateTimeImmutable $firstAuthenticationAt = null, + DateTimeImmutable $lastAuthenticationAt = null, + 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(); + + $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( + 'Error executing query to insert connected service. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error( + $message, + compact('spId', 'userId', 'count', 'firstAuthenticationAt', 'lastAuthenticationAt') + ); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function updateConnectedServiceVersionCount( + int $connectedServiceId, + int $userVersionId, + DateTimeImmutable $happenedAt, + int $incrementCountBy = 1 + ): void { + $incrementCountBy = max($incrementCountBy, 1); + + $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) + ) + ) + ); + + try { + $updateCountQueryBuilder->executeStatement(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing update count for connected service. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error( + $message, + compact('connectedServiceId', 'userVersionId', 'happenedAt', 'incrementCountBy') + ); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function getConnectedServices(string $userIdentifierHashSha256): array + { + try { + $connectedServicesQueryBuilder = $this->connection->dbal()->createQueryBuilder(); + + /** @psalm-suppress TooManyArguments */ + $connectedServicesQueryBuilder->select( + BaseTableConstants::TABLE_ALIAS_SP . '.' . + BaseTableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID . ' AS ' . + TableConstants::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, + TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' . + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT . ' AS ' . + TableConstants::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, + 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, + )->from($this->tableNameConnectedService, TableConstants::TABLE_ALIAS_CONNECTED_SERVICE) + ->innerJoin( + //'ccs', + TableConstants::TABLE_ALIAS_CONNECTED_SERVICE, + //'cds_sp', + $this->tableNameSp, + //'cs', + BaseTableConstants::TABLE_ALIAS_SP, + //'ccs.sp_id = cs.id' + TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' . + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_SP_ID . + ' = ' . BaseTableConstants::TABLE_ALIAS_SP . '.' . + BaseTableConstants::TABLE_SP_COLUMN_NAME_ID + ) + ->innerJoin( + TableConstants::TABLE_ALIAS_CONNECTED_SERVICE, + $this->tableNameUser, + BaseTableConstants::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 + ) + ->innerJoin( + TableConstants::TABLE_ALIAS_CONNECTED_SERVICE, + $this->tableNameUserVersion, + BaseTableConstants::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 + ) + ->where( + BaseTableConstants::TABLE_ALIAS_USER . '.' . + BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . + $connectedServicesQueryBuilder->createNamedParameter($userIdentifierHashSha256) + )->orderBy( + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS, + 'DESC' + ); + + return $connectedServicesQueryBuilder->executeQuery()->fetchAllAssociative(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing query to get connected services. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error($message, compact('userIdentifierHashSha256')); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..d0331be7b040900eca9b549e26e09bdc11bec9e0 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Current/Store/TableConstants.php @@ -0,0 +1,33 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Current\Store; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Current\Store\TableConstants + as BaseTableConstants; + +class TableConstants +{ + // Table 'connected_service' (connected organizations). + 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 + public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT = 'first_authentication_at'; // datetime + public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT = 'last_authentication_at'; // datetime + 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/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawConnectedServiceProvider.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php similarity index 68% rename from src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawConnectedServiceProvider.php rename to src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php index d730b3150a45c2b6c468f16d2f3acf10614fd282..41334eaf9d90deca5f43f7269cc332c8794ba3b2 100644 --- a/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawConnectedServiceProvider.php +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedService.php @@ -2,14 +2,15 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal; 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; -use SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity; -class RawConnectedServiceProvider extends AbstractRawEntity +class RawConnectedService extends AbstractRawEntity { protected int $numberOfAuthentications; protected DateTimeImmutable $lastAuthenticationAt; @@ -22,23 +23,23 @@ class RawConnectedServiceProvider extends AbstractRawEntity parent::__construct($rawRow, $abstractPlatform); $this->numberOfAuthentications = (int)$rawRow[ - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS ]; $this->lastAuthenticationAt = $this->resolveDateTimeImmutable( - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT] + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT] ); $this->firstAuthenticationAt = $this->resolveDateTimeImmutable( - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT] + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT] ); $this->serviceProviderMetadata = $this->resolveServiceProviderMetadata( - (string)$rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] + (string)$rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] ); $this->userAttributes = $this->resolveUserAttributes( - (string)$rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES] + (string)$rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] ); } @@ -88,11 +89,11 @@ class RawConnectedServiceProvider extends AbstractRawEntity protected function validate(array $rawRow): void { $columnsToCheck = [ - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES, + 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, ]; foreach ($columnsToCheck as $column) { @@ -102,44 +103,44 @@ class RawConnectedServiceProvider extends AbstractRawEntity } if ( - ! is_numeric($rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]) + ! is_numeric($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]) ) { $message = sprintf( 'Column %s must be numeric.', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS ); throw new UnexpectedValueException($message); } /** @noinspection DuplicatedCode */ - if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT])) { + if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT])) { $message = sprintf( 'Column %s must be string.', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT ); throw new UnexpectedValueException($message); } - if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT])) { + if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT])) { $message = sprintf( 'Column %s must be string.', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT ); throw new UnexpectedValueException($message); } - if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA])) { + if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA])) { $message = sprintf( 'Column %s must be string.', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA ); throw new UnexpectedValueException($message); } - if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES])) { + if (! is_string($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES])) { $message = sprintf( 'Column %s must be string.', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES ); throw new UnexpectedValueException($message); } diff --git a/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php new file mode 100644 index 0000000000000000000000000000000000000000..6887a5bff291b1b36398e09e0cb633b9a6c94de7 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store.php @@ -0,0 +1,154 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned; + +use DateTimeImmutable; +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; +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 +{ + 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 + ); + + $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()); + + $idpId = $this->resolveIdpId($hashDecoratedState); + $idpVersionId = $this->resolveIdpVersionId($idpId, $hashDecoratedState); + $spId = $this->resolveSpId($hashDecoratedState); + $spVersionId = $this->resolveSpVersionId($spId, $hashDecoratedState); + $userId = $this->resolveUserId($hashDecoratedState); + $userVersionId = $this->resolveUserVersionId($userId, $hashDecoratedState); + $idpSpUserVersionId = $this->resolveIdpSpUserVersionId($idpVersionId, $spVersionId, $userVersionId); + + /** @psalm-suppress MixedAssignment */ + if ( + ($connectedServiceId = $this->repository->getConnectedService($idpSpUserVersionId)->fetchOne()) !== false + ) { + $this->repository->updateConnectedServiceVersionCount( + (int)$connectedServiceId, + $authenticationEvent->getHappenedAt() + ); + } else { + $this->repository->insertConnectedService($idpSpUserVersionId, $authenticationEvent->getHappenedAt()); + } + + $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 + */ + public function deleteDataOlderThan(DateTimeImmutable $dateTime): void + { + $this->repository->deleteConnectedServicesOlderThan($dateTime); + } +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..69e5d76ecd74ca9c29d542f05c5347a5ae6581f2 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000000CreateIdpTable extends Migrations\CreateIdpTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..70d4af584a1fe613ecd5f6bdcc10a9eab73a8717 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000100CreateIdpVersionTable extends Migrations\CreateIdpVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..7a23c8e2d04ef925576f5562d3c0a060429f67f5 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000200CreateSpTable extends Migrations\CreateSpTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..4a300071485ccee1eb28fcdba9997c295ffdfee0 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000300CreateSpVersionTable extends Migrations\CreateSpVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..30b6b9d0a242c20e4c21f95da9c4a8ab7b34abfa --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000400CreateUserTable extends Migrations\CreateUserTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..71c01901baa4bd83bd033ecfdc4a1e6258198c69 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000500CreateUserVersionTable extends Migrations\CreateUserVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..6d4427b40392a097f3ae3ed7bb7178f4e3782498 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTable.php @@ -0,0 +1,11 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +class Version20220801000600CreateIdpSpUserVersionTable extends Migrations\CreateIdpSpUserVersionTable +{ +} 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 new file mode 100644 index 0000000000000000000000000000000000000000..10df6dcf21266f608a8c70f457f8479247b5b601 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateConnectedServiceTable.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations; + +use Doctrine\DBAL\Schema\Table; +use Doctrine\DBAL\Types\Types; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; +use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; +use Throwable; + +use function sprintf; + +class Version20220801000700CreateConnectedServiceTable extends AbstractMigration +{ + protected function getLocalTablePrefix(): string + { + return 'vds_'; + } + + /** + * @inheritDoc + * @throws MigrationException + */ + public function run(): void + { + $tableName = $this->preparePrefixedTableName('connected_service'); + + try { + $table = new Table($tableName); + + $table->addColumn('id', Types::BIGINT) + ->setUnsigned(true) + ->setAutoincrement(true); + + $table->addColumn('idp_sp_user_version_id', Types::BIGINT) + ->setUnsigned(true); + + $table->addColumn('first_authentication_at', Types::DATETIMETZ_IMMUTABLE); + + $table->addColumn('last_authentication_at', Types::DATETIMETZ_IMMUTABLE); + + $table->addColumn('count', Types::BIGINT)->setUnsigned(true); + + $table->addColumn('created_at', Types::DATETIMETZ_IMMUTABLE); + + $table->addColumn('updated_at', Types::DATETIMETZ_IMMUTABLE); + + $table->setPrimaryKey(['id']); + + $table->addForeignKeyConstraint( + $this->preparePrefixedTableName('idp_sp_user_version'), + ['idp_sp_user_version_id'], + ['id'] + ); + + // Old data can be deleted using updated_at column, so add index for it. + $table->addIndex(['updated_at']); + + $table->addUniqueConstraint(['idp_sp_user_version_id']); + + $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('connected_service'); + + 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/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php new file mode 100644 index 0000000000000000000000000000000000000000..51f2f5a849382ce83cd96284b461e06bc1a098eb --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/Repository.php @@ -0,0 +1,612 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store; + +use DateTimeImmutable; +use Doctrine\DBAL\ParameterType; +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; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use Throwable; + +class Repository extends BaseRepository +{ + protected string $tableNameConnectedService; + + public function __construct(Connection $connection, LoggerInterface $logger) + { + parent::__construct($connection, $logger); + + $this->tableNameConnectedService = $this->preparePrefixedTableName( + TableConstants::TABLE_NAME_CONNECTED_SERVICE + ); + } + + /** + * @throws StoreException + */ + public function getConnectedService(int $idpSpUserVersionId): Result + { + try { + $queryBuilder = $this->connection->dbal()->createQueryBuilder(); + + /** @psalm-suppress TooManyArguments */ + $queryBuilder->select( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_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_LAST_AUTHENTICATION_AT, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_COUNT, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_CREATED_AT, + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT, + ) + ->from($this->tableNameConnectedService) + ->where( + $queryBuilder->expr()->and( + $queryBuilder->expr()->eq( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID, + $queryBuilder->createNamedParameter($idpSpUserVersionId, ParameterType::INTEGER) + ) + ) + )->setMaxResults(1); + + return $queryBuilder->executeQuery(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing query to get connected service. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error($message, compact('idpSpUserVersionId')); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function insertConnectedService( + int $idpSpUserVersionId, + DateTimeImmutable $firstAuthenticationAt = null, + DateTimeImmutable $lastAuthenticationAt = null, + 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(); + + $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( + 'Error executing query to insert connected service. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error( + $message, + compact('idpSpUserVersionId', 'count', 'firstAuthenticationAt', 'lastAuthenticationAt') + ); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function updateConnectedServiceVersionCount( + int $connectedServiceId, + DateTimeImmutable $happenedAt, + int $incrementCountBy = 1 + ): void { + $incrementCountBy = max($incrementCountBy, 1); + + $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) + ) + ) + ); + + try { + $updateCountQueryBuilder->executeStatement(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing update count for connected service. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error( + $message, + compact('connectedServiceId', 'happenedAt', 'incrementCountBy') + ); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function touchConnectedServiceVersionsTimestamp( + int $userId, + int $spId, + DateTimeImmutable $happenedAt = null + ): void { + $happenedAt = $happenedAt ?? new DateTimeImmutable(); + + $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, + //'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, + //'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, + //'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) + ) + ) + ); + + try { + /** @var array<array-key,string> $connectedServiceVersions */ + $connectedServiceVersions = $selectConnectedServiceVersionsQueryBuilder->executeQuery()->fetchFirstColumn(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error selecting connected service versions. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error( + $message, + compact('userId', 'spId', 'happenedAt') + ); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + + $updateLastAuthenticationAtQueryBuilder = $this->connection->dbal()->createQueryBuilder(); + + $updateLastAuthenticationAtQueryBuilder->update($this->tableNameConnectedService) + ->set( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT, + ':' . TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT + ) + ->setParameter( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_UPDATED_AT, + $happenedAt, + Types::DATETIMETZ_IMMUTABLE + ) + ->where( + $updateLastAuthenticationAtQueryBuilder->expr()->in( + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_ID, + $connectedServiceVersions + ) + ); + + try { + $updateLastAuthenticationAtQueryBuilder->executeStatement(); + } catch (Throwable $exception) { + $message = sprintf( + 'Error touching connected service versions. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error( + $message, + compact('userId', 'spId', 'happenedAt') + ); + throw new StoreException($message, (int)$exception->getCode(), $exception); + } + } + + /** + * @throws StoreException + */ + public function getConnectedServices(string $userIdentifierHashSha256): array + { + try { + $connectedServicesQueryBuilder = $this->connection->dbal()->createQueryBuilder(); + $lastMetadataAndAttributesQueryBuilder = $this->connection->dbal()->createQueryBuilder(); + + /** @psalm-suppress TooManyArguments */ + $connectedServicesQueryBuilder->select( + //'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, + //'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, + //'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, + //'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, + )->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, + //'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, + //'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, + //'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( + //'vu.identifier_hash_sha256 = ' . + BaseTableConstants::TABLE_ALIAS_USER . '.' . + BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . + $connectedServicesQueryBuilder->createNamedParameter($userIdentifierHashSha256) + )->groupBy( + //'sp_entity_id' + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_ENTITY_ID + )->orderBy( + //'number_of_authentications', + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS, + 'DESC' + ); + + /** @psalm-suppress TooManyArguments */ + $lastMetadataAndAttributesQueryBuilder->select( + //'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, + //'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, + //'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 + )->from( + //'vds_connected_service', + $this->tableNameConnectedService, + //'vcs' + 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, + //'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, + //'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, + //'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 + ) + // SP version join for latest one capability + ->leftJoin( + //'vsv', + BaseTableConstants::TABLE_ALIAS_SP_VERSION, + //'vds_sp_version', + $this->tableNameSpVersion, + //'vsv2', + BaseTableConstants::TABLE_ALIAS_SP_VERSION_2, // Another alias for self joining... + //'vsv.id = vsv2.id AND vsv.id < vsv2.id' // To be able to get latest one... + BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' . + BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . ' = ' . + BaseTableConstants::TABLE_ALIAS_SP_VERSION_2 . '.' . + BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . ' AND ' . + BaseTableConstants::TABLE_ALIAS_SP_VERSION . '.' . + BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . ' < ' . + BaseTableConstants::TABLE_ALIAS_SP_VERSION_2 . '.' . + BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID + ) + // User version join for latest one capability + ->leftJoin( + //'vuv', + BaseTableConstants::TABLE_ALIAS_USER_VERSION, + //'vds_user_version', + $this->tableNameUserVersion, + //'vuv2', + BaseTableConstants::TABLE_ALIAS_USER_VERSION_2, // Another alias for self joining... + //'vuv.id = vuv2.id AND vuv.id < vuv2.id' // To be able to get latest one... + BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' . + BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' = ' . + BaseTableConstants::TABLE_ALIAS_USER_VERSION_2 . '.' . + BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' AND ' . + BaseTableConstants::TABLE_ALIAS_USER_VERSION . '.' . + BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' < ' . + BaseTableConstants::TABLE_ALIAS_USER_VERSION_2 . '.' . + BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID + )->where( + //'vu.identifier_hash_sha256 = ' . + BaseTableConstants::TABLE_ALIAS_USER . '.' . + BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256 . ' = ' . + $lastMetadataAndAttributesQueryBuilder->createNamedParameter($userIdentifierHashSha256) + )->andWhere( + //'vsv2.id IS NULL' + BaseTableConstants::TABLE_ALIAS_SP_VERSION_2 . '.' . + BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID . ' IS NULL' + )->andWhere( + //'vuv2.id IS NULL' + BaseTableConstants::TABLE_ALIAS_USER_VERSION_2 . '.' . + BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID . ' IS NULL' + )->orderBy( + TableConstants::TABLE_ALIAS_CONNECTED_SERVICE . '.' . + TableConstants::TABLE_CONNECTED_SERVICE_COLUMN_NAME_IDP_SP_USER_VERSION_ID, + 'ASC' + ); + + $connectedServices = $connectedServicesQueryBuilder->executeQuery()->fetchAllAssociativeIndexed(); + $lastMetadataAndAttributes = $lastMetadataAndAttributesQueryBuilder + ->executeQuery() + ->fetchAllAssociativeIndexed(); + + return array_merge_recursive($connectedServices, $lastMetadataAndAttributes); + } catch (Throwable $exception) { + $message = sprintf( + 'Error executing query to get connected services. Error was: %s.', + $exception->getMessage() + ); + $this->logger->error($message, compact('userIdentifierHashSha256')); + 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 new file mode 100644 index 0000000000000000000000000000000000000000..028c4d501dc256f2363b09af35a46891b2ba7cf2 --- /dev/null +++ b/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/TableConstants.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store; + +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants + as BaseTableConstants; + +class TableConstants +{ + // Table 'connected_service' (connected organizations). + 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_SP_USER_VERSION_ID = 'idp_sp_user_version_id'; // int + public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT = 'first_authentication_at'; // datetime + public const TABLE_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT = 'last_authentication_at'; // datetime + 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/Stores/Bases/AbstractStore.php b/src/Data/Stores/Bases/AbstractStore.php similarity index 97% rename from src/Stores/Bases/AbstractStore.php rename to src/Data/Stores/Bases/AbstractStore.php index 09b5ff6becf1754a0effa93f01fe6f9ffa1a6dcf..d4966c0d1c1541a0ab5c51b423d54c1131e4f20c 100644 --- a/src/Stores/Bases/AbstractStore.php +++ b/src/Data/Stores/Bases/AbstractStore.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Bases; +namespace SimpleSAML\Module\accounting\Data\Stores\Bases; use Psr\Log\LoggerInterface; use ReflectionClass; diff --git a/src/Stores/Bases/DoctrineDbal/AbstractRawEntity.php b/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntity.php similarity index 95% rename from src/Stores/Bases/DoctrineDbal/AbstractRawEntity.php rename to src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntity.php index c832bd86061ad722c2662c47280bf09ce46cf3da..1f3ea885bbb1ca7deaec136fb1426f57e19f6a03 100644 --- a/src/Stores/Bases/DoctrineDbal/AbstractRawEntity.php +++ b/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntity.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal; +namespace SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; diff --git a/src/Stores/Bases/DoctrineDbal/AbstractStore.php b/src/Data/Stores/Bases/DoctrineDbal/AbstractStore.php similarity index 88% rename from src/Stores/Bases/DoctrineDbal/AbstractStore.php rename to src/Data/Stores/Bases/DoctrineDbal/AbstractStore.php index 0f992d994ea615091942623e5248f9bc222d3337..1e352da71886e220235177ad9834307885d33ec4 100644 --- a/src/Stores/Bases/DoctrineDbal/AbstractStore.php +++ b/src/Data/Stores/Bases/DoctrineDbal/AbstractStore.php @@ -2,18 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal; +namespace SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal; use Psr\Log\LoggerInterface; +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\Factory; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -abstract class AbstractStore extends \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore +abstract class AbstractStore extends \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore { protected Connection $connection; protected Migrator $migrator; diff --git a/src/Stores/Builders/Bases/AbstractStoreBuilder.php b/src/Data/Stores/Builders/Bases/AbstractStoreBuilder.php similarity index 89% rename from src/Stores/Builders/Bases/AbstractStoreBuilder.php rename to src/Data/Stores/Builders/Bases/AbstractStoreBuilder.php index 45c932149ef21a2a4811f2dd087316221ccc2515..86b03d69c10ae206a89c786b78fbdb5464ade22e 100644 --- a/src/Stores/Builders/Bases/AbstractStoreBuilder.php +++ b/src/Data/Stores/Builders/Bases/AbstractStoreBuilder.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Builders\Bases; +namespace SimpleSAML\Module\accounting\Data\Stores\Builders\Bases; use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\StoreInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Interfaces\StoreInterface; use Throwable; -use function sprintf; use function is_subclass_of; +use function sprintf; abstract class AbstractStoreBuilder { @@ -48,7 +48,7 @@ abstract class AbstractStoreBuilder } // Build store... - /** @var StoreInterface $store */ + /** @var \SimpleSAML\Module\accounting\Data\Stores\Interfaces\StoreInterface $store */ $store = $this->helpersManager->getInstanceBuilderUsingModuleConfiguration()->build( $class, $this->moduleConfiguration, diff --git a/src/Stores/Builders/DataStoreBuilder.php b/src/Data/Stores/Builders/DataStoreBuilder.php similarity index 74% rename from src/Stores/Builders/DataStoreBuilder.php rename to src/Data/Stores/Builders/DataStoreBuilder.php index 857b447f91ed1e51a7f61752e4023b231efffa94..847e68e8ec53af29b4892ccb76c67d97e6280c4a 100644 --- a/src/Stores/Builders/DataStoreBuilder.php +++ b/src/Data/Stores/Builders/DataStoreBuilder.php @@ -2,13 +2,14 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Builders; +namespace SimpleSAML\Module\accounting\Data\Stores\Builders; +use SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\DataStoreInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration\ConnectionType; -use SimpleSAML\Module\accounting\Stores\Interfaces\DataStoreInterface; -class DataStoreBuilder extends Bases\AbstractStoreBuilder +class DataStoreBuilder extends AbstractStoreBuilder { /** * @throws StoreException diff --git a/src/Stores/Builders/JobsStoreBuilder.php b/src/Data/Stores/Builders/JobsStoreBuilder.php similarity index 78% rename from src/Stores/Builders/JobsStoreBuilder.php rename to src/Data/Stores/Builders/JobsStoreBuilder.php index 9ad37849728be1192c9873e36f6326b00e641d45..82704fad638176921c5811de82622b38241f4e85 100644 --- a/src/Stores/Builders/JobsStoreBuilder.php +++ b/src/Data/Stores/Builders/JobsStoreBuilder.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Builders; +namespace SimpleSAML\Module\accounting\Data\Stores\Builders; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\JobsStoreInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration\ConnectionType; -use SimpleSAML\Module\accounting\Stores\Interfaces\JobsStoreInterface; use function sprintf; -class JobsStoreBuilder extends Bases\AbstractStoreBuilder +class JobsStoreBuilder extends \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder { /** * @throws StoreException diff --git a/src/Stores/Connections/Bases/AbstractMigrator.php b/src/Data/Stores/Connections/Bases/AbstractMigrator.php similarity index 96% rename from src/Stores/Connections/Bases/AbstractMigrator.php rename to src/Data/Stores/Connections/Bases/AbstractMigrator.php index ae7e0feafcb7424f4b720993a58caee8b24e64d7..97cdaa2bd95455c94aabe4a395914e4c3f9f7a82 100644 --- a/src/Stores/Connections/Bases/AbstractMigrator.php +++ b/src/Data/Stores/Connections/Bases/AbstractMigrator.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Connections\Bases; +namespace SimpleSAML\Module\accounting\Data\Stores\Connections\Bases; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\MigrationInterface; use SimpleSAML\Module\accounting\Exceptions\InvalidValueException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface; use Throwable; abstract class AbstractMigrator diff --git a/src/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php b/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php similarity index 89% rename from src/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php rename to src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php index 9619f19bb7b4a90d9175a18608930e15f160d7c4..f4e37ec68eab9a391121a862bb03ae8ade8faca2 100644 --- a/src/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php +++ b/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigration.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases; +namespace SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\MigrationInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; use Throwable; abstract class AbstractMigration implements MigrationInterface diff --git a/src/Stores/Connections/DoctrineDbal/Connection.php b/src/Data/Stores/Connections/DoctrineDbal/Connection.php similarity index 91% rename from src/Stores/Connections/DoctrineDbal/Connection.php rename to src/Data/Stores/Connections/DoctrineDbal/Connection.php index 69c6202cb4c370a4ff7af039c8d1730a11786491..de4e520e70e8a98b132c40611c87cb2fd2d74223 100644 --- a/src/Stores/Connections/DoctrineDbal/Connection.php +++ b/src/Data/Stores/Connections/DoctrineDbal/Connection.php @@ -2,12 +2,12 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal; +namespace SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Exception; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\ConnectionInterface; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; -use SimpleSAML\Module\accounting\Stores\Interfaces\ConnectionInterface; class Connection implements ConnectionInterface { diff --git a/src/Stores/Connections/DoctrineDbal/Factory.php b/src/Data/Stores/Connections/DoctrineDbal/Factory.php similarity index 92% rename from src/Stores/Connections/DoctrineDbal/Factory.php rename to src/Data/Stores/Connections/DoctrineDbal/Factory.php index b447dbf13c6b356c6f94343e116c4081b6227a9b..e5a748537429b5c3c9a42c3216d042a6b1cf0384 100644 --- a/src/Stores/Connections/DoctrineDbal/Factory.php +++ b/src/Data/Stores/Connections/DoctrineDbal/Factory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal; +namespace SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal; use Psr\Log\LoggerInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; diff --git a/src/Stores/Connections/DoctrineDbal/Migrator.php b/src/Data/Stores/Connections/DoctrineDbal/Migrator.php similarity index 94% rename from src/Stores/Connections/DoctrineDbal/Migrator.php rename to src/Data/Stores/Connections/DoctrineDbal/Migrator.php index 80464462def1241e26a7e54ccee8f60c323f94d3..ad7f87378ff4bae3628fa2614273f3e3006e904f 100644 --- a/src/Stores/Connections/DoctrineDbal/Migrator.php +++ b/src/Data/Stores/Connections/DoctrineDbal/Migrator.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal; +namespace SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal; use DateTimeImmutable; use Doctrine\DBAL\Schema\AbstractSchemaManager; @@ -11,12 +11,12 @@ use Doctrine\DBAL\Types\Types; use Psr\Log\LoggerInterface; use ReflectionClass; use ReflectionException; +use SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\MigrationInterface; use SimpleSAML\Module\accounting\Exceptions\InvalidValueException; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface; use Throwable; class Migrator extends AbstractMigrator diff --git a/src/Data/Stores/Interfaces/ActivityInterface.php b/src/Data/Stores/Interfaces/ActivityInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..1dfd67b218705423cac664433e5c5a649771fc29 --- /dev/null +++ b/src/Data/Stores/Interfaces/ActivityInterface.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; + +use SimpleSAML\Module\accounting\Entities\Activity; + +interface ActivityInterface extends DataStoreInterface +{ + public function getActivity(string $userIdentifier, int $maxResults, int $firstResult): Activity\Bag; +} diff --git a/src/Data/Stores/Interfaces/ConnectedServicesInterface.php b/src/Data/Stores/Interfaces/ConnectedServicesInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..9c39afc5ea2f5a42a968e453ee261c43937a76f5 --- /dev/null +++ b/src/Data/Stores/Interfaces/ConnectedServicesInterface.php @@ -0,0 +1,12 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; + +use SimpleSAML\Module\accounting\Entities\ConnectedService; + +interface ConnectedServicesInterface extends DataStoreInterface +{ + public function getConnectedServices(string $userIdentifier): ConnectedService\Bag; +} diff --git a/src/Stores/Interfaces/ConnectionInterface.php b/src/Data/Stores/Interfaces/ConnectionInterface.php similarity index 51% rename from src/Stores/Interfaces/ConnectionInterface.php rename to src/Data/Stores/Interfaces/ConnectionInterface.php index b61d3208388cd61c501b2df5d93530628f501f59..b1e5aa93da1b9245bac122c1ae8e349a484c3a3d 100644 --- a/src/Stores/Interfaces/ConnectionInterface.php +++ b/src/Data/Stores/Interfaces/ConnectionInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; interface ConnectionInterface { diff --git a/src/Stores/Interfaces/DataStoreInterface.php b/src/Data/Stores/Interfaces/DataStoreInterface.php similarity index 61% rename from src/Stores/Interfaces/DataStoreInterface.php rename to src/Data/Stores/Interfaces/DataStoreInterface.php index ce578f49c1382c9864535712b80801e58a221c58..c025fe8878dcff55661314864a5f723ebdc17792 100644 --- a/src/Stores/Interfaces/DataStoreInterface.php +++ b/src/Data/Stores/Interfaces/DataStoreInterface.php @@ -2,13 +2,11 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; use DateTimeImmutable; use Psr\Log\LoggerInterface; use SimpleSAML\Module\accounting\Entities\Authentication\Event; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; -use SimpleSAML\Module\accounting\Entities\Activity; use SimpleSAML\Module\accounting\ModuleConfiguration; interface DataStoreInterface extends StoreInterface @@ -22,9 +20,5 @@ interface DataStoreInterface extends StoreInterface public function persist(Event $authenticationEvent): void; - public function getConnectedOrganizations(string $userIdentifierHashSha256): ConnectedServiceProvider\Bag; - - public function getActivity(string $userIdentifierHashSha256, int $maxResults, int $firstResult): Activity\Bag; - public function deleteDataOlderThan(DateTimeImmutable $dateTime): void; } diff --git a/src/Stores/Interfaces/JobsStoreInterface.php b/src/Data/Stores/Interfaces/JobsStoreInterface.php similarity index 93% rename from src/Stores/Interfaces/JobsStoreInterface.php rename to src/Data/Stores/Interfaces/JobsStoreInterface.php index 43f9bdb28d86e40c6659ce63d1c78c1530600278..46c93ecb34e67655b7579530206df2fb421bf977 100644 --- a/src/Stores/Interfaces/JobsStoreInterface.php +++ b/src/Data/Stores/Interfaces/JobsStoreInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; use Psr\Log\LoggerInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface; diff --git a/src/Stores/Interfaces/MigrationInterface.php b/src/Data/Stores/Interfaces/MigrationInterface.php similarity index 81% rename from src/Stores/Interfaces/MigrationInterface.php rename to src/Data/Stores/Interfaces/MigrationInterface.php index ce7e523381d6e84b95720bc319baaa8e380cf12e..9c0c2f695bf7323ca1c55d9cfec38cbd33f372f1 100644 --- a/src/Stores/Interfaces/MigrationInterface.php +++ b/src/Data/Stores/Interfaces/MigrationInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; interface MigrationInterface { diff --git a/src/Stores/Interfaces/StoreInterface.php b/src/Data/Stores/Interfaces/StoreInterface.php similarity index 89% rename from src/Stores/Interfaces/StoreInterface.php rename to src/Data/Stores/Interfaces/StoreInterface.php index 29b1bd0b9734eef04185e3781e4c67b08702e94b..3241c9f384b48460b23c99e80b6cc0a70ee8eb8f 100644 --- a/src/Stores/Interfaces/StoreInterface.php +++ b/src/Data/Stores/Interfaces/StoreInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Stores\Interfaces; use Psr\Log\LoggerInterface; use SimpleSAML\Module\accounting\Interfaces\BuildableUsingModuleConfigurationInterface; diff --git a/src/Stores/Jobs/DoctrineDbal/Store.php b/src/Data/Stores/Jobs/DoctrineDbal/Store.php similarity index 91% rename from src/Stores/Jobs/DoctrineDbal/Store.php rename to src/Data/Stores/Jobs/DoctrineDbal/Store.php index 0d5f8046a85f9064c232287a7369c4c3b7b90f8f..e81d2221ea93166b730735d43df0a3a0935a9365 100644 --- a/src/Stores/Jobs/DoctrineDbal/Store.php +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store.php @@ -2,19 +2,19 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal; use Doctrine\DBAL\Exception; use Psr\Log\LoggerInterface; +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\JobsStoreInterface; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Repository; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use SimpleSAML\Module\accounting\Stores\Interfaces\JobsStoreInterface; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Repository; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\TableConstants; use Throwable; class Store extends AbstractStore implements JobsStoreInterface diff --git a/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php similarity index 86% rename from src/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php rename to src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php index d602ec68b4e279bdfcdf075710e7b107e89ff285..b88ac7f21f3df6ef0c5b3b2dbd17f345c80a31a7 100644 --- a/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Bases/AbstractCreateJobsTable.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Types\Types; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\TableConstants; use Throwable; use function sprintf; diff --git a/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php similarity index 58% rename from src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php rename to src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php index 838b202bcb7403e608126e9c32f70fd62d4c79ef..49bbd7f52d7c2543b9b79e42f08a6e1ee6499583 100644 --- a/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTable.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; class Version20220601000000CreateJobTable extends Store\Migrations\Bases\AbstractCreateJobsTable { diff --git a/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php new file mode 100644 index 0000000000000000000000000000000000000000..0fbd60a2ffcbcc98036f72999fe9e8a4dcf83187 --- /dev/null +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations; + +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable; + +class Version20220601000100CreateJobFailedTable extends AbstractCreateJobsTable +{ + protected function getJobsTableName(): string + { + return 'job_failed'; + } +} diff --git a/src/Stores/Jobs/DoctrineDbal/Store/RawJob.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJob.php similarity index 60% rename from src/Stores/Jobs/DoctrineDbal/Store/RawJob.php rename to src/Data/Stores/Jobs/DoctrineDbal/Store/RawJob.php index b302feeba47c8ce5330e1afc9d2bd6c36f1fb9c0..1377034838d04d7bea948f156612b3d4836e2926 100644 --- a/src/Stores/Jobs/DoctrineDbal/Store/RawJob.php +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJob.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; +use SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Module\accounting\Entities\Bases\AbstractPayload; use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; use function sprintf; @@ -24,19 +24,19 @@ class RawJob extends AbstractRawEntity { parent::__construct($rawRow, $abstractPlatform); - $this->id = (int)$rawRow[Store\TableConstants::COLUMN_NAME_ID]; - $this->payload = $this->resolvePayload((string)$rawRow[Store\TableConstants::COLUMN_NAME_PAYLOAD]); - $this->type = (string)$rawRow[Store\TableConstants::COLUMN_NAME_TYPE]; - $this->createdAt = $this->resolveDateTimeImmutable($rawRow[Store\TableConstants::COLUMN_NAME_CREATED_AT]); + $this->id = (int)$rawRow[TableConstants::COLUMN_NAME_ID]; + $this->payload = $this->resolvePayload((string)$rawRow[TableConstants::COLUMN_NAME_PAYLOAD]); + $this->type = (string)$rawRow[TableConstants::COLUMN_NAME_TYPE]; + $this->createdAt = $this->resolveDateTimeImmutable($rawRow[TableConstants::COLUMN_NAME_CREATED_AT]); } protected function validate(array $rawRow): void { $columnsToCheck = [ - Store\TableConstants::COLUMN_NAME_ID, - Store\TableConstants::COLUMN_NAME_PAYLOAD, - Store\TableConstants::COLUMN_NAME_TYPE, - Store\TableConstants::COLUMN_NAME_CREATED_AT, + TableConstants::COLUMN_NAME_ID, + TableConstants::COLUMN_NAME_PAYLOAD, + TableConstants::COLUMN_NAME_TYPE, + TableConstants::COLUMN_NAME_CREATED_AT, ]; foreach ($columnsToCheck as $column) { @@ -45,27 +45,27 @@ class RawJob extends AbstractRawEntity } } - if (! is_numeric($rawRow[Store\TableConstants::COLUMN_NAME_ID])) { + if (! is_numeric($rawRow[TableConstants::COLUMN_NAME_ID])) { throw new UnexpectedValueException( - sprintf('Column %s must be numeric.', Store\TableConstants::COLUMN_NAME_ID) + sprintf('Column %s must be numeric.', TableConstants::COLUMN_NAME_ID) ); } - if (! is_string($rawRow[Store\TableConstants::COLUMN_NAME_PAYLOAD])) { + if (! is_string($rawRow[TableConstants::COLUMN_NAME_PAYLOAD])) { throw new UnexpectedValueException( - sprintf('Column %s must be string.', Store\TableConstants::COLUMN_NAME_PAYLOAD) + sprintf('Column %s must be string.', TableConstants::COLUMN_NAME_PAYLOAD) ); } - if (! is_string($rawRow[Store\TableConstants::COLUMN_NAME_TYPE])) { + if (! is_string($rawRow[TableConstants::COLUMN_NAME_TYPE])) { throw new UnexpectedValueException( - sprintf('Column %s must be string.', Store\TableConstants::COLUMN_NAME_TYPE) + sprintf('Column %s must be string.', TableConstants::COLUMN_NAME_TYPE) ); } - if (! is_string($rawRow[Store\TableConstants::COLUMN_NAME_CREATED_AT])) { + if (! is_string($rawRow[TableConstants::COLUMN_NAME_CREATED_AT])) { throw new UnexpectedValueException( - sprintf('Column %s must be string.', Store\TableConstants::COLUMN_NAME_CREATED_AT) + sprintf('Column %s must be string.', TableConstants::COLUMN_NAME_CREATED_AT) ); } } diff --git a/src/Stores/Jobs/DoctrineDbal/Store/Repository.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/Repository.php similarity index 96% rename from src/Stores/Jobs/DoctrineDbal/Store/Repository.php rename to src/Data/Stores/Jobs/DoctrineDbal/Store/Repository.php index 09a17fff67a9f7012d26f3587bac2d9a1c514fce..23f0842762de563fe40fe96a62d74eec3ad41149 100644 --- a/src/Stores/Jobs/DoctrineDbal/Store/Repository.php +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store/Repository.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use Doctrine\DBAL\Types\Types; use Psr\Log\LoggerInterface; use ReflectionClass; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Module\accounting\Entities\GenericJob; use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface; use SimpleSAML\Module\accounting\Exceptions\StoreException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; use Throwable; class Repository diff --git a/src/Stores/Jobs/DoctrineDbal/Store/TableConstants.php b/src/Data/Stores/Jobs/DoctrineDbal/Store/TableConstants.php similarity index 84% rename from src/Stores/Jobs/DoctrineDbal/Store/TableConstants.php rename to src/Data/Stores/Jobs/DoctrineDbal/Store/TableConstants.php index 55f1779217f7d8467ac5e2cb775c6a9c60920c99..e3ded0c231b90f2d4565e569036e94bbb0eda3bf 100644 --- a/src/Stores/Jobs/DoctrineDbal/Store/TableConstants.php +++ b/src/Data/Stores/Jobs/DoctrineDbal/Store/TableConstants.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; class TableConstants { diff --git a/src/Stores/Jobs/PhpRedis/RedisStore.php b/src/Data/Stores/Jobs/PhpRedis/RedisStore.php similarity index 96% rename from src/Stores/Jobs/PhpRedis/RedisStore.php rename to src/Data/Stores/Jobs/PhpRedis/RedisStore.php index 0aa6f04cddaa2047d5a5219a9887a1af986705cf..c61b4ecf190254c88f5addb0057b83a2dcca357b 100644 --- a/src/Stores/Jobs/PhpRedis/RedisStore.php +++ b/src/Data/Stores/Jobs/PhpRedis/RedisStore.php @@ -2,16 +2,16 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Stores\Jobs\PhpRedis; +namespace SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis; use Psr\Log\LoggerInterface; use Redis; +use SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\JobsStoreInterface; use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores\Bases\AbstractStore; -use SimpleSAML\Module\accounting\Stores\Interfaces\JobsStoreInterface; use Throwable; class RedisStore extends AbstractStore implements JobsStoreInterface diff --git a/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php b/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php new file mode 100644 index 0000000000000000000000000000000000000000..8f30682233ad5f30284d94da2d16c25a15ef8f40 --- /dev/null +++ b/src/Data/Trackers/Activity/DoctrineDbal/VersionedDataTracker.php @@ -0,0 +1,40 @@ +<?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\VersionedDataProvider; +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 VersionedDataTracker extends VersionedDataProvider implements DataTrackerInterface +{ + /** + * @throws StoreException + */ + public static function build( + ModuleConfiguration $moduleConfiguration, + LoggerInterface $logger, + string $connectionType = ModuleConfiguration\ConnectionType::MASTER + ): self { + return new self($moduleConfiguration, $logger, $connectionType); + } + + public function process(Event $authenticationEvent): void + { + $this->store->persist($authenticationEvent); + } + + public function enforceDataRetentionPolicy(DateInterval $retentionPolicy): void + { + $dateTime = (new DateTimeImmutable())->sub($retentionPolicy); + + $this->store->deleteDataOlderThan($dateTime); + } +} diff --git a/src/Trackers/Builders/AuthenticationDataTrackerBuilder.php b/src/Data/Trackers/Builders/DataTrackerBuilder.php similarity index 71% rename from src/Trackers/Builders/AuthenticationDataTrackerBuilder.php rename to src/Data/Trackers/Builders/DataTrackerBuilder.php index 7a58645c59f2a9b3e043454c888d54f2a836cfe4..51b1654652ae93b24fde2f338b6233b0c5574a4c 100644 --- a/src/Trackers/Builders/AuthenticationDataTrackerBuilder.php +++ b/src/Data/Trackers/Builders/DataTrackerBuilder.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Trackers\Builders; +namespace SimpleSAML\Module\accounting\Data\Trackers\Builders; use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; 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\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface; use Throwable; -class AuthenticationDataTrackerBuilder +class DataTrackerBuilder { protected ModuleConfiguration $moduleConfiguration; protected LoggerInterface $logger; @@ -31,22 +31,22 @@ class AuthenticationDataTrackerBuilder /** * @throws Exception */ - public function build(string $class): AuthenticationDataTrackerInterface + public function build(string $class): DataTrackerInterface { try { // Make sure that the class implements proper interface - if (!is_subclass_of($class, AuthenticationDataTrackerInterface::class)) { + if (!is_subclass_of($class, DataTrackerInterface::class)) { $message = sprintf( 'Class %s does not implement interface %s.', $class, - AuthenticationDataTrackerInterface::class + DataTrackerInterface::class ); throw new UnexpectedValueException($message); } // Build... - /** @var AuthenticationDataTrackerInterface $store */ - $store = $this->helpersManager->getInstanceBuilderUsingModuleConfiguration()->build( + /** @var DataTrackerInterface $tracker */ + $tracker = $this->helpersManager->getInstanceBuilderUsingModuleConfiguration()->build( $class, $this->moduleConfiguration, $this->logger @@ -56,6 +56,6 @@ class AuthenticationDataTrackerBuilder throw new Exception($message, (int)$exception->getCode(), $exception); } - return $store; + return $tracker; } } diff --git a/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php b/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php new file mode 100644 index 0000000000000000000000000000000000000000..0ff530fcada5f5aa1916c2efc5fad8e3eb95d89b --- /dev/null +++ b/src/Data/Trackers/ConnectedServices/DoctrineDbal/CurrentDataTracker.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal; + +use DateInterval; +use DateTimeImmutable; +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\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); + } + + public function process(Event $authenticationEvent): void + { + $this->store->persist($authenticationEvent); + } + + public function enforceDataRetentionPolicy(DateInterval $retentionPolicy): void + { + $dateTime = (new DateTimeImmutable())->sub($retentionPolicy); + + $this->store->deleteDataOlderThan($dateTime); + } +} diff --git a/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php b/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php new file mode 100644 index 0000000000000000000000000000000000000000..96013b0974ac4eed693db57b36d8a4a7634c432b --- /dev/null +++ b/src/Data/Trackers/ConnectedServices/DoctrineDbal/VersionedDataTracker.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal; + +use DateInterval; +use DateTimeImmutable; +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider; +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 VersionedDataTracker extends VersionedDataProvider implements DataTrackerInterface +{ + /** + * @throws StoreException + */ + public static function build( + ModuleConfiguration $moduleConfiguration, + LoggerInterface $logger, + string $connectionType = ModuleConfiguration\ConnectionType::MASTER + ): self { + return new self($moduleConfiguration, $logger, $connectionType); + } + + public function process(Event $authenticationEvent): void + { + $this->store->persist($authenticationEvent); + } + + public function enforceDataRetentionPolicy(DateInterval $retentionPolicy): void + { + $dateTime = (new DateTimeImmutable())->sub($retentionPolicy); + + $this->store->deleteDataOlderThan($dateTime); + } +} diff --git a/src/Trackers/Interfaces/AuthenticationDataTrackerInterface.php b/src/Data/Trackers/Interfaces/DataTrackerInterface.php similarity index 77% rename from src/Trackers/Interfaces/AuthenticationDataTrackerInterface.php rename to src/Data/Trackers/Interfaces/DataTrackerInterface.php index ca0fb42c11391c4adb30222f86eea9ad056633ff..975e5e3c0d96f3ad91350badf73606bb6551812a 100644 --- a/src/Trackers/Interfaces/AuthenticationDataTrackerInterface.php +++ b/src/Data/Trackers/Interfaces/DataTrackerInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Trackers\Interfaces; +namespace SimpleSAML\Module\accounting\Data\Trackers\Interfaces; use DateInterval; use Psr\Log\LoggerInterface; @@ -11,7 +11,7 @@ use SimpleSAML\Module\accounting\Interfaces\BuildableUsingModuleConfigurationInt use SimpleSAML\Module\accounting\Interfaces\SetupableInterface; use SimpleSAML\Module\accounting\ModuleConfiguration; -interface AuthenticationDataTrackerInterface extends BuildableUsingModuleConfigurationInterface, SetupableInterface +interface DataTrackerInterface extends BuildableUsingModuleConfigurationInterface, SetupableInterface { public static function build(ModuleConfiguration $moduleConfiguration, LoggerInterface $logger): self; diff --git a/src/Entities/Authentication/Event/State/Oidc.php b/src/Entities/Authentication/Event/State/Oidc.php index b9c384f0edbdf5fa0e1c2becd272ed2e2f5484b0..a2243145d4dabca63fcee02a29900d9598e62699 100644 --- a/src/Entities/Authentication/Event/State/Oidc.php +++ b/src/Entities/Authentication/Event/State/Oidc.php @@ -31,7 +31,7 @@ class Oidc extends AbstractState return $oidcState[self::KEY_OPEN_ID_PROVIDER_METADATA]; } - throw new UnexpectedValueException('State array does not contain OpenID Provider metadata.'); + throw new UnexpectedValueException('State array does not contain OpenID VersionedDataProvider metadata.'); } protected function resolveIdentityProviderEntityId(): string @@ -43,7 +43,7 @@ class Oidc extends AbstractState return $this->identityProviderMetadata[Identity\Oidc::METADATA_KEY_ENTITY_ID]; } - throw new UnexpectedValueException('OpenID Provider metadata array does not contain issuer.'); + throw new UnexpectedValueException('OpenID VersionedDataProvider metadata array does not contain issuer.'); } protected function resolveServiceProviderMetadata(array $state): array diff --git a/src/Entities/ConnectedServiceProvider.php b/src/Entities/ConnectedService.php similarity index 94% rename from src/Entities/ConnectedServiceProvider.php rename to src/Entities/ConnectedService.php index b1ded7a7aa28e730ea156f244843d27c0107e042..2a6347b02c7178bf58ed7d98439ea47bbcdfa735 100644 --- a/src/Entities/ConnectedServiceProvider.php +++ b/src/Entities/ConnectedService.php @@ -8,9 +8,9 @@ use DateTimeImmutable; use SimpleSAML\Module\accounting\Entities\Interfaces\ServiceProviderInterface; /** - * Represents a Service Provider to which a user has authenticated at least once. + * Represents a Service VersionedDataProvider to which a user has authenticated at least once. */ -class ConnectedServiceProvider +class ConnectedService { protected ServiceProviderInterface $serviceProvider; protected int $numberOfAuthentications; diff --git a/src/Entities/ConnectedServiceProvider/Bag.php b/src/Entities/ConnectedService/Bag.php similarity index 53% rename from src/Entities/ConnectedServiceProvider/Bag.php rename to src/Entities/ConnectedService/Bag.php index 9507d8f45aa072d8c3cce369305fe9e417b7180d..09f468c2964addc4888a80078c2603d1815dd94f 100644 --- a/src/Entities/ConnectedServiceProvider/Bag.php +++ b/src/Entities/ConnectedService/Bag.php @@ -2,22 +2,22 @@ declare(strict_types=1); -namespace SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; +namespace SimpleSAML\Module\accounting\Entities\ConnectedService; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; +use SimpleSAML\Module\accounting\Entities\ConnectedService; class Bag { /** - * @var ConnectedServiceProvider[] + * @var ConnectedService[] */ protected array $connectedServiceProviders = []; - public function addOrReplace(ConnectedServiceProvider $connectedServiceProvider): void + public function addOrReplace(ConnectedService $connectedService): void { - $spEntityId = $connectedServiceProvider->getServiceProvider()->getEntityId(); + $spEntityId = $connectedService->getServiceProvider()->getEntityId(); - $this->connectedServiceProviders[$spEntityId] = $connectedServiceProvider; + $this->connectedServiceProviders[$spEntityId] = $connectedService; } public function getAll(): array diff --git a/src/Entities/Providers/Identity/Oidc.php b/src/Entities/Providers/Identity/Oidc.php index d326113ce1918f16819d8eb0b798bd17b8fb3328..c662f78753be98476d0c03d8086fb94a0dafa24b 100644 --- a/src/Entities/Providers/Identity/Oidc.php +++ b/src/Entities/Providers/Identity/Oidc.php @@ -36,7 +36,7 @@ class Oidc extends AbstractProvider implements IdentityProviderInterface return $this->metadata[self::METADATA_KEY_ENTITY_ID]; } - throw new MetadataException('OpenID Provider metadata does not contain entity ID.'); + throw new MetadataException('OpenID VersionedDataProvider metadata does not contain entity ID.'); } public function getProtocol(): AuthenticationProtocolInterface diff --git a/src/Entities/Providers/Service/Oidc.php b/src/Entities/Providers/Service/Oidc.php index 382eabdf1815a3598b8f60bc168c33242febfeb6..5f5ff82477128169db5708b40932172d79b54a6d 100644 --- a/src/Entities/Providers/Service/Oidc.php +++ b/src/Entities/Providers/Service/Oidc.php @@ -38,7 +38,7 @@ class Oidc extends AbstractProvider implements ServiceProviderInterface return $this->metadata[self::METADATA_KEY_ENTITY_ID]; } - throw new MetadataException('Relying Provider metadata does not contain entity ID.'); + throw new MetadataException('Relying VersionedDataProvider metadata does not contain entity ID.'); } public function getProtocol(): AuthenticationProtocolInterface diff --git a/src/Http/Controllers/Admin/Configuration.php b/src/Http/Controllers/Admin/Configuration.php index cd5d9534f49bc0d2bdfb63c3562c812c3848aa4e..b1b2433957d0948b1a7ffd34994e8757d2f96421 100644 --- a/src/Http/Controllers/Admin/Configuration.php +++ b/src/Http/Controllers/Admin/Configuration.php @@ -7,11 +7,12 @@ namespace SimpleSAML\Module\accounting\Http\Controllers\Admin; use Exception; use Psr\Log\LoggerInterface; use SimpleSAML\Configuration as SspConfiguration; +use SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder; +use SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder; +use SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder; use SimpleSAML\Module\accounting\Helpers\Routes; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; use SimpleSAML\Session; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; @@ -59,7 +60,7 @@ class Configuration $moduleConfiguration = null; $configurationValidationErrors = null; $jobsStore = null; - $defaultDataTrackerAndProvider = null; + $providers = []; $additionalTrackers = []; $setupNeeded = false; $runSetup = $request->query->has('runSetup'); @@ -67,16 +68,17 @@ class Configuration try { $moduleConfiguration = new ModuleConfiguration(); - $defaultDataTrackerAndProvider = - (new AuthenticationDataTrackerBuilder($moduleConfiguration, $this->logger, $this->helpersManager)) - ->build($moduleConfiguration->getDefaultDataTrackerAndProviderClass()); - - if ($defaultDataTrackerAndProvider->needsSetup()) { - if ($runSetup) { - $defaultDataTrackerAndProvider->runSetup(); - } else { - $setupNeeded = true; + $dataProviderBuilder = new DataProviderBuilder($moduleConfiguration, $this->logger, $this->helpersManager); + foreach ($moduleConfiguration->getProviderClasses() as $providerClass) { + $providerInstance = $dataProviderBuilder->build($providerClass); + if ($providerInstance->needsSetup()) { + if ($runSetup) { + $providerInstance->runSetup(); + } else { + $setupNeeded = true; + } } + $providers[$providerClass] = $providerInstance; } if ( @@ -94,19 +96,18 @@ class Configuration } } + $dataTrackerBuilder = new DataTrackerBuilder($moduleConfiguration, $this->logger, $this->helpersManager); foreach ($moduleConfiguration->getAdditionalTrackers() as $trackerClass) { - $additionalTrackerInstance = - (new AuthenticationDataTrackerBuilder($moduleConfiguration, $this->logger, $this->helpersManager)) - ->build($trackerClass); + $trackerInstance = $dataTrackerBuilder->build($trackerClass); - if ($additionalTrackerInstance->needsSetup()) { + if ($trackerInstance->needsSetup()) { if ($runSetup) { - $additionalTrackerInstance->runSetup(); + $trackerInstance->runSetup(); } else { $setupNeeded = true; } } - $additionalTrackers[$trackerClass] = $additionalTrackerInstance; + $additionalTrackers[$trackerClass] = $trackerInstance; } } catch (Throwable $exception) { $configurationValidationErrors = $exception->getMessage(); @@ -116,11 +117,12 @@ class Configuration 'moduleConfiguration' => $moduleConfiguration, 'configurationValidationErrors' => $configurationValidationErrors, 'jobsStore' => $jobsStore, - 'defaultDataTrackerAndProvider' => $defaultDataTrackerAndProvider, + 'providers' => $providers, 'additionalTrackers' => $additionalTrackers, 'setupNeeded' => $setupNeeded, 'profilePageUri' => $this->helpersManager->getRoutes() ->getUrl(Routes::PATH_USER_PERSONAL_DATA), + 'runSetup' => $runSetup, ]; $template = new Template($this->sspConfiguration, 'accounting:admin/configuration/status.twig'); diff --git a/src/Http/Controllers/User/Profile.php b/src/Http/Controllers/User/Profile.php index c144a97b3bc35368582813b296cf54f9a654352d..09dd023332d1f9c6fba630d2cab536886382e82c 100644 --- a/src/Http/Controllers/User/Profile.php +++ b/src/Http/Controllers/User/Profile.php @@ -11,23 +11,28 @@ use SimpleSAML\Error\ConfigurationError; use SimpleSAML\Error\CriticalConfigurationError; use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Locale\Translate; +use SimpleSAML\Metadata\MetaDataStorageHandler; +use SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\ActivityInterface; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\DataProviderInterface; use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Oidc; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; +use SimpleSAML\Module\accounting\Entities\ConnectedService; use SimpleSAML\Module\accounting\Entities\User; use SimpleSAML\Module\accounting\Exceptions\Exception; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; use SimpleSAML\Module\accounting\Helpers\Attributes; +use SimpleSAML\Module\accounting\Helpers\ProviderResolver; use SimpleSAML\Module\accounting\Helpers\Routes; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\ModuleConfiguration\ConnectionType; -use SimpleSAML\Module\accounting\Providers\Builders\AuthenticationDataProviderBuilder; -use SimpleSAML\Module\accounting\Providers\Interfaces\AuthenticationDataProviderInterface; use SimpleSAML\Module\accounting\Services\AlertsBag; use SimpleSAML\Module\accounting\Services\CsrfToken; use SimpleSAML\Module\accounting\Services\HelpersManager; use SimpleSAML\Module\accounting\Services\MenuManager; use SimpleSAML\Module\accounting\Services\SspModuleManager; +use SimpleSAML\Module\oidc\Services\OidcOpenIdProviderMetadataService; use SimpleSAML\Session; +use SimpleSAML\Utils\Config\Metadata; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\JsonResponse; @@ -46,7 +51,7 @@ class Profile protected LoggerInterface $logger; protected string $defaultAuthenticationSource; protected Simple $authSimple; - protected AuthenticationDataProviderBuilder $authenticationDataProviderBuilder; + protected DataProviderBuilder $dataProviderBuilder; protected HelpersManager $helpersManager; protected SspModuleManager $sspModuleManager; protected User $user; @@ -60,7 +65,7 @@ class Profile * @param Session $session The current user session. * @param LoggerInterface $logger * @param Simple|null $authSimple - * @param AuthenticationDataProviderBuilder|null $authenticationDataProviderBuilder + * @param DataProviderBuilder|null $authenticationDataProviderBuilder * @param HelpersManager|null $helpersManager * @param SspModuleManager|null $sspModuleManager * @param CsrfToken|null $csrfToken @@ -72,7 +77,7 @@ class Profile Session $session, LoggerInterface $logger, Simple $authSimple = null, - AuthenticationDataProviderBuilder $authenticationDataProviderBuilder = null, + DataProviderBuilder $authenticationDataProviderBuilder = null, HelpersManager $helpersManager = null, SspModuleManager $sspModuleManager = null, CsrfToken $csrfToken = null, @@ -88,8 +93,8 @@ class Profile $this->helpersManager = $helpersManager ?? new HelpersManager(); - $this->authenticationDataProviderBuilder = $authenticationDataProviderBuilder ?? - new AuthenticationDataProviderBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager); + $this->dataProviderBuilder = $authenticationDataProviderBuilder ?? + new DataProviderBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager); $this->sspModuleManager = $sspModuleManager ?? new SspModuleManager($this->logger, $this->helpersManager); @@ -132,13 +137,21 @@ class Profile * @throws Exception * @throws ConfigurationError */ - public function connectedOrganizations(): Template + public function connectedOrganizations(): Response { $userIdentifier = $this->resolveUserIdentifier(); - $authenticationDataProvider = $this->resolveAuthenticationDataProvider(); + $connectedServiceProviderClass = $this->moduleConfiguration->getConnectedServicesProviderClass(); - $connectedServiceProviderBag = $authenticationDataProvider->getConnectedServiceProviders($userIdentifier); + if (is_null($connectedServiceProviderClass)) { + return new RedirectResponse($this->helpersManager->getRoutes()->getUrl(Routes::PATH_USER_PERSONAL_DATA)); + } + + $connectedServicesDataProvider = $this->dataProviderBuilder->buildConnectedServicesProvider( + $connectedServiceProviderClass + ); + + $connectedServiceProviderBag = $connectedServicesDataProvider->getConnectedServices($userIdentifier); $oidc = $this->sspModuleManager->getOidc(); $accessTokensByClient = []; @@ -149,13 +162,13 @@ class Profile if ($oidc->isEnabled()) { // Filter out OIDC service providers and get their entity (client) IDs. $oidcClientIds = array_map( - function (ConnectedServiceProvider $connectedServiceProvider) { - return $connectedServiceProvider->getServiceProvider()->getEntityId(); + function (ConnectedService $connectedService) { + return $connectedService->getServiceProvider()->getEntityId(); }, array_filter( $connectedServiceProviderBag->getAll(), - function (ConnectedServiceProvider $connectedServiceProvider) { - return $connectedServiceProvider->getServiceProvider()->getProtocol()->getDesignation() === + function (ConnectedService $connectedService) { + return $connectedService->getServiceProvider()->getProtocol()->getDesignation() === Oidc::DESIGNATION; } ) @@ -190,18 +203,24 @@ class Profile * @throws Exception * @throws ConfigurationError */ - public function activity(Request $request): Template + public function activity(Request $request): Response { $userIdentifier = $this->resolveUserIdentifier(); - $authenticationDataProvider = $this->resolveAuthenticationDataProvider(); + $activityProviderClass = $this->moduleConfiguration->getActivityProviderClass(); + + if (is_null($activityProviderClass)) { + return new RedirectResponse($this->helpersManager->getRoutes()->getUrl(Routes::PATH_USER_PERSONAL_DATA)); + } + + $activityDataProvider = $this->dataProviderBuilder->buildActivityProvider($activityProviderClass); $page = ($page = (int)$request->query->get('page', 1)) > 0 ? $page : 1; $maxResults = 10; $firstResult = ($page - 1) * $maxResults; - $activityBag = $authenticationDataProvider->getActivity($userIdentifier, $maxResults, $firstResult); + $activityBag = $activityDataProvider->getActivity($userIdentifier, $maxResults, $firstResult); $template = $this->resolveTemplate('accounting:user/activity.twig'); $template->data += compact('activityBag', 'page', 'maxResults'); @@ -209,98 +228,8 @@ class Profile return $template; } - /** - * @throws Exception|ConfigurationError - */ - public function oidcTokens(): Response - { - $oidc = $this->sspModuleManager->getOidc(); - - // If oidc module is not enabled, this route should not be called. - if (!$oidc->isEnabled()) { - return new RedirectResponse($this->helpersManager->getRoutes()->getUrl(Routes::PATH_USER_PERSONAL_DATA)); - } - - $userIdentifier = $this->resolveUserIdentifier(); - - $accessTokensByClient = $this->helpersManager->getArr()->groupByValue( - $oidc->getUsersAccessTokens($userIdentifier), - 'client_id' - ); - - $refreshTokensByClient = $this->helpersManager->getArr()->groupByValue( - $oidc->getUsersRefreshTokens($userIdentifier), - 'client_id' - ); - - $clientIds = array_unique(array_merge(array_keys($accessTokensByClient), array_keys($refreshTokensByClient))); - $clients = $this->helpersManager->getArr()->groupByValue( - $oidc->getClients($clientIds), - 'id' - ); - - //die(var_dump($accessTokensByClient, $refreshTokensByClient, $clientIds, $clients)); - - $template = $this->resolveTemplate('accounting:user/oidc-tokens.twig'); - $template->data += compact('accessTokensByClient', 'refreshTokensByClient', 'clients'); - - return $template; - } - - public function oidcTokenRevoke(Request $request): Response - { - $oidc = $this->sspModuleManager->getOidc(); - - // If oidc module is not enabled, this route should not be called. - if (! $oidc->isEnabled()) { - return new RedirectResponse($this->helpersManager->getRoutes()->getUrl(Routes::PATH_USER_PERSONAL_DATA)); - } - - $redirectTo = (string) $request->query->get(Routes::QUERY_REDIRECT_TO_PATH, Routes::PATH_USER_OIDC_TOKENS); - - $response = new RedirectResponse( - $this->helpersManager->getRoutes()->getUrl($redirectTo) - ); - - if (! $this->csrfToken->validate($request->request->getAlnum('csrf-token'))) { - $this->alertsBag->put( - new AlertsBag\Alert('Could not verify CSRF token.', 'warning') - ); - - return $response; - } - - $validTokenTypes = ['access', 'refresh']; - - $tokenType = $request->request->getAlnum('token-type'); - - if (! in_array($tokenType, $validTokenTypes)) { - $this->alertsBag->put( - new AlertsBag\Alert('Token type not valid.', 'warning') - ); - return $response; - } - - $tokenId = $request->request->getAlnum('token-id'); - - $userIdentifier = $this->resolveUserIdentifier(); - - if ($tokenType === 'access') { - $oidc->revokeUsersAccessToken($userIdentifier, $tokenId); - } elseif ($tokenType === 'refresh') { - $oidc->revokeUsersRefreshToken($userIdentifier, $tokenId); - } - - $this->alertsBag->put( - new AlertsBag\Alert('Token revoked successfully.', 'success') - ); - - return $response; - } - public function oidcTokenRevokeXhr(Request $request): Response { - $oidc = $this->sspModuleManager->getOidc(); $response = new JsonResponse(); @@ -359,18 +288,6 @@ class Profile return $userIdentifier; } - /** - * @throws Exception - */ - protected function resolveAuthenticationDataProvider(): AuthenticationDataProviderInterface - { - return $this->authenticationDataProviderBuilder - ->build( - $this->moduleConfiguration->getDefaultDataTrackerAndProviderClass(), - ConnectionType::SLAVE - ); - } - public function logout(): Response { return new RunnableResponse([$this->authSimple, 'logout'], [$this->getLogoutUrl()]); @@ -428,25 +345,25 @@ class Profile 'personal-data', Translate::noop('Personal Data'), 'css/src/icons/prof-page.svg' - ), - new MenuManager\MenuItem( - 'connected-organizations', - Translate::noop('Connected Organizations'), - 'css/src/icons/conn-orgs.svg' - ), - new MenuManager\MenuItem( - 'activity', - Translate::noop('Activity'), - 'css/src/icons/activity.svg' ) ); - // Depending on other functionalities, add additional menu items. - if ($this->sspModuleManager->getOidc()->isEnabled()) { + // Depending on enabled functionalities, add additional menu items. + if ($this->moduleConfiguration->getConnectedServicesProviderClass() !== null) { + $menuManager->addItems( + new MenuManager\MenuItem( + 'connected-organizations', + Translate::noop('Connected Organizations'), + 'css/src/icons/conn-orgs.svg' + ) + ); + } + + if ($this->moduleConfiguration->getActivityProviderClass() !== null) { $menuManager->addItems( new MenuManager\MenuItem( - 'oidc-tokens', - Translate::noop('Tokens'), + 'activity', + Translate::noop('Activity'), 'css/src/icons/activity.svg' ) ); diff --git a/src/ModuleConfiguration.php b/src/ModuleConfiguration.php index e4e9e13ccf4a7110db2be03db576442d0c265953..7926be502059222a03bc413f2c0ee60f49654b44 100644 --- a/src/ModuleConfiguration.php +++ b/src/ModuleConfiguration.php @@ -7,12 +7,12 @@ namespace SimpleSAML\Module\accounting; use DateInterval; use Exception; use SimpleSAML\Configuration; +use SimpleSAML\Module\accounting\Data\Providers\Interfaces\DataProviderInterface; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\JobsStoreInterface; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; use SimpleSAML\Module\accounting\ModuleConfiguration\AccountingProcessingType; use SimpleSAML\Module\accounting\ModuleConfiguration\ConnectionType; -use SimpleSAML\Module\accounting\Providers\Interfaces\AuthenticationDataProviderInterface; -use SimpleSAML\Module\accounting\Stores\Interfaces\JobsStoreInterface; -use SimpleSAML\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface; use Throwable; class ModuleConfiguration @@ -20,7 +20,7 @@ class ModuleConfiguration public const MODULE_NAME = 'accounting'; /** - * Default file name for module configuration. Can be overridden, for example, for testing purposes. + * Default file name for module configuration. Can be overridden in constructor, for example, for testing purposes. */ public const FILE_NAME = 'module_accounting.php'; @@ -28,7 +28,6 @@ class ModuleConfiguration public const OPTION_DEFAULT_AUTHENTICATION_SOURCE = 'default_authentication_source'; public const OPTION_ACCOUNTING_PROCESSING_TYPE = 'accounting_processing_type'; public const OPTION_JOBS_STORE = 'jobs_store'; - public const OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER = 'default_data_tracker_and_provider'; public const OPTION_ADDITIONAL_TRACKERS = 'additional_trackers'; public const OPTION_CONNECTIONS_AND_PARAMETERS = 'connections_and_parameters'; public const OPTION_CLASS_TO_CONNECTION_MAP = 'class_to_connection_map'; @@ -38,6 +37,8 @@ class ModuleConfiguration 'job_runner_should_pause_after_number_of_jobs_processed'; public const OPTION_TRACKER_DATA_RETENTION_POLICY = 'tracker_data_retention_policy'; public const OPTION_CRON_TAG_FOR_TRACKER_DATA_RETENTION_POLICY = 'cron_tag_for_tracker_data_retention_policy'; + public const OPTION_PROVIDER_FOR_CONNECTED_SERVICES = 'provider_for_connected_services'; + public const OPTION_PROVIDER_FOR_ACTIVITY = 'provider_for_activity'; /** * Contains configuration from module configuration file. @@ -132,9 +133,25 @@ class ModuleConfiguration return $value; } - public function getDefaultDataTrackerAndProviderClass(): string + public function getConnectedServicesProviderClass(): ?string { - return $this->getConfiguration()->getString(self::OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER); + return $this->getConfiguration()->getOptionalString(self::OPTION_PROVIDER_FOR_CONNECTED_SERVICES, null); + } + + public function getActivityProviderClass(): ?string + { + return $this->getConfiguration()->getOptionalString(self::OPTION_PROVIDER_FOR_ACTIVITY, null); + } + + /** + * @return array<string> + */ + public function getProviderClasses(): array + { + return array_filter([ + $this->getConnectedServicesProviderClass(), + $this->getActivityProviderClass(), + ]); } public function getConnectionsAndParameters(): array @@ -280,7 +297,7 @@ class ModuleConfiguration } try { - $this->validateDefaultDataTrackerAndProvider(); + $this->validateDataProviders(); } catch (Throwable $exception) { $errors[] = $exception->getMessage(); } @@ -313,27 +330,18 @@ class ModuleConfiguration /** * @throws InvalidConfigurationException */ - protected function validateDefaultDataTrackerAndProvider(): void + protected function validateDataProviders(): void { $errors = []; - // Default data tracker and provider must implement proper interfaces. - $defaultDataTrackerAndProviderClass = $this->getDefaultDataTrackerAndProviderClass(); - - if (!is_subclass_of($defaultDataTrackerAndProviderClass, AuthenticationDataTrackerInterface::class)) { - $errors[] = sprintf( - 'Default authentication data tracker and provider class \'%s\' does not implement interface \'%s\'.', - $defaultDataTrackerAndProviderClass, - AuthenticationDataTrackerInterface::class - ); - } - - if (!is_subclass_of($defaultDataTrackerAndProviderClass, AuthenticationDataProviderInterface::class)) { - $errors[] = sprintf( - 'Default authentication data tracker and provider class \'%s\' does not implement interface \'%s\'.', - $defaultDataTrackerAndProviderClass, - AuthenticationDataProviderInterface::class - ); + foreach ($this->getProviderClasses() as $providerClass) { + if (!is_subclass_of($providerClass, DataProviderInterface::class)) { + $errors[] = sprintf( + 'VersionedDataProvider class \'%s\' does not implement interface \'%s\'.', + $providerClass, + DataProviderInterface::class + ); + } } if (!empty($errors)) { @@ -353,11 +361,11 @@ class ModuleConfiguration /** @psalm-suppress DocblockTypeContradiction */ if (!is_string($trackerClass)) { $errors[] = 'Additional trackers array must contain class strings only.'; - } elseif (!is_subclass_of($trackerClass, AuthenticationDataTrackerInterface::class)) { + } elseif (!is_subclass_of($trackerClass, DataTrackerInterface::class)) { $errors[] = sprintf( - 'Tracker class \'%s\' does not implement interface \'%s\'.', + 'VersionedDataTracker class \'%s\' does not implement interface \'%s\'.', $trackerClass, - AuthenticationDataTrackerInterface::class + DataTrackerInterface::class ); } } diff --git a/src/Providers/Builders/AuthenticationDataProviderBuilder.php b/src/Providers/Builders/AuthenticationDataProviderBuilder.php deleted file mode 100644 index 1bc13f34a57ccf32576f32d4405c1c0464faf4a6..0000000000000000000000000000000000000000 --- a/src/Providers/Builders/AuthenticationDataProviderBuilder.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Module\accounting\Providers\Builders; - -use Psr\Log\LoggerInterface; -use SimpleSAML\Module\accounting\Exceptions\Exception; -use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Providers\Interfaces\AuthenticationDataProviderInterface; -use SimpleSAML\Module\accounting\Services\HelpersManager; -use Throwable; - -class AuthenticationDataProviderBuilder -{ - protected ModuleConfiguration $moduleConfiguration; - protected LoggerInterface $logger; - protected HelpersManager $helpersManager; - - public function __construct( - ModuleConfiguration $moduleConfiguration, - LoggerInterface $logger, - HelpersManager $helpersManager - ) { - $this->moduleConfiguration = $moduleConfiguration; - $this->logger = $logger; - $this->helpersManager = $helpersManager; - } - - /** - * @throws Exception - */ - public function build( - string $class, - string $connectionType = ModuleConfiguration\ConnectionType::MASTER - ): AuthenticationDataProviderInterface { - try { - // Make sure that the class implements proper interface - if (!is_subclass_of($class, AuthenticationDataProviderInterface::class)) { - $message = sprintf( - 'Class %s does not implement interface %s.', - $class, - AuthenticationDataProviderInterface::class - ); - throw new UnexpectedValueException($message); - } - - // Build... - /** @var AuthenticationDataProviderInterface $store */ - $store = $this->helpersManager->getInstanceBuilderUsingModuleConfiguration()->build( - $class, - $this->moduleConfiguration, - $this->logger, - [$connectionType] - ); - } catch (Throwable $exception) { - $message = sprintf('Error building instance for class %s. Error was: %s', $class, $exception->getMessage()); - throw new Exception($message, (int)$exception->getCode(), $exception); - } - - return $store; - } -} diff --git a/src/Services/JobRunner.php b/src/Services/JobRunner.php index 12b47e2fbbefeb4be1b848764e0a8941ecd38de8..f73796c97b3d1b0b6b8d345940856e8a4e0e0b04 100644 --- a/src/Services/JobRunner.php +++ b/src/Services/JobRunner.php @@ -11,6 +11,8 @@ use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\InvalidArgumentException; use SimpleSAML\Configuration as SspConfiguration; +use SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; use SimpleSAML\Module\accounting\Entities\Authentication\Event\Job; use SimpleSAML\Module\accounting\Exceptions\Exception; use SimpleSAML\Module\accounting\Exceptions\StoreException; @@ -18,9 +20,6 @@ use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\JobRunner\RateLimiter; use SimpleSAML\Module\accounting\Services\JobRunner\State; -use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; -use SimpleSAML\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface; use Throwable; class JobRunner @@ -28,7 +27,6 @@ class JobRunner protected ModuleConfiguration $moduleConfiguration; protected SspConfiguration $sspConfiguration; protected LoggerInterface $logger; - protected AuthenticationDataTrackerBuilder $authenticationDataTrackerBuilder; protected JobsStoreBuilder $jobsStoreBuilder; protected CacheInterface $cache; protected State $state; @@ -49,6 +47,7 @@ class JobRunner protected DateInterval $stateStaleThresholdInterval; protected RateLimiter $rateLimiter; protected HelpersManager $helpersManager; + protected TrackerResolver $trackerResolver; protected ?DateInterval $maximumExecutionTime; protected ?int $shouldPauseAfterNumberOfJobsProcessed; @@ -61,7 +60,7 @@ class JobRunner SspConfiguration $sspConfiguration, LoggerInterface $logger = null, HelpersManager $helpersManager = null, - AuthenticationDataTrackerBuilder $authenticationDataTrackerBuilder = null, + TrackerResolver $trackerResolver = null, JobsStoreBuilder $jobsStoreBuilder = null, CacheInterface $cache = null, State $state = null, @@ -72,8 +71,8 @@ class JobRunner $this->logger = $logger ?? new Logger(); $this->helpersManager = $helpersManager ?? new HelpersManager(); - $this->authenticationDataTrackerBuilder = $authenticationDataTrackerBuilder ?? - new AuthenticationDataTrackerBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager); + $this->trackerResolver = $trackerResolver ?? + new TrackerResolver($this->moduleConfiguration, $this->logger, $this->helpersManager); $this->jobsStoreBuilder = $jobsStoreBuilder ?? new JobsStoreBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager); @@ -83,7 +82,7 @@ class JobRunner $this->state = $state ?? new State($this->jobRunnerId); - $this->trackers = $this->resolveTrackers(); + $this->trackers = $this->trackerResolver->fromModuleConfiguration(); $this->stateStaleThresholdInterval = new DateInterval(self::STATE_STALE_THRESHOLD_INTERVAL); $this->rateLimiter = $rateLimiter ?? new RateLimiter(); @@ -161,7 +160,7 @@ class JobRunner $this->rateLimiter->resetBackoffPause(); } - /** @var AuthenticationDataTrackerInterface $tracker */ + /** @var DataTrackerInterface $tracker */ foreach ($this->trackers as $tracker) { /** @var Job $job */ $tracker->process($job->getPayload()); @@ -313,7 +312,7 @@ class JobRunner } if ($cachedState->getJobRunnerId() !== $this->jobRunnerId) { - $message = 'Current job runner ID differs from the ID in the cached state.'; + $message = 'CurrentDataProvider job runner ID differs from the ID in the cached state.'; throw new Exception($message); } @@ -489,25 +488,6 @@ class JobRunner } } - /** - * @throws Exception - */ - protected function resolveTrackers(): array - { - $trackers = []; - - $configuredTrackerClasses = array_merge( - [$this->moduleConfiguration->getDefaultDataTrackerAndProviderClass()], - $this->moduleConfiguration->getAdditionalTrackers() - ); - - foreach ($configuredTrackerClasses as $trackerClass) { - $trackers[$trackerClass] = $this->authenticationDataTrackerBuilder->build($trackerClass); - } - - return $trackers; - } - protected function isCli(): bool { return $this->helpersManager->getEnvironment()->isCli(); diff --git a/src/Services/TrackerResolver.php b/src/Services/TrackerResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..575cbf63cd266b1459344cc8219f062ffab01ab7 --- /dev/null +++ b/src/Services/TrackerResolver.php @@ -0,0 +1,66 @@ +<?php + +namespace SimpleSAML\Module\accounting\Services; + +use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder; +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; + +class TrackerResolver +{ + protected ModuleConfiguration $moduleConfiguration; + protected LoggerInterface $logger; + protected HelpersManager $helpersManager; + protected DataProviderBuilder $dataProviderBuilder; + protected DataTrackerBuilder $dataTrackerBuilder; + + public function __construct( + ModuleConfiguration $moduleConfiguration, + LoggerInterface $logger, + HelpersManager $helpersManager, + DataProviderBuilder $dataProviderBuilder = null, + DataTrackerBuilder $dataTrackerBuilder = null + ) { + $this->moduleConfiguration = $moduleConfiguration; + $this->logger = $logger; + $this->helpersManager = $helpersManager; + $this->dataProviderBuilder = $dataProviderBuilder ?? new DataProviderBuilder( + $this->moduleConfiguration, + $this->logger, + $this->helpersManager + ); + $this->dataTrackerBuilder = $dataTrackerBuilder ?? new DataTrackerBuilder( + $this->moduleConfiguration, + $this->logger, + $this->helpersManager + ); + } + + /** + * @return array<string,DataTrackerInterface> + * @throws Exception + */ + public function fromModuleConfiguration(): array + { + $trackers = []; + + foreach ($this->moduleConfiguration->getProviderClasses() as $providerClass) { + if ( + ($provider = $this->dataProviderBuilder->build($providerClass)) && + ($providersTracker = $provider->getTracker()) !== null + ) { + $trackers[get_class($providersTracker)] = $providersTracker; + } + } + + // Process specifically configured trackers. + foreach ($this->moduleConfiguration->getAdditionalTrackers() as $trackerClass) { + $trackers[$trackerClass] = $this->dataTrackerBuilder->build($trackerClass); + } + + return $trackers; + } +} diff --git a/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php b/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php deleted file mode 100644 index 9aa852dc9bdd08fb3813d87c539745090e4936a7..0000000000000000000000000000000000000000 --- a/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTable.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations; - -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; - -class Version20220601000100CreateJobFailedTable extends Store\Migrations\Bases\AbstractCreateJobsTable -{ - protected function getJobsTableName(): string - { - return 'job_failed'; - } -} diff --git a/src/Trackers/Authentication/DoctrineDbal/Versioned/Tracker.php b/src/Trackers/Authentication/DoctrineDbal/Versioned/Tracker.php deleted file mode 100644 index 118ae20614d1fefbe18f012ad2e3731339bef0fb..0000000000000000000000000000000000000000 --- a/src/Trackers/Authentication/DoctrineDbal/Versioned/Tracker.php +++ /dev/null @@ -1,104 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned; - -use DateInterval; -use DateTimeImmutable; -use Psr\Log\LoggerInterface; -use SimpleSAML\Module\accounting\Entities\Activity; -use SimpleSAML\Module\accounting\Entities\Authentication\Event; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; -use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; -use SimpleSAML\Module\accounting\Exceptions\StoreException; -use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Providers\Interfaces\AuthenticationDataProviderInterface; -use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Builders\DataStoreBuilder; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; -use SimpleSAML\Module\accounting\Stores\Interfaces\DataStoreInterface; -use SimpleSAML\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface; - -class Tracker implements AuthenticationDataTrackerInterface, AuthenticationDataProviderInterface -{ - protected ModuleConfiguration $moduleConfiguration; - protected LoggerInterface $logger; - protected DataStoreInterface $dataStore; - protected HelpersManager $helpersManager; - - /** - * @throws StoreException - */ - public function __construct( - ModuleConfiguration $moduleConfiguration, - LoggerInterface $logger, - string $connectionType = ModuleConfiguration\ConnectionType::MASTER, - HelpersManager $helpersManager = null, - DataStoreInterface $dataStore = null - ) { - $this->moduleConfiguration = $moduleConfiguration; - $this->logger = $logger; - - $this->helpersManager = $helpersManager ?? new HelpersManager(); - - // Use provided store or initialize default store for this tracker. - $this->dataStore = $dataStore ?? - (new DataStoreBuilder($this->moduleConfiguration, $this->logger, $this->helpersManager)) - ->build( - Store::class, - $this->moduleConfiguration->getClassConnectionKey(self::class), - $connectionType - ); - } - - /** - * @throws StoreException - */ - public static function build( - ModuleConfiguration $moduleConfiguration, - LoggerInterface $logger, - string $connectionType = ModuleConfiguration\ConnectionType::MASTER - ): self { - return new self($moduleConfiguration, $logger, $connectionType); - } - - public function process(Event $authenticationEvent): void - { - $this->dataStore->persist($authenticationEvent); - } - - public function needsSetup(): bool - { - return $this->dataStore->needsSetup(); - } - - public function runSetup(): void - { - if (! $this->needsSetup()) { - $this->logger->warning('Run setup called, however setup is not needed.'); - return; - } - - $this->dataStore->runSetup(); - } - - public function getConnectedServiceProviders(string $userIdentifier): ConnectedServiceProvider\Bag - { - $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier); - return $this->dataStore->getConnectedOrganizations($userIdentifierHashSha256); - } - - public function getActivity(string $userIdentifier, int $maxResults, int $firstResult): Activity\Bag - { - $userIdentifierHashSha256 = $this->helpersManager->getHash()->getSha256($userIdentifier); - return $this->dataStore->getActivity($userIdentifierHashSha256, $maxResults, $firstResult); - } - - public function enforceDataRetentionPolicy(DateInterval $retentionPolicy): void - { - $dateTime = (new DateTimeImmutable())->sub($retentionPolicy); - - $this->dataStore->deleteDataOlderThan($dateTime); - } -} diff --git a/templates/admin/configuration/status.twig b/templates/admin/configuration/status.twig index 9927f73ac568730751181d03c3fd9075b326c5b5..d8b00d9e62461235ce75cded91061ce922783201 100644 --- a/templates/admin/configuration/status.twig +++ b/templates/admin/configuration/status.twig @@ -10,24 +10,40 @@ <h2>{{ pagetitle }} </h2> {% if configurationValidationErrors is not null %} + + <p>There wre some errors. Check your configuration and try again.</p> + <p>{{ configurationValidationErrors }}</p> + + <br> + + {% if runSetup %} + <a class="pure-button pure-button-warning" href="?runSetup=1">{{ 'Try again'|trans }}</a> + {% endif %} {% elseif moduleConfiguration is not null %} <ul> <li> <strong>{{ 'User ID Attribute Name'|trans }}</strong>: {{ moduleConfiguration.getUserIdAttributeName }} </li> + <li> <strong>{{ 'Accounting Processing Type'|trans }}</strong>: {{ moduleConfiguration.getAccountingProcessingType }} </li> - <li> - <strong>{{ 'Default Data Tracker and Provider Class'|trans }}</strong>: - {{ moduleConfiguration.getDefaultDataTrackerAndProviderClass }} - </li> - <li> - <strong>{{ 'Tracker and Provider Setup Needed'|trans }}</strong>: - {{ defaultDataTrackerAndProvider.needsSetup ? 'Yes'|trans : 'No'|trans }} - </li> + + {% if providers is not empty %} + <li> + <strong>{{ 'Providers setup'|trans }}</strong>: + <ul> + {% for providerClass, providerInstance in providers %} + <li> + {{ providerClass }}: {{ providerInstance.needsSetup ? 'Yes'|trans : 'No'|trans }} + </li> + {% endfor %} + </ul> + </li> + {% endif %} + {% if additionalTrackers is not empty %} <li> <strong>{{ 'Additional Trackers and setup'|trans }}</strong>: @@ -57,21 +73,20 @@ </li> </ul> {% endif %} - {% else %} - <p>{{ 'Could not initialize module configuration.'|trans }}</p> - {% endif %} - <br> - - {% if setupNeeded %} - <p>{{ 'Run setup before using the module.'|trans }}</p> - <a class="pure-button pure-button-warning" href="?runSetup=1">{{ 'Run Setup'|trans }}</a> + {% if setupNeeded %} + <p>{{ 'Run setup before using the module.'|trans }}</p> + <a class="pure-button pure-button-warning" href="?runSetup=1">{{ 'Run Setup'|trans }}</a> + {% else %} + <p> + {{ 'Everything seems good to go.'|trans }} + <br> + <br> + {{ ' Profile page URL is'|trans }}: <a href="{{ profilePageUri }}">{{ profilePageUri }}</a> + </p> + {% endif %} {% else %} - <p> - {{ 'Everything seems good to go.'|trans }} - <br> - <br> - {{ ' Profile page URL is'|trans }}: <a href="{{ profilePageUri }}">{{ profilePageUri }}</a> - </p> + <p>{{ 'Could not initialize module configuration. Check your settings and try again.'|trans }}</p> {% endif %} + {% endblock %} diff --git a/templates/user/activity.twig b/templates/user/activity.twig index 7dae7af5b0e33b242cab8e4c9b36b0d0b7cf43dd..e3f3f82fa78964f3072aae66f01e742bf9deeb93 100644 --- a/templates/user/activity.twig +++ b/templates/user/activity.twig @@ -1,5 +1,5 @@ {# @var activityBag \SimpleSAML\Module\accounting\Entities\Activity\Bag #} -{# @var connectedServiceProvider \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider #} +{# @var connectedServiceProvider \SimpleSAML\Module\accounting\Entities\ConnectedService #} {% extends "@accounting/base.twig" %} diff --git a/templates/user/connected-organizations.twig b/templates/user/connected-organizations.twig index 00d545462f8c11162b205dbe851fbacc26288342..6bbbf8a4c2966de007cb4c13e1372bd453adf052 100644 --- a/templates/user/connected-organizations.twig +++ b/templates/user/connected-organizations.twig @@ -1,5 +1,5 @@ -{# @var connectedServiceProviderBag \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider\Bag #} -{# @var connectedServiceProvider \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider #} +{# @var connectedServiceProviderBag \SimpleSAML\Module\accounting\Entities\ConnectedService\Bag #} +{# @var connectedServiceProvider \SimpleSAML\Module\accounting\Entities\ConnectedService #} {# @var accessTokensByClient array #} {# @var refreshTokensByClient array #} diff --git a/templates/user/oidc-tokens.twig b/templates/user/oidc-tokens.twig deleted file mode 100644 index 0f9c6e95125f3ae97478a4e971929bf977e54495..0000000000000000000000000000000000000000 --- a/templates/user/oidc-tokens.twig +++ /dev/null @@ -1,78 +0,0 @@ -{# @var accessTokensByClient array #} -{# @var refreshTokensByClient array #} -{# @var clients array #} - -{% extends "@accounting/base.twig" %} - -{% set pagetitle = 'OIDC Tokens'|trans %} - -{% set pageMenuItem = 'oidc-tokens' %} - -{% block content %} - <section id="banner"> - <div> - {% trans %}Overview of active OIDC Access / Refresh Tokens by OIDC client.{% endtrans %} - </div> - </section> - - <table> - {% for clientId, clientInfo in clients %} - <tr> - <td colspan="2"> - <strong>{{ clientInfo.0.name }}</strong> - <br> - {{ clientInfo.0.description }} - </td> - </tr> - - {% if accessTokensByClient is not empty and attribute(accessTokensByClient, clientId) %} - <tr> - <td colspan="2">{{ 'Access Tokens'|trans }}</td> - </tr> - {% for accessToken in attribute(accessTokensByClient, clientId) %} - <tr> - <td> - {{ accessToken.id }}<br> - {{ 'Expires at'|trans }} {{ accessToken.expires_at|date }} - </td> - <td> - <form method="post" action="oidc-tokens/revoke"> - <input type="hidden" name="csrf-token" value="{{ csrfToken.get }}"> - <input type="hidden" name="token-type" value="access"> - <input type="hidden" name="token-id" value="{{ accessToken.id }}"> - <input type="submit" value="Revoke"> - </form> - </td> - </tr> - {% endfor %} - {% endif %} - - {% if refreshTokensByClient is not empty and attribute(refreshTokensByClient, clientId) %} - <tr> - <td colspan="2">{{ 'Refresh Tokens'|trans }}</td> - </tr> - {% for refreshToken in attribute(refreshTokensByClient, clientId) %} - <tr> - <td> - {{ refreshToken.id }}<br> - {{ 'Expires at'|trans }} {{ refreshToken.expires_at|date }} - </td> - <td> - <form method="post" action="oidc-tokens/revoke"> - <input type="hidden" name="csrf-token" value="{{ csrfToken.get }}"> - <input type="hidden" name="token-type" value="refresh"> - <input type="hidden" name="token-id" value="{{ refreshToken.id }}"> - <input type="submit" value="Revoke"> - </form> - </td> - </tr> - {% endfor %} - {% endif %} - {% else %} - <tr> - <td colspan="3">{{ 'No data available'|trans }}</td> - </tr> - {% endfor %} - <!-- end of repeating item --> - </table> -{% endblock %} diff --git a/tests/config-templates/config.php b/tests/config-templates/config.php index 7cc113e0db7498187b6805b65039bc5a6f394701..d9f238c718dbf009c84c74c425337f8931fbd223 100644 --- a/tests/config-templates/config.php +++ b/tests/config-templates/config.php @@ -906,7 +906,7 @@ $config = [ *************************************/ /* - * Tracker processing filters that will be executed for all IdPs + * VersionedDataTracker processing filters that will be executed for all IdPs */ 'authproc.idp' => [ /* Enable the authproc filter below to add URN prefixes to all attributes @@ -961,7 +961,7 @@ $config = [ ], /* - * Tracker processing filters that will be executed for all SPs + * VersionedDataTracker processing filters that will be executed for all SPs */ 'authproc.sp' => [ /* diff --git a/tests/config-templates/module_accounting.php b/tests/config-templates/module_accounting.php index 0ecea7b2269eeaff86028ce3936e23890e3aa7c9..014f2e51d17f002891a5f9312833b4fb0e3198fa 100644 --- a/tests/config-templates/module_accounting.php +++ b/tests/config-templates/module_accounting.php @@ -2,10 +2,10 @@ declare(strict_types=1); +use SimpleSAML\Module\accounting\Data\Providers; +use SimpleSAML\Module\accounting\Data\Stores; +use SimpleSAML\Module\accounting\Data\Trackers; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Providers; -use SimpleSAML\Module\accounting\Stores; -use SimpleSAML\Module\accounting\Trackers; $config = [ @@ -18,8 +18,11 @@ $config = [ ModuleConfiguration::OPTION_JOBS_STORE => Stores\Jobs\DoctrineDbal\Store::class, - ModuleConfiguration::OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER => - Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class, + ModuleConfiguration::OPTION_PROVIDER_FOR_ACTIVITY => + Providers\Activity\DoctrineDbal\VersionedDataProvider::class, + + ModuleConfiguration::OPTION_PROVIDER_FOR_CONNECTED_SERVICES => + Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider::class, ModuleConfiguration::OPTION_ADDITIONAL_TRACKERS => [ // @@ -27,7 +30,13 @@ $config = [ ModuleConfiguration::OPTION_CLASS_TO_CONNECTION_MAP => [ Stores\Jobs\DoctrineDbal\Store::class => 'doctrine_dbal_pdo_sqlite', - Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class => [ + Providers\Activity\DoctrineDbal\VersionedDataProvider::class => [ + ModuleConfiguration\ConnectionType::MASTER => 'doctrine_dbal_pdo_sqlite', + ModuleConfiguration\ConnectionType::SLAVE => [ + 'doctrine_dbal_pdo_sqlite_slave', + ], + ], + Providers\ConnectedServices\DoctrineDbal\VersionedDataProvider::class => [ ModuleConfiguration\ConnectionType::MASTER => 'doctrine_dbal_pdo_sqlite', ModuleConfiguration\ConnectionType::SLAVE => [ 'doctrine_dbal_pdo_sqlite_slave', diff --git a/tests/src/Auth/Process/AccountingTest.php b/tests/src/Auth/Process/AccountingTest.php index 0bcf38579beae7b6baed3a6ff29aac340e9509f0..2db27bae1e592e092825ea423332acbb36bd3536 100644 --- a/tests/src/Auth/Process/AccountingTest.php +++ b/tests/src/Auth/Process/AccountingTest.php @@ -6,17 +6,18 @@ namespace SimpleSAML\Test\Module\accounting\Auth\Process; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use SimpleSAML\Module\accounting\Auth\Process\Accounting; -use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider; +use SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker; use SimpleSAML\Module\accounting\Entities\Authentication\Event; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; -use SimpleSAML\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned\Tracker; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; +use SimpleSAML\Module\accounting\Services\TrackerResolver; use SimpleSAML\Test\Module\accounting\Constants\StateArrays; /** @@ -25,14 +26,16 @@ use SimpleSAML\Test\Module\accounting\Constants\StateArrays; * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractState * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\State\Saml2 * @uses \SimpleSAML\Module\accounting\Helpers\AuthenticationEventStateResolver - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Builders\Bases\AbstractStoreBuilder - * @uses \SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\Job * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractJob * @uses \SimpleSAML\Module\accounting\Helpers\Network * @uses \SimpleSAML\Module\accounting\Services\HelpersManager + * @uses \SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder + * @uses \SimpleSAML\Module\accounting\Services\TrackerResolver */ class AccountingTest extends TestCase { @@ -40,11 +43,14 @@ class AccountingTest extends TestCase protected MockObject $loggerMock; protected array $filterConfig; protected MockObject $jobsStoreBuilderMock; - protected MockObject $authenticationDataTrackerBuilderMock; protected MockObject $jobsStoreMock; protected MockObject $trackerMock; protected array $sampleState; protected HelpersManager $helpersManager; + /** + * @var MockObject + */ + protected $trackerResolver; protected function setUp(): void { @@ -53,17 +59,16 @@ class AccountingTest extends TestCase $this->loggerMock = $this->createMock(LoggerInterface::class); $this->jobsStoreBuilderMock = $this->createMock(JobsStoreBuilder::class); - $this->authenticationDataTrackerBuilderMock = - $this->createMock(AuthenticationDataTrackerBuilder::class); $this->jobsStoreMock = $this->createMock(Store::class); - $this->trackerMock = $this->createMock(Tracker::class); + $this->trackerMock = $this->createMock(VersionedDataTracker::class); $this->sampleState = StateArrays::SAML2_FULL; $this->filterConfig = []; $this->helpersManager = new HelpersManager(); + $this->trackerResolver = $this->createMock(TrackerResolver::class); } public function testCanCreateInstance(): void @@ -87,7 +92,7 @@ class AccountingTest extends TestCase $this->loggerMock, $this->helpersManager, $this->jobsStoreBuilderMock, - $this->authenticationDataTrackerBuilderMock + $this->trackerResolver ) ); } @@ -108,9 +113,9 @@ class AccountingTest extends TestCase ->with($this->equalTo(Store::class)) ->willReturn($this->jobsStoreMock); - $this->authenticationDataTrackerBuilderMock + $this->trackerResolver ->expects($this->never()) - ->method('build'); + ->method('fromModuleConfiguration'); (new Accounting( $this->filterConfig, @@ -119,7 +124,7 @@ class AccountingTest extends TestCase $this->loggerMock, $this->helpersManager, $this->jobsStoreBuilderMock, - $this->authenticationDataTrackerBuilderMock + $this->trackerResolver ))->process($this->sampleState); } @@ -128,8 +133,8 @@ class AccountingTest extends TestCase $this->moduleConfigurationStub->method('getAccountingProcessingType') ->willReturn(ModuleConfiguration\AccountingProcessingType::VALUE_SYNCHRONOUS); - $this->moduleConfigurationStub->method('getDefaultDataTrackerAndProviderClass') - ->willReturn(Tracker::class); + $this->moduleConfigurationStub->method('getProviderClasses') + ->willReturn([VersionedDataProvider::class]); $this->moduleConfigurationStub->method('getAdditionalTrackers')->willReturn([]); $this->jobsStoreBuilderMock->expects($this->never()) @@ -140,11 +145,10 @@ class AccountingTest extends TestCase ->method('process') ->with($this->isInstanceOf(Event::class)); - $this->authenticationDataTrackerBuilderMock + $this->trackerResolver ->expects($this->once()) - ->method('build') - ->with($this->equalTo(Tracker::class)) - ->willReturn($this->trackerMock); + ->method('fromModuleConfiguration') + ->willReturn([$this->trackerMock]); (new Accounting( $this->filterConfig, @@ -153,7 +157,7 @@ class AccountingTest extends TestCase $this->loggerMock, $this->helpersManager, $this->jobsStoreBuilderMock, - $this->authenticationDataTrackerBuilderMock + $this->trackerResolver ))->process($this->sampleState); } @@ -171,7 +175,7 @@ class AccountingTest extends TestCase $this->loggerMock, $this->helpersManager, $this->jobsStoreBuilderMock, - $this->authenticationDataTrackerBuilderMock + $this->trackerResolver ))->process($this->sampleState); } } diff --git a/tests/src/Constants/RawRowResult.php b/tests/src/Constants/RawRowResult.php index 2d9f04481c4d1c53278e40b06d82901ee9127321..faa0e7ee6d652fd07c8fe8d7038f65a0c7f5b93b 100644 --- a/tests/src/Constants/RawRowResult.php +++ b/tests/src/Constants/RawRowResult.php @@ -5,24 +5,25 @@ declare(strict_types=1); namespace SimpleSAML\Test\Module\accounting\Constants; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; +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; class RawRowResult { - public const CONNECTED_ORGANIZATION = [ - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS => 1, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT => '2022-02-22 22:22:22', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT => '2022-02-02 22:22:22', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA => 'a:9:{s:19:"SingleLogoutService";a:1:{i:0;a:2:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";s:8:"Location";s:121:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/singleLogoutService/default-sp";}}s:24:"AssertionConsumerService";a:2:{i:0;a:3:{s:7:"Binding";s:46:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:0;}i:1;a:3:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:1;}}s:8:"contacts";a:1:{i:0;a:3:{s:12:"emailAddress";s:15:"mivanci@srce.hr";s:9:"givenName";s:15:"Marko Ivančić";s:11:"contactType";s:9:"technical";}}s:4:"name";a:1:{s:2:"en";s:12:"Test service";}s:11:"description";a:1:{s:2:"en";s:27:"Description of test service";}s:16:"OrganizationName";a:1:{s:2:"en";s:17:"Test organization";}s:8:"entityid";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:14:"metadata-index";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:12:"metadata-set";s:16:"saml20-sp-remote";}', - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES => 'a:5:{s:33:"urn:oid:0.9.2342.19200300.100.1.1";a:1:{i:0;s:11:"student-uid";}s:32:"urn:oid:1.3.6.1.4.1.5923.1.1.1.1";a:2:{i:0;s:6:"member";i:1;s:7:"student";}s:15:"urn:oid:2.5.4.4";a:1:{i:0;s:10:"student-sn";}s:19:"hrEduPersonUniqueID";a:1:{i:0;s:19:"student@example.org";}s:23:"hrEduPersonPersistentID";a:1:{i:0;s:13:"student123abc";}}', + public const CONNECTED_SERVICE = [ + ConnectedServicesTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS => 1, + ConnectedServicesTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT => '2022-02-22 22:22:22', + ConnectedServicesTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT => '2022-02-02 22:22:22', + ConnectedServicesTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA => 'a:9:{s:19:"SingleLogoutService";a:1:{i:0;a:2:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";s:8:"Location";s:121:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/singleLogoutService/default-sp";}}s:24:"AssertionConsumerService";a:2:{i:0;a:3:{s:7:"Binding";s:46:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:0;}i:1;a:3:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:1;}}s:8:"contacts";a:1:{i:0;a:3:{s:12:"emailAddress";s:15:"mivanci@srce.hr";s:9:"givenName";s:15:"Marko Ivančić";s:11:"contactType";s:9:"technical";}}s:4:"name";a:1:{s:2:"en";s:12:"Test service";}s:11:"description";a:1:{s:2:"en";s:27:"Description of test service";}s:16:"OrganizationName";a:1:{s:2:"en";s:17:"Test organization";}s:8:"entityid";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:14:"metadata-index";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:12:"metadata-set";s:16:"saml20-sp-remote";}', + ConnectedServicesTableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES => 'a:5:{s:33:"urn:oid:0.9.2342.19200300.100.1.1";a:1:{i:0;s:11:"student-uid";}s:32:"urn:oid:1.3.6.1.4.1.5923.1.1.1.1";a:2:{i:0;s:6:"member";i:1;s:7:"student";}s:15:"urn:oid:2.5.4.4";a:1:{i:0;s:10:"student-sn";}s:19:"hrEduPersonUniqueID";a:1:{i:0;s:19:"student@example.org";}s:23:"hrEduPersonPersistentID";a:1:{i:0;s:13:"student123abc";}}', ]; public const ACTIVITY = [ - TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT => '2022-02-22 22:22:22', - TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA => 'a:9:{s:19:"SingleLogoutService";a:1:{i:0;a:2:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";s:8:"Location";s:121:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/singleLogoutService/default-sp";}}s:24:"AssertionConsumerService";a:2:{i:0;a:3:{s:7:"Binding";s:46:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:0;}i:1;a:3:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:1;}}s:8:"contacts";a:1:{i:0;a:3:{s:12:"emailAddress";s:15:"mivanci@srce.hr";s:9:"givenName";s:15:"Marko Ivančić";s:11:"contactType";s:9:"technical";}}s:4:"name";a:1:{s:2:"en";s:12:"Test service";}s:11:"description";a:1:{s:2:"en";s:27:"Description of test service";}s:16:"OrganizationName";a:1:{s:2:"en";s:17:"Test organization";}s:8:"entityid";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:14:"metadata-index";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:12:"metadata-set";s:16:"saml20-sp-remote";}', - TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES => 'a:5:{s:33:"urn:oid:0.9.2342.19200300.100.1.1";a:1:{i:0;s:11:"student-uid";}s:32:"urn:oid:1.3.6.1.4.1.5923.1.1.1.1";a:2:{i:0;s:6:"member";i:1;s:7:"student";}s:15:"urn:oid:2.5.4.4";a:1:{i:0;s:10:"student-sn";}s:19:"hrEduPersonUniqueID";a:1:{i:0;s:19:"student@example.org";}s:23:"hrEduPersonPersistentID";a:1:{i:0;s:13:"student123abc";}}', - TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS => '172.21.0.1', - TableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION => 'SAML2', + ActivityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_HAPPENED_AT => '2022-02-22 22:22:22', + ActivityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_SP_METADATA => 'a:9:{s:19:"SingleLogoutService";a:1:{i:0;a:2:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";s:8:"Location";s:121:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/singleLogoutService/default-sp";}}s:24:"AssertionConsumerService";a:2:{i:0;a:3:{s:7:"Binding";s:46:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:0;}i:1;a:3:{s:7:"Binding";s:50:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact";s:8:"Location";s:126:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/assertionConsumerService/default-sp";s:5:"index";i:1;}}s:8:"contacts";a:1:{i:0;a:3:{s:12:"emailAddress";s:15:"mivanci@srce.hr";s:9:"givenName";s:15:"Marko Ivančić";s:11:"contactType";s:9:"technical";}}s:4:"name";a:1:{s:2:"en";s:12:"Test service";}s:11:"description";a:1:{s:2:"en";s:27:"Description of test service";}s:16:"OrganizationName";a:1:{s:2:"en";s:17:"Test organization";}s:8:"entityid";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:14:"metadata-index";s:114:"https://pc-mivancic.srce.hr:9074/simplesamlphp/simplesamlphp-2-beta-git/module.php/saml/sp/metadata.php/default-sp";s:12:"metadata-set";s:16:"saml20-sp-remote";}', + ActivityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_USER_ATTRIBUTES => 'a:5:{s:33:"urn:oid:0.9.2342.19200300.100.1.1";a:1:{i:0;s:11:"student-uid";}s:32:"urn:oid:1.3.6.1.4.1.5923.1.1.1.1";a:2:{i:0;s:6:"member";i:1;s:7:"student";}s:15:"urn:oid:2.5.4.4";a:1:{i:0;s:10:"student-sn";}s:19:"hrEduPersonUniqueID";a:1:{i:0;s:19:"student@example.org";}s:23:"hrEduPersonPersistentID";a:1:{i:0;s:13:"student123abc";}}', + ActivityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_CLIENT_IP_ADDRESS => '172.21.0.1', + ActivityTableConstants::ENTITY_ACTIVITY_COLUMN_NAME_AUTHENTICATION_PROTOCOL_DESIGNATION => 'SAML2', ]; } diff --git a/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php b/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7b2b5ac7a031a3b6cad82420c15a3b94a3d607d7 --- /dev/null +++ b/tests/src/Data/Providers/Builders/DataProviderBuilderTest.php @@ -0,0 +1,90 @@ +<?php + +declare(strict_types=1); + +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\Builders\DataProviderBuilder; +use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker; +use SimpleSAML\Module\accounting\Exceptions\Exception; +use SimpleSAML\Module\accounting\ModuleConfiguration; +use SimpleSAML\Module\accounting\Services\HelpersManager; +use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Providers\Builders\DataProviderBuilder + * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\DataStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Repository + * @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 + */ +class DataProviderBuilderTest extends TestCase +{ + protected Stub $moduleConfigurationStub; + + protected Stub $loggerStub; + protected HelpersManager $helpersManager; + + protected function setUp(): void + { + $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class); + $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY; + $this->moduleConfigurationStub->method('getConnectionParameters') + ->willReturn($connectionParams); + + $this->loggerStub = $this->createStub(LoggerInterface::class); + $this->helpersManager = new HelpersManager(); + } + + public function testCanCreateInstance(): void + { + $this->assertInstanceOf( + DataProviderBuilder::class, + new DataProviderBuilder( + $this->moduleConfigurationStub, + $this->loggerStub, + $this->helpersManager + ) + ); + } + + /** + * @throws Exception + */ + public function testCanBuildDataProvider(): void + { + $builder = new DataProviderBuilder( + $this->moduleConfigurationStub, + $this->loggerStub, + $this->helpersManager + ); + + $this->assertInstanceOf(VersionedDataTracker::class, $builder->build(VersionedDataTracker::class)); + } + + public function testThrowsForInvalidClass(): void + { + $this->expectException(Exception::class); + + (new DataProviderBuilder( + $this->moduleConfigurationStub, + $this->loggerStub, + $this->helpersManager + ))->build('invalid'); + } +} diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php similarity index 84% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php rename to tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php index d309767c3d512e80aacc21c6c3d2b757be754407..c1a790da8f33eec01139a93657b63b220fd83916 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php +++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/Migrations/Version20220801000700CreateAuthenticationEventTableTest.php @@ -2,22 +2,22 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateAuthenticationEventTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\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 { diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawActivityTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivityTest.php similarity index 89% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawActivityTest.php rename to tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivityTest.php index 31589851249ad274e0bf66eebfdd2ad2aa6b7129..73f332287d4c4a83a5b22bb8fb6f7394a86a4896 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawActivityTest.php +++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RawActivityTest.php @@ -2,21 +2,21 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store; 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\Versioned\Store\TableConstants; use SimpleSAML\Module\accounting\Entities\Authentication\Protocol\Saml2; use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawActivity; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; use SimpleSAML\Test\Module\accounting\Constants\DateTime; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawActivity - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\RawActivity + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity */ class RawActivityTest extends TestCase { @@ -62,7 +62,10 @@ class RawActivityTest extends TestCase { $rawActivity = new RawActivity($this->rawRow, $this->abstractPlatformStub); - $this->assertInstanceOf(RawActivity::class, $rawActivity); + $this->assertInstanceOf( + RawActivity::class, + $rawActivity + ); } public function testCanGetProperties(): void 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 new file mode 100644 index 0000000000000000000000000000000000000000..fbf3b363d4e880503412afeaf56cccfb89aac73a --- /dev/null +++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/Store/RepositoryTest.php @@ -0,0 +1,328 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store; + +use DateInterval; +use DateTimeImmutable; +use Exception; +use PHPUnit\Framework\MockObject\Stub; +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\Repository; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants + as BaseTableConstants; +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; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\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\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000000CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable + * @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\Versioned\Store\Migrations\Version20220801000600CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateAuthenticationEventTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Services\HelpersManager + */ +class RepositoryTest extends TestCase +{ + protected Connection $connection; + protected \Doctrine\DBAL\Connection $dbal; + /** + * @var Stub + */ + protected $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; + /** + * @var Stub + */ + protected $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 . + 'Versioned' . 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, $this->createdAt); + } + + /** + * @throws StoreException + * @throws \Doctrine\DBAL\Exception + */ + public function testCanGetActivity(): 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(); + + $idpSpUserVersionId = + (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; + + $this->repository->insertAuthenticationEvent( + $idpSpUserVersionId, + $this->createdAt, + $this->clientIpAddress, + $this->authenticationProtocolDesignation, + $this->createdAt + ); + + $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0); + $this->assertCount(1, $resultArray); + + $this->repository->insertAuthenticationEvent( + $idpSpUserVersionId, + $this->createdAt, + $this->clientIpAddress, + $this->authenticationProtocolDesignation, + $this->createdAt + ); + $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0); + $this->assertCount(2, $resultArray); + + $this->repository->insertAuthenticationEvent( + $idpSpUserVersionId, + $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, $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(); + + $idpSpUserVersionId = + (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; + + $this->repository->insertAuthenticationEvent( + $idpSpUserVersionId, + $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->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(); + + $idpSpUserVersionId = + (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; + + $this->repository->insertAuthenticationEvent( + $idpSpUserVersionId, + $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/Versioned/StoreTest.php b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/StoreTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d86650ef30a2ef7892dbd7f1d904350f8a359e0e --- /dev/null +++ b/tests/src/Data/Stores/Accounting/Activity/DoctrineDbal/Versioned/StoreTest.php @@ -0,0 +1,416 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned; + +use DateTimeImmutable; +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\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; +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\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; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\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\Versioned\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\Versioned\Store\Migrations\Version20220801000000CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable + * @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\Versioned\Store\Migrations\Version20220801000600CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateAuthenticationEventTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @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\Versioned\Store\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(); + + $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(); + $authenticationEventCountQueryBuilder = $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 + ) + ); + $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)$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)$authenticationEventCountQueryBuilder->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)$authenticationEventCountQueryBuilder->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(2, (int)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne()); + } + + /** + * @throws StoreException + */ + public function testResolveIdpSpUserVersionIdThrowsOnFirstGetIdpSpUserVersionFailure(): 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->persist($this->authenticationEvent); + } + + /** + * @throws StoreException + */ + public function testResolveIdpSpUserVersionIdThrowsOnInsertAndGetIdpSpUserVersionFailure(): 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->persist($this->authenticationEvent); + } + + /** + * @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[TableConstants::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/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cba763ee83600ed71f2ea590c97f92b932f71903 --- /dev/null +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpSpUserVersionTableTest.php @@ -0,0 +1,103 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; + +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Schema\AbstractSchemaManager; +use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; +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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection + */ +class CreateIdpSpUserVersionTableTest 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_idp_sp_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\CreateIdpSpUserVersionTable($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\CreateIdpSpUserVersionTable($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\CreateIdpSpUserVersionTable($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 Migrations\CreateIdpSpUserVersionTable($this->connectionStub); + $this->expectException(MigrationException::class); + $migration->run(); + } +} diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php similarity index 74% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTableTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php index 4c93391dfacade4a0c5b136dc89dc3f8321ba9e2..41792f86f6861354ed66e07c1ac2a551dc0b77f3 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000000CreateIdpTableTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpTableTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000000CreateIdpTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ -class Version20220801000000CreateIdpTableTest extends TestCase +class CreateIdpTableTest extends TestCase { protected Connection $connection; protected AbstractSchemaManager $schemaManager; @@ -50,7 +50,7 @@ class Version20220801000000CreateIdpTableTest extends TestCase public function testCanRunMigration(): void { $this->assertFalse($this->schemaManager->tablesExist($this->tableName)); - $migration = new Migrations\Version20220801000000CreateIdpTable($this->connection); + $migration = new CreateIdpTable($this->connection); $migration->run(); $this->assertTrue($this->schemaManager->tablesExist($this->tableName)); $migration->revert(); @@ -68,7 +68,7 @@ class Version20220801000000CreateIdpTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000000CreateIdpTable($this->connectionStub); + $migration = new CreateIdpTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } @@ -83,7 +83,7 @@ class Version20220801000000CreateIdpTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000000CreateIdpTable($this->connectionStub); + $migration = new CreateIdpTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->revert(); } @@ -96,7 +96,7 @@ class Version20220801000000CreateIdpTableTest extends TestCase $this->connectionStub->method('preparePrefixedTableName') ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table - $migration = new Migrations\Version20220801000000CreateIdpTable($this->connectionStub); + $migration = new CreateIdpTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php similarity index 73% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTableTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php index 60e725dc49bbb79e7dd8f77340bbc19696cfba25..cc8c07955ca5c3cf20baf32023b0d63a33e729ef 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000100CreateIdpVersionTableTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateIdpVersionTableTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection * */ -class Version20220801000100CreateIdpVersionTableTest extends TestCase +class CreateIdpVersionTableTest extends TestCase { protected Connection $connection; protected AbstractSchemaManager $schemaManager; @@ -50,7 +50,7 @@ class Version20220801000100CreateIdpVersionTableTest extends TestCase public function testCanRunMigration(): void { $this->assertFalse($this->schemaManager->tablesExist($this->tableName)); - $migration = new Migrations\Version20220801000100CreateIdpVersionTable($this->connection); + $migration = new Migrations\CreateIdpVersionTable($this->connection); $migration->run(); $this->assertTrue($this->schemaManager->tablesExist($this->tableName)); $migration->revert(); @@ -68,7 +68,7 @@ class Version20220801000100CreateIdpVersionTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000100CreateIdpVersionTable($this->connectionStub); + $migration = new Migrations\CreateIdpVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } @@ -83,7 +83,7 @@ class Version20220801000100CreateIdpVersionTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000100CreateIdpVersionTable($this->connectionStub); + $migration = new Migrations\CreateIdpVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->revert(); } @@ -96,7 +96,7 @@ class Version20220801000100CreateIdpVersionTableTest extends TestCase $this->connectionStub->method('preparePrefixedTableName') ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table - $migration = new Migrations\Version20220801000100CreateIdpVersionTable($this->connectionStub); + $migration = new Migrations\CreateIdpVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php similarity index 74% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTableTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php index 66eb8ec6279fdc22fe237370be2eb4fb6a415197..a388427bcce0c087d1475f9f9e7edf861c9565a5 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000200CreateSpTableTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpTableTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; -use PHPUnit\Framework\TestCase; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ -class Version20220801000200CreateSpTableTest extends TestCase +class CreateSpTableTest extends TestCase { protected Connection $connection; protected AbstractSchemaManager $schemaManager; @@ -50,7 +50,7 @@ class Version20220801000200CreateSpTableTest extends TestCase public function testCanRunMigration(): void { $this->assertFalse($this->schemaManager->tablesExist($this->tableName)); - $migration = new Migrations\Version20220801000200CreateSpTable($this->connection); + $migration = new CreateSpTable($this->connection); $migration->run(); $this->assertTrue($this->schemaManager->tablesExist($this->tableName)); $migration->revert(); @@ -68,7 +68,7 @@ class Version20220801000200CreateSpTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000200CreateSpTable($this->connectionStub); + $migration = new CreateSpTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } @@ -83,7 +83,7 @@ class Version20220801000200CreateSpTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000200CreateSpTable($this->connectionStub); + $migration = new CreateSpTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->revert(); } @@ -96,7 +96,7 @@ class Version20220801000200CreateSpTableTest extends TestCase $this->connectionStub->method('preparePrefixedTableName') ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table - $migration = new Migrations\Version20220801000200CreateSpTable($this->connectionStub); + $migration = new CreateSpTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php similarity index 73% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTableTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php index ebbf6d2302254046be3544457099418f24f465ff..44e4151b76452401805973642318f4247631f528 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000300CreateSpVersionTableTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateSpVersionTableTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ -class Version20220801000300CreateSpVersionTableTest extends TestCase +class CreateSpVersionTableTest extends TestCase { protected Connection $connection; protected AbstractSchemaManager $schemaManager; @@ -50,7 +50,7 @@ class Version20220801000300CreateSpVersionTableTest extends TestCase public function testCanRunMigration(): void { $this->assertFalse($this->schemaManager->tablesExist($this->tableName)); - $migration = new Migrations\Version20220801000300CreateSpVersionTable($this->connection); + $migration = new Migrations\CreateSpVersionTable($this->connection); $migration->run(); $this->assertTrue($this->schemaManager->tablesExist($this->tableName)); $migration->revert(); @@ -68,7 +68,7 @@ class Version20220801000300CreateSpVersionTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000300CreateSpVersionTable($this->connectionStub); + $migration = new Migrations\CreateSpVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } @@ -83,7 +83,7 @@ class Version20220801000300CreateSpVersionTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000300CreateSpVersionTable($this->connectionStub); + $migration = new Migrations\CreateSpVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->revert(); } @@ -96,7 +96,7 @@ class Version20220801000300CreateSpVersionTableTest extends TestCase $this->connectionStub->method('preparePrefixedTableName') ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table - $migration = new Migrations\Version20220801000300CreateSpVersionTable($this->connectionStub); + $migration = new Migrations\CreateSpVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php similarity index 73% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTableTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php index f20463f9fbb95193acfd99f6452b21bedb95229b..786ff8bf0450b059855cd3929d23bbc755e734ae 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000400CreateUserTableTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserTableTest.php @@ -2,24 +2,25 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserTable; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * + * @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 + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ -class Version20220801000400CreateUserTableTest extends TestCase +class CreateUserTableTest extends TestCase { protected Connection $connection; protected AbstractSchemaManager $schemaManager; @@ -50,7 +51,7 @@ class Version20220801000400CreateUserTableTest extends TestCase public function testCanRunMigration(): void { $this->assertFalse($this->schemaManager->tablesExist($this->tableName)); - $migration = new Migrations\Version20220801000400CreateUserTable($this->connection); + $migration = new CreateUserTable($this->connection); $migration->run(); $this->assertTrue($this->schemaManager->tablesExist($this->tableName)); $migration->revert(); @@ -68,7 +69,7 @@ class Version20220801000400CreateUserTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000400CreateUserTable($this->connectionStub); + $migration = new CreateUserTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } @@ -83,7 +84,7 @@ class Version20220801000400CreateUserTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000400CreateUserTable($this->connectionStub); + $migration = new CreateUserTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->revert(); } @@ -96,7 +97,7 @@ class Version20220801000400CreateUserTableTest extends TestCase $this->connectionStub->method('preparePrefixedTableName') ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table - $migration = new Migrations\Version20220801000400CreateUserTable($this->connectionStub); + $migration = new CreateUserTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTableTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php similarity index 73% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTableTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php index 8a6cf644be0d85974b92f6249e29bc8cbe14d80b..a1515049e9d08b38455305ffae22c037609f9b56 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000500CreateUserVersionTableTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/Migrations/CreateUserVersionTableTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000500CreateUserVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ -class Version20220801000500CreateUserVersionTableTest extends TestCase +class CreateUserVersionTableTest extends TestCase { protected Connection $connection; protected AbstractSchemaManager $schemaManager; @@ -50,7 +50,7 @@ class Version20220801000500CreateUserVersionTableTest extends TestCase public function testCanRunMigration(): void { $this->assertFalse($this->schemaManager->tablesExist($this->tableName)); - $migration = new Migrations\Version20220801000500CreateUserVersionTable($this->connection); + $migration = new Migrations\CreateUserVersionTable($this->connection); $migration->run(); $this->assertTrue($this->schemaManager->tablesExist($this->tableName)); $migration->revert(); @@ -68,7 +68,7 @@ class Version20220801000500CreateUserVersionTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000500CreateUserVersionTable($this->connectionStub); + $migration = new Migrations\CreateUserVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } @@ -83,7 +83,7 @@ class Version20220801000500CreateUserVersionTableTest extends TestCase $this->dbalStub->method('createSchemaManager')->willReturn($this->schemaManagerStub); $this->connectionStub->method('dbal')->willReturn($this->dbalStub); - $migration = new Migrations\Version20220801000500CreateUserVersionTable($this->connectionStub); + $migration = new Migrations\CreateUserVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->revert(); } @@ -96,7 +96,7 @@ class Version20220801000500CreateUserVersionTableTest extends TestCase $this->connectionStub->method('preparePrefixedTableName') ->willReturnOnConsecutiveCalls(''); // Invalid (empty) name for table - $migration = new Migrations\Version20220801000500CreateUserVersionTable($this->connectionStub); + $migration = new Migrations\CreateUserVersionTable($this->connectionStub); $this->expectException(MigrationException::class); $migration->run(); } 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 new file mode 100644 index 0000000000000000000000000000000000000000..2007a264ba492429d3c84296034793adfe1f0adb --- /dev/null +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/Store/RepositoryTest.php @@ -0,0 +1,394 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store; + +use DateTimeImmutable; +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; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\TableConstants + as BaseTableConstants; +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; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\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\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Services\HelpersManager + */ +class RepositoryTest extends TestCase +{ + protected Connection $connection; + protected \Doctrine\DBAL\Connection $dbal; + /** + * @var Stub + */ + protected $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; + /** + * @var Stub + */ + protected $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 . + 'Versioned' . 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->createdAt); + + $result = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative(); + + $this->assertSame($this->idpEntityId, $result[BaseTableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID]); + $this->assertSame( + $this->idpEntityIdHash, + $result[BaseTableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256] + ); + $this->assertSame( + $this->createdAt->format($this->dateTimeFormat), + $result[BaseTableConstants::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->createdAt); + $this->repository->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $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); + } + + /** + * @depends testCanInsertAndGetIdp + * @throws StoreException + * @throws StoreException + * @throws \Doctrine\DBAL\Exception + */ + public function testCanInsertAndGetIdpVersion(array $idpResult): array + { + $idpId = (int)$idpResult[BaseTableConstants::TABLE_IDP_COLUMN_NAME_ID]; + + $this->repository->insertIdpVersion($idpId, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt); + + $result = $this->repository->getIdpVersion($idpId, $this->idpMetadataHash)->fetchAssociative(); + + $this->assertSame($this->idpMetadata, $result[BaseTableConstants::TABLE_IDP_VERSION_COLUMN_NAME_METADATA]); + $this->assertSame( + $this->idpMetadataHash, + $result[BaseTableConstants::TABLE_IDP_VERSION_COLUMN_NAME_METADATA_HASH_SHA256] + ); + $this->assertSame( + $this->createdAt->format($this->dateTimeFormat), + $result[BaseTableConstants::TABLE_IDP_VERSION_COLUMN_NAME_CREATED_AT] + ); + + return $result; + } + + public function testInsertIdpVersionThrowsOnNonUniqueIdpMetadataHash(): void + { + $this->expectException(StoreException::class); + // IdP Metadata Hash must be unique. + $this->repository->insertIdpVersion(1, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt); + $this->repository->insertIdpVersion(1, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt); + } + + public function testGetIdpVersionThrowsOnInvalidDbal(): void + { + $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); + $repository = new Repository($this->connectionStub, $this->loggerStub); + $this->expectException(StoreException::class); + + $repository->getIdpVersion(1, $this->idpMetadataHash); + } + + /** + * @throws StoreException + * @throws \Doctrine\DBAL\Exception + */ + public function testCanInsertAndGetSp(): array + { + $this->repository->insertSp($this->spEntityId, $this->spEntityIdHash, $this->createdAt); + + $result = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative(); + + $this->assertSame($this->spEntityId, $result[BaseTableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID]); + $this->assertSame( + $this->spEntityIdHash, + $result[BaseTableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256] + ); + $this->assertSame( + $this->createdAt->format($this->dateTimeFormat), + $result[BaseTableConstants::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->createdAt); + $this->repository->insertSp($this->spEntityId, $this->spEntityIdHash, $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); + } + + /** + * @depends testCanInsertAndGetSp + * @throws StoreException + * @throws StoreException + * @throws \Doctrine\DBAL\Exception + */ + public function testCanInsertAndGetSpVersion(array $spResult): array + { + $spId = (int)$spResult[BaseTableConstants::TABLE_SP_COLUMN_NAME_ID]; + + $this->repository->insertSpVersion($spId, $this->spMetadata, $this->spMetadataHash, $this->createdAt); + + $result = $this->repository->getSpVersion($spId, $this->spMetadataHash)->fetchAssociative(); + + $this->assertSame($this->spMetadata, $result[BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA]); + $this->assertSame( + $this->spMetadataHash, + $result[BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA_HASH_SHA256] + ); + $this->assertSame( + $this->createdAt->format($this->dateTimeFormat), + $result[BaseTableConstants::TABLE_SP_VERSION_COLUMN_NAME_CREATED_AT] + ); + + return $result; + } + + public function testInsertSpVersionThrowsOnNonUniqueMetadataHash(): void + { + $this->expectException(StoreException::class); + // SP metadata hash must be unique. + $this->repository->insertSpVersion(1, $this->spMetadata, $this->spMetadataHash, $this->createdAt); + $this->repository->insertSpVersion(1, $this->spMetadata, $this->spMetadataHash, $this->createdAt); + } + + public function testGetSpVersionThrowsOnInvalidDbal(): void + { + $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); + $repository = new Repository($this->connectionStub, $this->loggerStub); + $this->expectException(StoreException::class); + + $repository->getSpVersion(1, $this->spMetadataHash); + } + + /** + * @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[BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER]); + $this->assertSame( + $this->userIdentifierHash, + $result[BaseTableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256] + ); + $this->assertSame( + $this->createdAt->format($this->dateTimeFormat), + $result[BaseTableConstants::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); + } + + /** + * @depends testCanInsertAndGetUser + * @throws StoreException + * @throws StoreException + * @throws \Doctrine\DBAL\Exception + */ + public function testCanInsertAndGetUserVersion(array $userResult): array + { + $userId = (int)$userResult[BaseTableConstants::TABLE_USER_COLUMN_NAME_ID]; + + $this->repository + ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt); + + $result = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative(); + + $this->assertSame( + $this->userAttributes, + $result[BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES] + ); + $this->assertSame( + $this->userAttributesHash, + $result[BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256] + ); + $this->assertSame( + $this->createdAt->format($this->dateTimeFormat), + $result[BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT] + ); + + return $result; + } + + public function testInsertUserVersionThrowsOnNonUniqueAttributesHash(): void + { + $this->expectException(StoreException::class); + $this->repository + ->insertUserVersion(1, $this->userAttributes, $this->userAttributesHash, $this->createdAt); + $this->repository + ->insertUserVersion(1, $this->userAttributes, $this->userAttributesHash, $this->createdAt); + } + + public function testGetUserVersionThrowsOnInvalidDbal(): void + { + $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); + $repository = new Repository($this->connectionStub, $this->loggerStub); + $this->expectException(StoreException::class); + + $repository->getUserVersion(1, $this->userIdentifierHash); + } +} diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/StoreTest.php b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php similarity index 56% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/StoreTest.php rename to tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php index df3cdf45aae45bf8bca500ed985f00d0f1cedf39..10c9068482d3bea1fb5755285ead9c118cba32fa 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/StoreTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/DoctrineDbal/Versioned/StoreTest.php @@ -2,66 +2,61 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned; -use DateTimeImmutable; 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\TableConstants; +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\Entities\Authentication\Event\State; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; +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; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\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\Bases\DoctrineDbal\Versioned\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\Stores\Connections\Bases\AbstractMigrator - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000000CreateIdpTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000500CreateUserVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000600CreateIdpSpUserVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateAuthenticationEventTable + * @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\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\HashDecoratedState * @uses \SimpleSAML\Module\accounting\Helpers\Hash * @uses \SimpleSAML\Module\accounting\Helpers\Arr * @uses \SimpleSAML\Module\accounting\Helpers\Network - * @uses \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider\Bag * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractProvider - * @uses \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider * @uses \SimpleSAML\Module\accounting\Entities\User - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawConnectedServiceProvider - * @uses \SimpleSAML\Module\accounting\Entities\Activity\Bag - * @uses \SimpleSAML\Module\accounting\Entities\Activity - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawActivity + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @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 @@ -74,7 +69,7 @@ class StoreTest extends TestCase protected Connection $connection; protected State\Saml2 $state; protected Event $authenticationEvent; - protected Store\HashDecoratedState $hashDecoratedState; + protected HashDecoratedState $hashDecoratedState; /** * @var MockObject */ @@ -87,6 +82,10 @@ class StoreTest extends TestCase * @var MockObject */ protected $loggerMock; + /** + * @var MockObject + */ + protected $helpersManagerMock; /** * @throws StoreException @@ -113,10 +112,13 @@ class StoreTest extends TestCase $this->state = new State\Saml2(StateArrays::SAML2_FULL); $this->authenticationEvent = new Event($this->state); - $this->hashDecoratedState = new Store\HashDecoratedState($this->state); - $this->repositoryMock = $this->createMock(Store\Repository::class); + $this->hashDecoratedState = new HashDecoratedState($this->state); + $this->repositoryMock = $this->createMock( + \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository::class + ); $this->resultStub = $this->createStub(Result::class); + $this->helpersManagerMock = $this->createMock(HelpersManager::class); } /** @@ -152,7 +154,7 @@ class StoreTest extends TestCase * @throws \Doctrine\DBAL\Exception * @throws MigrationException */ - public function testCanPersistAuthenticationEvent(): void + public function testCanPersistVersionedData(): void { $store = new Store( $this->moduleConfigurationStub, @@ -169,59 +171,43 @@ class StoreTest extends TestCase $spVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder(); $userCountQueryBuilder = $this->connection->dbal()->createQueryBuilder(); $userVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder(); - $idpSpUserVersionCountQueryBuilder = $this->connection->dbal()->createQueryBuilder(); - $authenticationEventCountQueryBuilder = $this->connection->dbal()->createQueryBuilder(); $idpCountQueryBuilder->select('COUNT(id) as idpCount')->from( //'vds_idp' $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_IDP + TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_IDP ) ); $idpVersionCountQueryBuilder->select('COUNT(id) as idpVersionCount')->from( //'vds_idp_version' $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_IDP_VERSION + TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_IDP_VERSION ) ); $spCountQueryBuilder->select('COUNT(id) as spCount')->from( //'vds_sp' $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_SP + TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_SP ) ); $spVersionCountQueryBuilder->select('COUNT(id) as spVersionCount')->from( //'vds_sp_version' $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_SP_VERSION + TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_SP_VERSION ) ); $userCountQueryBuilder->select('COUNT(id) as userCount')->from( //'vds_user' $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_USER + TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_USER ) ); $userVersionCountQueryBuilder->select('COUNT(id) as userVersionCount')->from( //'vds_user_version' $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_USER_VERSION + TableConstants::TABLE_PREFIX . TableConstants::TABLE_NAME_USER_VERSION ) ); - $idpSpUserVersionCountQueryBuilder->select('COUNT(id) as idpSpUserVersionCount') - ->from( - //'vds_idp_sp_user_version' - $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_IDP_SP_USER_VERSION - ) - ); - $authenticationEventCountQueryBuilder->select('COUNT(id) as authenticationEventCount') - ->from( - //'vds_authentication_event' - $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_PREFIX . Store\TableConstants::TABLE_NAME_AUTHENTICATION_EVENT - ) - ); $this->assertSame(0, (int)$idpCountQueryBuilder->executeQuery()->fetchOne()); $this->assertSame(0, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne()); @@ -229,10 +215,13 @@ class StoreTest extends TestCase $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)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne()); - $store->persist($this->authenticationEvent); + $idpId = $store->resolveIdpId($this->hashDecoratedState); + $store->resolveIdpVersionId($idpId, $this->hashDecoratedState); + $spId = $store->resolveSpId($this->hashDecoratedState); + $store->resolveSpVersionId($spId, $this->hashDecoratedState); + $userId = $store->resolveUserId($this->hashDecoratedState); + $store->resolveUserVersionId($userId, $this->hashDecoratedState); $this->assertSame(1, (int)$idpCountQueryBuilder->executeQuery()->fetchOne()); $this->assertSame(1, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne()); @@ -240,10 +229,13 @@ class StoreTest extends TestCase $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)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne()); - $store->persist($this->authenticationEvent); + $idpId = $store->resolveIdpId($this->hashDecoratedState); + $store->resolveIdpVersionId($idpId, $this->hashDecoratedState); + $spId = $store->resolveSpId($this->hashDecoratedState); + $store->resolveSpVersionId($spId, $this->hashDecoratedState); + $userId = $store->resolveUserId($this->hashDecoratedState); + $store->resolveUserVersionId($userId, $this->hashDecoratedState); $this->assertSame(1, (int)$idpCountQueryBuilder->executeQuery()->fetchOne()); $this->assertSame(1, (int)$idpVersionCountQueryBuilder->executeQuery()->fetchOne()); @@ -251,8 +243,6 @@ class StoreTest extends TestCase $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(2, (int)$authenticationEventCountQueryBuilder->executeQuery()->fetchOne()); } /** @@ -267,12 +257,13 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); - $store->persist($this->authenticationEvent); + $store->resolveIdpId($this->hashDecoratedState); } /** @@ -290,13 +281,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); $this->loggerMock->expects($this->once())->method('warning'); - $store->persist($this->authenticationEvent); + $store->resolveIdpId($this->hashDecoratedState); } /** @@ -311,12 +303,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); - $store->persist($this->authenticationEvent); + $idpId = $store->resolveIdpId($this->hashDecoratedState); + $store->resolveIdpVersionId($idpId, $this->hashDecoratedState); } /** @@ -334,13 +328,15 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); $this->loggerMock->expects($this->once())->method('warning'); - $store->persist($this->authenticationEvent); + $idpId = $store->resolveIdpId($this->hashDecoratedState); + $store->resolveIdpVersionId($idpId, $this->hashDecoratedState); } /** @@ -355,12 +351,13 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); - $store->persist($this->authenticationEvent); + $store->resolveSpId($this->hashDecoratedState); } /** @@ -378,13 +375,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); $this->loggerMock->expects($this->once())->method('warning'); - $store->persist($this->authenticationEvent); + $store->resolveSpId($this->hashDecoratedState); } /** @@ -399,12 +397,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); - $store->persist($this->authenticationEvent); + $spId = $store->resolveSpId($this->hashDecoratedState); + $store->resolveSpVersionId($spId, $this->hashDecoratedState); } /** @@ -422,13 +422,15 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); $this->loggerMock->expects($this->once())->method('warning'); - $store->persist($this->authenticationEvent); + $spId = $store->resolveSpId($this->hashDecoratedState); + $store->resolveSpVersionId($spId, $this->hashDecoratedState); } /** @@ -445,12 +447,13 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(UnexpectedValueException::class); - $store->persist($this->authenticationEvent); + $store->resolveUserId($this->hashDecoratedState); } /** @@ -465,12 +468,13 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); - $store->persist($this->authenticationEvent); + $store->resolveUserId($this->hashDecoratedState); } /** @@ -488,13 +492,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); $this->loggerMock->expects($this->once())->method('warning'); - $store->persist($this->authenticationEvent); + $store->resolveUserId($this->hashDecoratedState); } /** @@ -509,12 +514,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); - $store->persist($this->authenticationEvent); + $userId = $store->resolveUserId($this->hashDecoratedState); + $store->resolveUserVersionId($userId, $this->hashDecoratedState); } /** @@ -532,213 +539,14 @@ class StoreTest extends TestCase null, ModuleConfiguration\ConnectionType::MASTER, $this->factoryStub, + $this->helpersManagerMock, $this->repositoryMock ); $this->expectException(StoreException::class); $this->loggerMock->expects($this->once())->method('warning'); - $store->persist($this->authenticationEvent); - } - - /** - * @throws StoreException - */ - public function testResolveIdpSpUserVersionIdThrowsOnFirstGetIdpSpUserVersionFailure(): void - { - $this->repositoryMock->method('getIdpSpUserVersion')->willThrowException(new Exception('test')); - $store = new Store( - $this->moduleConfigurationStub, - $this->loggerMock, - null, - ModuleConfiguration\ConnectionType::MASTER, - $this->factoryStub, - $this->repositoryMock - ); - - $this->expectException(StoreException::class); - - $store->persist($this->authenticationEvent); - } - - /** - * @throws StoreException - */ - public function testResolveIdpSpUserVersionIdThrowsOnInsertAndGetIdpSpUserVersionFailure(): 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->repositoryMock - ); - - $this->expectException(StoreException::class); - $this->loggerMock->expects($this->once())->method('warning'); - - $store->persist($this->authenticationEvent); - } - - /** - * @throws StoreException - */ - public function testGetConnectedOrganizationsReturnsEmptyBagIfNoResults(): void - { - $this->repositoryMock->method('getConnectedServiceProviders')->willReturn([]); - - $store = new Store( - $this->moduleConfigurationStub, - $this->loggerMock, - null, - ModuleConfiguration\ConnectionType::MASTER, - $this->factoryStub, - $this->repositoryMock - ); - - $connectedServiceProviderBag = $store->getConnectedOrganizations('test'); - - $this->assertEmpty($connectedServiceProviderBag->getAll()); - } - - /** - * @throws StoreException - */ - public function testCanGetConnectedOrganizationsBag(): void - { - $this->repositoryMock->method('getConnectedServiceProviders') - ->willReturn([RawRowResult::CONNECTED_ORGANIZATION]); - - $store = new Store( - $this->moduleConfigurationStub, - $this->loggerMock, - null, - ModuleConfiguration\ConnectionType::MASTER, - $this->factoryStub, - $this->repositoryMock - ); - - $connectedServiceProviderBag = $store->getConnectedOrganizations('test'); - - $this->assertNotEmpty($connectedServiceProviderBag->getAll()); - } - - /** - * @throws StoreException - */ - public function testGetConnectedOrganizationsThrowsForInvalidResult(): void - { - $rawResult = RawRowResult::CONNECTED_ORGANIZATION; - unset($rawResult[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS]); - - $this->repositoryMock->method('getConnectedServiceProviders') - ->willReturn([$rawResult]); - - $store = new Store( - $this->moduleConfigurationStub, - $this->loggerMock, - null, - ModuleConfiguration\ConnectionType::MASTER, - $this->factoryStub, - $this->repositoryMock - ); - - $this->expectException(StoreException::class); - $store->getConnectedOrganizations('test'); - } - - /** - * @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->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->repositoryMock - ); - - $activityBag = $store->getActivity('test', 10, 0); - - $this->assertNotEmpty($activityBag->getAll()); - } - - /** - * @throws StoreException - */ - public function testGetActivityThrowsForInvalidResult(): void - { - $rawResult = RawRowResult::ACTIVITY; - unset($rawResult[TableConstants::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->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->repositoryMock - ); - - $store->deleteDataOlderThan($dateTime); + $userId = $store->resolveUserId($this->hashDecoratedState); + $store->resolveUserVersionId($userId, $this->hashDecoratedState); } } diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/HashDecoratedStateTest.php b/tests/src/Data/Stores/Accounting/Bases/HashDecoratedStateTest.php similarity index 88% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/HashDecoratedStateTest.php rename to tests/src/Data/Stores/Accounting/Bases/HashDecoratedStateTest.php index c08603b1032a228a7f7385e246f93a1eb4b23a84..0585513007738502c9c322c4ddd935449603f479 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/HashDecoratedStateTest.php +++ b/tests/src/Data/Stores/Accounting/Bases/HashDecoratedStateTest.php @@ -2,15 +2,15 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\Bases; use PHPUnit\Framework\MockObject\Stub; -use SimpleSAML\Module\accounting\Entities\Authentication\Event\State; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\HashDecoratedState; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState; +use SimpleSAML\Module\accounting\Entities\Authentication\Event\State; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\HashDecoratedState + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState * @uses \SimpleSAML\Module\accounting\Helpers\Hash * @uses \SimpleSAML\Module\accounting\Helpers\Arr * @uses \SimpleSAML\Module\accounting\Services\HelpersManager diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawConnectedServiceProviderTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php similarity index 60% rename from tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawConnectedServiceProviderTest.php rename to tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php index 63c45188871894ca84e486120da029f8fd26657f..a9dc48e2fd26ebd50f97919fb8ac6dfb62376984 100644 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RawConnectedServiceProviderTest.php +++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/RawConnectedServiceTest.php @@ -2,22 +2,22 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use PHPUnit\Framework\MockObject\Stub; -use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawConnectedServiceProvider; use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\TableConstants; +use SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService; +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; /** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\RawConnectedServiceProvider - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity */ -class RawConnectedServiceProviderTest extends TestCase +class RawConnectedServiceTest extends TestCase { protected int $numberOfAuthentications; protected string $lastAuthenticationAt; @@ -45,15 +45,15 @@ class RawConnectedServiceProviderTest extends TestCase $this->serviceProviderMetadata = ['sp' => 'metadata']; $this->userAttributes = ['user' => 'attribute']; $this->rawRow = [ - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS => + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS => $this->numberOfAuthentications, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT => + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT => $this->lastAuthenticationAt, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT => + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT => $this->firstAuthenticationAt, - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA => + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA => serialize($this->serviceProviderMetadata), - TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES => + TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES => serialize($this->userAttributes), ]; $this->dateTimeFormat = DateTime::DEFAULT_FORMAT; @@ -65,14 +65,17 @@ class RawConnectedServiceProviderTest extends TestCase public function testCanCreateInstance(): void { $this->assertInstanceOf( - RawConnectedServiceProvider::class, - new RawConnectedServiceProvider($this->rawRow, $this->abstractPlatformStub) + RawConnectedService::class, + new RawConnectedService($this->rawRow, $this->abstractPlatformStub) ); } public function testCanGetProperties(): void { - $rawConnectedServiceProvider = new RawConnectedServiceProvider($this->rawRow, $this->abstractPlatformStub); + $rawConnectedServiceProvider = new RawConnectedService( + $this->rawRow, + $this->abstractPlatformStub + ); $this->assertSame($this->numberOfAuthentications, $rawConnectedServiceProvider->getNumberOfAuthentications()); $this->assertInstanceOf(DateTimeImmutable::class, $rawConnectedServiceProvider->getLastAuthenticationAt()); @@ -92,80 +95,80 @@ class RawConnectedServiceProviderTest extends TestCase public function testThrowsIfColumnNotSet(): void { $rawRow = $this->rawRow; - unset($rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES]); + unset($rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES]); $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfNumberOfAuthenticationsNotNumeric(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] = 'a'; + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] = 'a'; $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfLastAuthenticationAtNotString(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_LAST_AUTHENTICATION_AT] = 1; + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_LAST_AUTHENTICATION_AT] = 1; $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfFirstAuthenticationAtNotString(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_FIRST_AUTHENTICATION_AT] = 1; + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_FIRST_AUTHENTICATION_AT] = 1; $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfSpMetadataNotString(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] = 1; + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] = 1; $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfUserAttributesNotString(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES] = 1; + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] = 1; $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfSpMetadataNotValid(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] = serialize(1); + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] = serialize(1); $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } public function testThrowsIfUserAttributesNotValid(): void { $rawRow = $this->rawRow; - $rawRow[TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES] = serialize(1); + $rawRow[TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] = serialize(1); $this->expectException(UnexpectedValueException::class); - new RawConnectedServiceProvider($rawRow, $this->abstractPlatformStub); + new RawConnectedService($rawRow, $this->abstractPlatformStub); } } 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 new file mode 100644 index 0000000000000000000000000000000000000000..aac918695cb0dd8c438fe6de50b1dba813b47445 --- /dev/null +++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/Store/RepositoryTest.php @@ -0,0 +1,383 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store; + +use DateInterval; +use DateTimeImmutable; +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; +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; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\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\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000000CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000500CreateUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateConnectedServiceTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Services\HelpersManager + */ +class RepositoryTest extends TestCase +{ + protected Connection $connection; + protected \Doctrine\DBAL\Connection $dbal; + /** + * @var Stub + */ + protected $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; + /** + * @var Stub + */ + protected $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 . + 'ConnectedServices' . DIRECTORY_SEPARATOR . + 'DoctrineDbal' . DIRECTORY_SEPARATOR . + 'Versioned' . 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 testCanGetConnectedServices(): 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]; + + $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash); + + $this->assertCount(0, $resultArray); + + $this->repository->insertConnectedService($idpSpUserVersionId); + + $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash); + $this->assertCount(1, $resultArray); + + $this->assertEquals( + '1', + $resultArray[$this->spEntityId] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] + ); + $this->assertSame( + $this->spMetadata, + $resultArray[$this->spEntityId] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] + ); + $this->assertSame( + $this->userAttributes, + $resultArray[$this->spEntityId] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] + ); + + $connectedServiceId = (int)$this->repository->getConnectedService($idpSpUserVersionId)->fetchOne(); + + $this->repository->updateConnectedServiceVersionCount( + $connectedServiceId, + new DateTimeImmutable() + ); + + $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash); + $this->assertCount(1, $resultArray); + $this->assertEquals( + '2', + $resultArray[$this->spEntityId] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] + ); + $this->assertSame( + $this->spMetadata, + $resultArray[$this->spEntityId] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] + ); + $this->assertSame( + $this->userAttributes, + $resultArray[$this->spEntityId] + [TableConstants::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, $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(); + + $idpSpUserVersionId = + (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; + + $this->repository->insertConnectedService($idpSpUserVersionId); + + $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash); + $this->assertCount(2, $resultArray); + $this->assertEquals( + '1', + $resultArray[$spEntityIdNew] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] + ); + $this->assertSame( + $spMetadataNew, + $resultArray[$spEntityIdNew] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_SP_METADATA] + ); + $this->assertSame( + $this->userAttributes, + $resultArray[$this->spEntityId] + [TableConstants::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[BaseTableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID]; + $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId); + $idpSpUserVersionResult = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId) + ->fetchAssociative(); + $idpSpUserVersionId = + (int)$idpSpUserVersionResult[BaseTableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; + + $this->repository->insertConnectedService($idpSpUserVersionId); + + $resultArray = $this->repository->getConnectedServices($this->userIdentifierHash); + + $this->assertCount(2, $resultArray); + $this->assertEquals( + '2', + $resultArray[$spEntityIdNew] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] + ); + $this->assertSame( + $spMetadataNew, + $resultArray[$spEntityIdNew] + [TableConstants::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] + ); + + // First SP still has old user attributes version... + $this->assertSame( + $this->userAttributes, + $resultArray[$this->spEntityId] + [TableConstants::ENTITY_CONNECTED_SERVICE_COLUMN_NAME_USER_ATTRIBUTES] + ); + } + + public function testGetConnectedServiceProvidersThrowsOnInvalidDbal(): void + { + $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); + $repository = new Repository($this->connectionStub, $this->loggerStub); + $this->expectException(StoreException::class); + + $repository->getConnectedServices($this->userIdentifierHash); + } + + /** + * @throws StoreException + * @throws \Doctrine\DBAL\Exception + */ + public function testCanDeleteConnectedServicesOlderThan(): 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]; + + $this->repository->insertConnectedService($idpSpUserVersionId); + + $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/Versioned/StoreTest.php b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/StoreTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3e810e1698ad9eee4d79d8af17f0d6fdefb1bbac --- /dev/null +++ b/tests/src/Data/Stores/Accounting/ConnectedServices/DoctrineDbal/Versioned/StoreTest.php @@ -0,0 +1,257 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned; + +use DateTimeImmutable; +use Doctrine\DBAL\Result; +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\HashDecoratedState; +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\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\Entities\Authentication\Event\State; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +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; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\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\ConnectedServices\DoctrineDbal\Versioned\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\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000000CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000500CreateUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateConnectedServiceTable + * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\HashDecoratedState + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateIdpVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Migrations\CreateSpVersionTable + * @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\Versioned\Store\Migrations\CreateIdpSpUserVersionTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Bases\DoctrineDbal\Versioned\Store + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore + * @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\Data\Stores\Accounting\ConnectedServices\DoctrineDbal\RawConnectedService + * @uses \SimpleSAML\Module\accounting\Entities\Activity\Bag + * @uses \SimpleSAML\Module\accounting\Entities\Activity + * @uses \SimpleSAML\Module\accounting\Services\HelpersManager + * @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( + 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 + */ + 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[TableConstants::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/Stores/Bases/AbstractStoreTest.php b/tests/src/Data/Stores/Bases/AbstractStoreTest.php similarity index 72% rename from tests/src/Stores/Bases/AbstractStoreTest.php rename to tests/src/Data/Stores/Bases/AbstractStoreTest.php index 9e4546043bb07ba3eea75820763f560ba93f9742..99576c2f99fa1a1917062fcd0907b2231379fa1e 100644 --- a/tests/src/Stores/Bases/AbstractStoreTest.php +++ b/tests/src/Data/Stores/Bases/AbstractStoreTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Bases; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Bases; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores\Bases\AbstractStore; -use PHPUnit\Framework\TestCase; /** - * @covers \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @covers \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore */ class AbstractStoreTest extends TestCase { - protected AbstractStore $abstractStore; + protected \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore $abstractStore; /** * @var Stub */ @@ -33,12 +33,12 @@ class AbstractStoreTest extends TestCase $this->abstractStore = new class ( $this->moduleConfigurationStub, $this->loggerStub - ) extends AbstractStore { + ) extends \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore { public static function build( ModuleConfiguration $moduleConfiguration, LoggerInterface $logger, string $connectionKey = null - ): AbstractStore { + ): \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore { return new self($moduleConfiguration, $logger, $connectionKey); } public function needsSetup(): bool @@ -55,7 +55,7 @@ class AbstractStoreTest extends TestCase { $this->assertInstanceOf(AbstractStore::class, $this->abstractStore); $this->assertInstanceOf( - AbstractStore::class, + \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore::class, $this->abstractStore::build($this->moduleConfigurationStub, $this->loggerStub) ); } diff --git a/tests/src/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php b/tests/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php similarity index 89% rename from tests/src/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php rename to tests/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php index 4c1fff14c33a70dd01e8bfa9935357341ac6fc29..60f26c97e31808601adb732adf30c8857a89cf30 100644 --- a/tests/src/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php +++ b/tests/src/Data/Stores/Bases/DoctrineDbal/AbstractRawEntityTest.php @@ -2,17 +2,17 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Bases\DoctrineDbal; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Bases\DoctrineDbal; use Doctrine\DBAL\Platforms\AbstractPlatform; use PHPUnit\Framework\MockObject\Stub; -use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity; +use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; use SimpleSAML\Test\Module\accounting\Constants\DateTime; /** - * @covers \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity + * @covers \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity */ class AbstractRawEntityTest extends TestCase { diff --git a/tests/src/Stores/Builders/DataStoreBuilderTest.php b/tests/src/Data/Stores/Builders/DataStoreBuilderTest.php similarity index 55% rename from tests/src/Stores/Builders/DataStoreBuilderTest.php rename to tests/src/Data/Stores/Builders/DataStoreBuilderTest.php index 307a5a485e298a04c01c62edfb461f784d038495..91ed81cac41042fb082f5d5bfe9095156987ea0d 100644 --- a/tests/src/Stores/Builders/DataStoreBuilderTest.php +++ b/tests/src/Data/Stores/Builders/DataStoreBuilderTest.php @@ -2,31 +2,34 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Builders; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Builders; use PHPUnit\Framework\MockObject\Stub; +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\Builders\DataStoreBuilder; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Builders\DataStoreBuilder; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Builders\DataStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store + * @covers \SimpleSAML\Module\accounting\Data\Stores\Builders\DataStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore - * @uses \SimpleSAML\Module\accounting\Stores\Builders\Bases\AbstractStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository - * @uses \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder + * @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\Activity\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @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 */ class DataStoreBuilderTest extends TestCase { diff --git a/tests/src/Stores/Builders/JobsStoreBuilderTest.php b/tests/src/Data/Stores/Builders/JobsStoreBuilderTest.php similarity index 77% rename from tests/src/Stores/Builders/JobsStoreBuilderTest.php rename to tests/src/Data/Stores/Builders/JobsStoreBuilderTest.php index cb1add5b1fe7b79736eb0fb4731efa901c894fd5..68902935f36291d44b9b1616f21a36162a7df89c 100644 --- a/tests/src/Stores/Builders/JobsStoreBuilderTest.php +++ b/tests/src/Data/Stores/Builders/JobsStoreBuilderTest.php @@ -2,33 +2,33 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Builders; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Builders; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder; +use SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\StoreInterface; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Builders\Bases\AbstractStoreBuilder; -use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder; -use SimpleSAML\Module\accounting\Stores\Interfaces\StoreInterface; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Builders\Bases\AbstractStoreBuilder - * @covers \SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Repository - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore + * @covers \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder + * @covers \SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store + * @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\Jobs\DoctrineDbal\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore */ class JobsStoreBuilderTest extends TestCase { diff --git a/tests/src/Stores/Connections/Bases/AbstractMigratorTest.php b/tests/src/Data/Stores/Connections/Bases/AbstractMigratorTest.php similarity index 76% rename from tests/src/Stores/Connections/Bases/AbstractMigratorTest.php rename to tests/src/Data/Stores/Connections/Bases/AbstractMigratorTest.php index 8a932f447468c688ebe88e23fee5671b8b4be678..9b8d3e41b65fed74aa550228d949530c9bbc0a5f 100644 --- a/tests/src/Stores/Connections/Bases/AbstractMigratorTest.php +++ b/tests/src/Data/Stores/Connections/Bases/AbstractMigratorTest.php @@ -2,33 +2,35 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Connections\Bases; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Connections\Bases; use Doctrine\DBAL\Schema\AbstractSchemaManager; use Exception; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator + * @covers \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem * @uses \SimpleSAML\Module\accounting\ModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable * @uses \SimpleSAML\Module\accounting\Services\HelpersManager */ class AbstractMigratorTest extends TestCase @@ -89,7 +91,9 @@ class AbstractMigratorTest extends TestCase $migrationClasses = $migrator->gatherMigrationClassesFromDirectory($directory, $namespace); - $jobsTableName = $this->connection->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_JOB); + $jobsTableName = $this->connection->preparePrefixedTableName( + TableConstants::TABLE_NAME_JOB + ); $this->assertFalse($this->schemaManager->tablesExist($jobsTableName)); @@ -150,7 +154,7 @@ class AbstractMigratorTest extends TestCase ); $this->assertTrue(in_array( - Store\Migrations\Version20220601000000CreateJobTable::class, + Version20220601000000CreateJobTable::class, $nonImplementedMigrationClasses )); } @@ -193,8 +197,12 @@ class AbstractMigratorTest extends TestCase protected function getSampleMigrationsDirectory(): string { return $this->moduleConfiguration->getModuleSourceDirectory() . DIRECTORY_SEPARATOR . - 'Stores' . DIRECTORY_SEPARATOR . 'Jobs' . DIRECTORY_SEPARATOR . 'DoctrineDbal' . DIRECTORY_SEPARATOR . - 'Store' . DIRECTORY_SEPARATOR . AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME; + 'Data' . DIRECTORY_SEPARATOR . + 'Stores' . DIRECTORY_SEPARATOR . + 'Jobs' . DIRECTORY_SEPARATOR . + 'DoctrineDbal' . DIRECTORY_SEPARATOR . + 'Store' . DIRECTORY_SEPARATOR . + AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME; } protected function getSampleNameSpace(): string diff --git a/tests/src/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php similarity index 83% rename from tests/src/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php rename to tests/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php index 8709e6e6594be9410aee6a8528b647c1413d136c..c0724703a8932706a2d882a253c7bd73270082af 100644 --- a/tests/src/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php +++ b/tests/src/Data/Stores/Connections/DoctrineDbal/Bases/AbstractMigrationTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Connections\DoctrineDbal\Bases; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases; use Exception; -use SimpleSAML\Module\accounting\Exceptions\StoreException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable; +use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection + * @covers \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ class AbstractMigrationTest extends TestCase { diff --git a/tests/src/Stores/Connections/DoctrineDbal/ConnectionTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/ConnectionTest.php similarity index 79% rename from tests/src/Stores/Connections/DoctrineDbal/ConnectionTest.php rename to tests/src/Data/Stores/Connections/DoctrineDbal/ConnectionTest.php index 98ff0dd1fcfc7afde76d1e3bc10e2160cb421d11..a47f7de10e8144723e1153ca73685d8a6e81d2d8 100644 --- a/tests/src/Stores/Connections/DoctrineDbal/ConnectionTest.php +++ b/tests/src/Data/Stores/Connections/DoctrineDbal/ConnectionTest.php @@ -2,14 +2,14 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Connections\DoctrineDbal; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Connections\DoctrineDbal; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; /** - * @covers \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection + * @covers \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection */ class ConnectionTest extends TestCase { @@ -52,6 +52,6 @@ class ConnectionTest extends TestCase $parameters['table_prefix'] = new class () { }; - (new Connection($parameters)); + (new \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection($parameters)); } } diff --git a/tests/src/Stores/Connections/DoctrineDbal/FactoryTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/FactoryTest.php similarity index 69% rename from tests/src/Stores/Connections/DoctrineDbal/FactoryTest.php rename to tests/src/Data/Stores/Connections/DoctrineDbal/FactoryTest.php index d1cd3d0356498769f1c786b31c8ad217408f46b7..76c63743ffddead34b3b6ac3e7063ab9d0f9eab9 100644 --- a/tests/src/Stores/Connections/DoctrineDbal/FactoryTest.php +++ b/tests/src/Data/Stores/Connections/DoctrineDbal/FactoryTest.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Connections\DoctrineDbal; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Connections\DoctrineDbal; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +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\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory + * @covers \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Factory * @uses \SimpleSAML\Module\accounting\ModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\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\Data\Stores\Connections\Bases\AbstractMigrator * @uses \SimpleSAML\Module\accounting\Services\HelpersManager */ class FactoryTest extends TestCase diff --git a/tests/src/Stores/Connections/DoctrineDbal/MigratorTest.php b/tests/src/Data/Stores/Connections/DoctrineDbal/MigratorTest.php similarity index 82% rename from tests/src/Stores/Connections/DoctrineDbal/MigratorTest.php rename to tests/src/Data/Stores/Connections/DoctrineDbal/MigratorTest.php index a7d449627d73cf264b917580af6cd064077fd977..1304a589973c2d248adbcc250c7d32f2f7cd174b 100644 --- a/tests/src/Stores/Connections/DoctrineDbal/MigratorTest.php +++ b/tests/src/Data/Stores/Connections/DoctrineDbal/MigratorTest.php @@ -2,37 +2,38 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Connections\DoctrineDbal; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Connections\DoctrineDbal; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\QueryBuilder; +use Doctrine\DBAL\Schema\AbstractSchemaManager; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Doctrine\DBAL\Schema\AbstractSchemaManager; +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\Data\Stores\Interfaces\MigrationInterface; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Exceptions\InvalidValueException; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; use function PHPUnit\Framework\assertFalse; /** - * @covers \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @covers \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable + * @covers \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Migrator + * @covers \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\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable * @uses \SimpleSAML\Module\accounting\ModuleConfiguration * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable * @uses \SimpleSAML\Module\accounting\Services\HelpersManager */ class MigratorTest extends TestCase @@ -108,7 +109,9 @@ class MigratorTest extends TestCase $migrator->runSetup(); - $tableNameJobs = $this->connection->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_JOB); + $tableNameJobs = $this->connection->preparePrefixedTableName( + TableConstants::TABLE_NAME_JOB + ); $this > assertFalse($this->schemaManager->tablesExist($tableNameJobs)); $migrator->runMigrationClasses([Store\Migrations\Version20220601000000CreateJobTable::class]); @@ -202,7 +205,9 @@ class MigratorTest extends TestCase ->willReturn(false); $dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class); $dbalStub->method('createSchemaManager')->willReturn($schemaManagerStub); - $connectionStub = $this->createStub(Connection::class); + $connectionStub = $this->createStub( + Connection::class + ); $connectionStub->method('dbal')->willReturn($dbalStub); $migrator = new Migrator($connectionStub, $this->loggerServiceMock); @@ -223,7 +228,9 @@ class MigratorTest extends TestCase ->willThrowException(new Exception('test')); $dbalStub = $this->createStub(\Doctrine\DBAL\Connection::class); $dbalStub->method('createQueryBuilder')->willReturn($queryBuilderStub); - $connectionStub = $this->createStub(Connection::class); + $connectionStub = $this->createStub( + Connection::class + ); $connectionStub->method('dbal')->willReturn($dbalStub); $connectionStub->method('preparePrefixedTableName')->willReturn(Migrator::TABLE_NAME); @@ -252,8 +259,12 @@ class MigratorTest extends TestCase protected function getSampleMigrationsDirectory(): string { return $this->moduleConfiguration->getModuleSourceDirectory() . DIRECTORY_SEPARATOR . - 'Stores' . DIRECTORY_SEPARATOR . 'Jobs' . DIRECTORY_SEPARATOR . 'DoctrineDbal' . DIRECTORY_SEPARATOR . - 'Store' . DIRECTORY_SEPARATOR . AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME; + 'Data' . DIRECTORY_SEPARATOR . + 'Stores' . DIRECTORY_SEPARATOR . + 'Jobs' . DIRECTORY_SEPARATOR . + 'DoctrineDbal' . DIRECTORY_SEPARATOR . + 'Store' . DIRECTORY_SEPARATOR . + AbstractMigrator::DEFAULT_MIGRATIONS_DIRECTORY_NAME; } protected function getSampleNameSpace(): string diff --git a/tests/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php similarity index 77% rename from tests/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php rename to tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php index c346d8b1fa81251e14a21a32704400320e16cdc3..ec245016109f91a5348d0ac49cad9e0c0947a475 100644 --- a/tests/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php +++ b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000000CreateJobTableTest.php @@ -2,23 +2,23 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable; -use PHPUnit\Framework\TestCase; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration */ class Version20220601000000CreateJobTableTest extends TestCase { @@ -33,7 +33,9 @@ class Version20220601000000CreateJobTableTest extends TestCase { $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY); $this->schemaManager = $this->connection->dbal()->createSchemaManager(); - $this->tableName = $this->connection->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_JOB); + $this->tableName = $this->connection->preparePrefixedTableName( + TableConstants::TABLE_NAME_JOB + ); } /** diff --git a/tests/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php similarity index 78% rename from tests/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php rename to tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php index f4fcabdf0fba07cdd0158e1e86cb0d4846b39e01..ea7658c70f7904cc4f9d2fe68af012b7ac37af10 100644 --- a/tests/src/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php +++ b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/Migrations/Version20220601000100CreateJobFailedTableTest.php @@ -2,23 +2,23 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations; use Doctrine\DBAL\Exception; use Doctrine\DBAL\Schema\AbstractSchemaManager; +use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations; -use PHPUnit\Framework\TestCase; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable */ class Version20220601000100CreateJobFailedTableTest extends TestCase { @@ -33,7 +33,9 @@ class Version20220601000100CreateJobFailedTableTest extends TestCase { $this->connection = new Connection(ConnectionParameters::DBAL_SQLITE_MEMORY); $this->schemaManager = $this->connection->dbal()->createSchemaManager(); - $this->tableName = $this->connection->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_JOB_FAILED); + $this->tableName = $this->connection->preparePrefixedTableName( + TableConstants::TABLE_NAME_JOB_FAILED + ); } /** diff --git a/tests/src/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php similarity index 66% rename from tests/src/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php rename to tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php index e272b82902bf6e70c745aae31b6b1159b0613575..31a968561f2299e1b2c8e83fb1a2455d6ae029ad 100644 --- a/tests/src/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php +++ b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RawJobTest.php @@ -2,26 +2,26 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Jobs\DoctrineDbal\Store; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use DateTimeImmutable; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\RawJob; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Entities\Authentication\Event; use SimpleSAML\Module\accounting\Entities\Authentication\Event\State; use SimpleSAML\Module\accounting\Exceptions\UnexpectedValueException; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\RawJob; use SimpleSAML\Test\Module\accounting\Constants\StateArrays; /** - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\RawJob + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\RawJob * @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\Stores\Bases\DoctrineDbal\AbstractRawEntity + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity * @uses \SimpleSAML\Module\accounting\Helpers\Network * @uses \SimpleSAML\Module\accounting\Services\HelpersManager */ @@ -40,27 +40,27 @@ class RawJobTest extends TestCase $this->authenticationEvent = new Event(new State\Saml2(StateArrays::SAML2_FULL)); $this->validRawRow = [ - Store\TableConstants::COLUMN_NAME_ID => 1, - Store\TableConstants::COLUMN_NAME_PAYLOAD => serialize($this->authenticationEvent), - Store\TableConstants::COLUMN_NAME_TYPE => get_class($this->authenticationEvent), - Store\TableConstants::COLUMN_NAME_CREATED_AT => '2022-08-17 13:26:12', + TableConstants::COLUMN_NAME_ID => 1, + TableConstants::COLUMN_NAME_PAYLOAD => serialize($this->authenticationEvent), + TableConstants::COLUMN_NAME_TYPE => get_class($this->authenticationEvent), + TableConstants::COLUMN_NAME_CREATED_AT => '2022-08-17 13:26:12', ]; } public function testCanInstantiateValidRawJob(): void { $abstractPlatform = new SqlitePlatform(); - $rawJob = new Store\RawJob($this->validRawRow, $abstractPlatform); - $this->assertSame($rawJob->getId(), $this->validRawRow[Store\TableConstants::COLUMN_NAME_ID]); + $rawJob = new RawJob($this->validRawRow, $abstractPlatform); + $this->assertSame($rawJob->getId(), $this->validRawRow[TableConstants::COLUMN_NAME_ID]); $this->assertEquals($rawJob->getPayload(), $this->authenticationEvent); - $this->assertSame($rawJob->getType(), $this->validRawRow[Store\TableConstants::COLUMN_NAME_TYPE]); + $this->assertSame($rawJob->getType(), $this->validRawRow[TableConstants::COLUMN_NAME_TYPE]); $this->assertInstanceOf(DateTimeImmutable::class, $rawJob->getCreatedAt()); } public function testThrowsOnEmptyColumn(): void { $invalidRawRow = $this->validRawRow; - unset($invalidRawRow[Store\TableConstants::COLUMN_NAME_ID]); + unset($invalidRawRow[TableConstants::COLUMN_NAME_ID]); $this->expectException(UnexpectedValueException::class); @@ -70,7 +70,7 @@ class RawJobTest extends TestCase public function testThrowsOnNonNumericId(): void { $invalidRawRow = $this->validRawRow; - $invalidRawRow[Store\TableConstants::COLUMN_NAME_ID] = 'a'; + $invalidRawRow[TableConstants::COLUMN_NAME_ID] = 'a'; $this->expectException(UnexpectedValueException::class); @@ -80,7 +80,7 @@ class RawJobTest extends TestCase public function testThrowsOnNonStringPayload(): void { $invalidRawRow = $this->validRawRow; - $invalidRawRow[Store\TableConstants::COLUMN_NAME_PAYLOAD] = 123; + $invalidRawRow[TableConstants::COLUMN_NAME_PAYLOAD] = 123; $this->expectException(UnexpectedValueException::class); @@ -90,27 +90,27 @@ class RawJobTest extends TestCase public function testThrowsOnNonAbstractPayload(): void { $invalidRawRow = $this->validRawRow; - $invalidRawRow[Store\TableConstants::COLUMN_NAME_PAYLOAD] = serialize('abc'); + $invalidRawRow[TableConstants::COLUMN_NAME_PAYLOAD] = serialize('abc'); $this->expectException(UnexpectedValueException::class); - new Store\RawJob($invalidRawRow, $this->abstractPlatformStub); + new RawJob($invalidRawRow, $this->abstractPlatformStub); } public function testThrowsOnNonStringType(): void { $invalidRawRow = $this->validRawRow; - $invalidRawRow[Store\TableConstants::COLUMN_NAME_TYPE] = 123; + $invalidRawRow[TableConstants::COLUMN_NAME_TYPE] = 123; $this->expectException(UnexpectedValueException::class); - new Store\RawJob($invalidRawRow, $this->abstractPlatformStub); + new RawJob($invalidRawRow, $this->abstractPlatformStub); } public function testThrowsOnNonStringCreatedAt(): void { $invalidRawRow = $this->validRawRow; - $invalidRawRow[Store\TableConstants::COLUMN_NAME_CREATED_AT] = 123; + $invalidRawRow[TableConstants::COLUMN_NAME_CREATED_AT] = 123; $this->expectException(UnexpectedValueException::class); @@ -120,10 +120,10 @@ class RawJobTest extends TestCase public function testThrowsOnNonValidCreatedAt(): void { $invalidRawRow = $this->validRawRow; - $invalidRawRow[Store\TableConstants::COLUMN_NAME_CREATED_AT] = '123'; + $invalidRawRow[TableConstants::COLUMN_NAME_CREATED_AT] = '123'; $this->expectException(UnexpectedValueException::class); - new Store\RawJob($invalidRawRow, $this->abstractPlatformStub); + new RawJob($invalidRawRow, $this->abstractPlatformStub); } } diff --git a/tests/src/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php similarity index 81% rename from tests/src/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php rename to tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php index 3924415b2237a82caaf76fb2026240c9256fcca2..3b5da38c4bb5c23ac4999bfb0686843a7242703c 100644 --- a/tests/src/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php +++ b/tests/src/Data/Stores/Jobs/DoctrineDbal/Store/RepositoryTest.php @@ -2,12 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Jobs\DoctrineDbal\Store; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; use DateTimeImmutable; use Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; +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\Data\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Repository; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Entities\Authentication\Event; use SimpleSAML\Module\accounting\Entities\Authentication\Event\State; use SimpleSAML\Module\accounting\Entities\Bases\AbstractJob; @@ -17,37 +23,32 @@ use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Repository; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; use SimpleSAML\Test\Module\accounting\Constants\StateArrays; /** - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Repository - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Connection * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractJob * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem * @uses \SimpleSAML\Module\accounting\ModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\RawJob + * @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\Migrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\RawJob * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\Job - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractState * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\State\Saml2 - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity * @uses \SimpleSAML\Module\accounting\Helpers\Network * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore */ class RepositoryTest extends TestCase { @@ -92,7 +93,9 @@ class RepositoryTest extends TestCase $this->factoryStub ); - $this->jobsTableName = $this->connection->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_JOB); + $this->jobsTableName = $this->connection->preparePrefixedTableName( + TableConstants::TABLE_NAME_JOB + ); } /** @@ -138,7 +141,7 @@ class RepositoryTest extends TestCase $this->expectException(StoreException::class); - $invalidType = str_pad('abc', Store\TableConstants::COLUMN_TYPE_LENGTH + 1); + $invalidType = str_pad('abc', TableConstants::COLUMN_TYPE_LENGTH + 1); $jobStub = $this->createStub(GenericJob::class); $jobStub->method('getPayload')->willReturn($this->payloadStub); $jobStub->method('getType')->willReturn($invalidType); @@ -172,7 +175,7 @@ class RepositoryTest extends TestCase $this->jobsStore->runSetup(); $payloadStub = $this->createStub(AbstractPayload::class); - $jobStub = $this->createStub(AbstractJob::class); // Abstract classes can't be initialized.. + $jobStub = $this->createStub(AbstractJob::class); // Abstract classes can't be initialized... $jobStub->method('getPayload')->willReturn($payloadStub); $jobStub->method('getType')->willReturn(AbstractJob::class); $jobStub->method('getCreatedAt')->willReturn(new DateTimeImmutable()); diff --git a/tests/src/Stores/Jobs/DoctrineDbal/StoreTest.php b/tests/src/Data/Stores/Jobs/DoctrineDbal/StoreTest.php similarity index 86% rename from tests/src/Stores/Jobs/DoctrineDbal/StoreTest.php rename to tests/src/Data/Stores/Jobs/DoctrineDbal/StoreTest.php index d93c78749062e03f3acd21e9e41aaabd9f941fb7..7e8712dab0da38e117eaf91f9beeda89d910971a 100644 --- a/tests/src/Stores/Jobs/DoctrineDbal/StoreTest.php +++ b/tests/src/Data/Stores/Jobs/DoctrineDbal/StoreTest.php @@ -2,12 +2,18 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Jobs\DoctrineDbal; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Jobs\DoctrineDbal; use DateTimeImmutable; use Doctrine\DBAL\Exception; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; +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\Data\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Repository; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\TableConstants; use SimpleSAML\Module\accounting\Entities\Authentication\Event; use SimpleSAML\Module\accounting\Entities\Authentication\Event\State; use SimpleSAML\Module\accounting\Entities\Bases\AbstractJob; @@ -17,37 +23,33 @@ use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\Logger; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -use SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; use SimpleSAML\Test\Module\accounting\Constants\StateArrays; /** - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store - * @covers \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store + * @covers \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractStore * @uses \SimpleSAML\Module\accounting\ModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\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\Connections\Bases\AbstractMigrator * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\DoctrineDbal\Bases\AbstractMigration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000000CreateJobTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Version20220601000100CreateJobFailedTable * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractJob - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\RawJob + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\RawJob * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\Job - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Repository - * @uses \SimpleSAML\Module\accounting\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store\Migrations\Bases\AbstractCreateJobsTable * @uses \SimpleSAML\Module\accounting\Entities\Bases\AbstractState * @uses \SimpleSAML\Module\accounting\Entities\Authentication\Event\State\Saml2 - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractRawEntity + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\DoctrineDbal\AbstractRawEntity * @uses \SimpleSAML\Module\accounting\Helpers\Network * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore */ class StoreTest extends TestCase { @@ -143,9 +145,11 @@ class StoreTest extends TestCase $this->factoryStub ); - $tableNameJobs = $this->connection->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_JOB); + $tableNameJobs = $this->connection->preparePrefixedTableName( + TableConstants::TABLE_NAME_JOB + ); $tableNameFailedJobs = $this->connection->preparePrefixedTableName( - Store\TableConstants::TABLE_NAME_JOB_FAILED + TableConstants::TABLE_NAME_JOB_FAILED ); $this->assertSame($tableNameJobs, $jobsStore->getPrefixedTableNameJobs()); @@ -318,7 +322,7 @@ class StoreTest extends TestCase */ public function testDequeueThrowsForJobWithInvalidId(): void { - $repositoryStub = $this->createStub(Store\Repository::class); + $repositoryStub = $this->createStub(Repository::class); $jobStub = $this->createStub(GenericJob::class); $jobStub->method('getPayload')->willReturn($this->payloadStub); $jobStub->method('getCreatedAt')->willReturn(new DateTimeImmutable()); @@ -349,7 +353,7 @@ class StoreTest extends TestCase */ public function testDequeThrowsAfterMaxDeleteAttempts(): void { - $repositoryStub = $this->createStub(Store\Repository::class); + $repositoryStub = $this->createStub(Repository::class); $repositoryStub->method('getNext')->willReturn($this->jobStub); $repositoryStub->method('delete')->willReturn(false); @@ -375,7 +379,7 @@ class StoreTest extends TestCase */ public function testCanContinueSearchingInCaseOfJobDeletion(): void { - $repositoryStub = $this->createStub(Store\Repository::class); + $repositoryStub = $this->createStub(Repository::class); $repositoryStub->method('getNext')->willReturn($this->jobStub); $repositoryStub->method('delete')->willReturnOnConsecutiveCalls(false, true); diff --git a/tests/src/Stores/Jobs/PhpRedis/RedisStoreTest.php b/tests/src/Data/Stores/Jobs/PhpRedis/RedisStoreTest.php similarity index 90% rename from tests/src/Stores/Jobs/PhpRedis/RedisStoreTest.php rename to tests/src/Data/Stores/Jobs/PhpRedis/RedisStoreTest.php index 188a36a5c639039ec3e87373726b4a27ac397fec..7fce3c2ca62206adc63c5e2aeb27895f8e8618b2 100644 --- a/tests/src/Stores/Jobs/PhpRedis/RedisStoreTest.php +++ b/tests/src/Data/Stores/Jobs/PhpRedis/RedisStoreTest.php @@ -4,24 +4,24 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Stores\Jobs\PhpRedis; +namespace SimpleSAML\Test\Module\accounting\Data\Stores\Jobs\PhpRedis; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Redis; use RedisException; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore; use SimpleSAML\Module\accounting\Entities\GenericJob; use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores\Jobs\PhpRedis\RedisStore; -use PHPUnit\Framework\TestCase; /** - * @covers \SimpleSAML\Module\accounting\Stores\Jobs\PhpRedis\RedisStore - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @covers \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore */ class RedisStoreTest extends TestCase { @@ -61,7 +61,7 @@ class RedisStoreTest extends TestCase $this->assertInstanceOf( RedisStore::class, - new RedisStore( + new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, @@ -80,7 +80,7 @@ class RedisStoreTest extends TestCase $this->assertInstanceOf( RedisStore::class, - new RedisStore( + new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, @@ -104,8 +104,8 @@ class RedisStoreTest extends TestCase $this->redisMock->method('connect')->willThrowException(new RedisException('test')); $this->assertInstanceOf( - RedisStore::class, - new RedisStore( + \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore::class, + new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, @@ -129,8 +129,8 @@ class RedisStoreTest extends TestCase $this->redisMock->method('auth')->willThrowException(new RedisException('test')); $this->assertInstanceOf( - RedisStore::class, - new RedisStore( + \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore::class, + new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, @@ -154,7 +154,7 @@ class RedisStoreTest extends TestCase $this->assertInstanceOf( RedisStore::class, - new RedisStore( + new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, @@ -199,7 +199,7 @@ class RedisStoreTest extends TestCase $this->redisMock->method('rPush')->willThrowException(new RedisException('test')); - $redisStore = new RedisStore( + $redisStore = new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, @@ -245,7 +245,7 @@ class RedisStoreTest extends TestCase ->method('error') ->with($this->stringContains('Could not pop job from Redis list.')); - $redisStore = new RedisStore( + $redisStore = new \SimpleSAML\Module\accounting\Data\Stores\Jobs\PhpRedis\RedisStore( $this->moduleConfigurationStub, $this->loggerMock, null, diff --git a/tests/src/Trackers/Authentication/DoctrineDbal/Versioned/TrackerTest.php b/tests/src/Data/Trackers/Activity/DoctrineDbal/Versioned/DataTrackerTest.php similarity index 52% rename from tests/src/Trackers/Authentication/DoctrineDbal/Versioned/TrackerTest.php rename to tests/src/Data/Trackers/Activity/DoctrineDbal/Versioned/DataTrackerTest.php index 9c51588de8f6b7c8bc6ba01c41be6b1ea997dac0..4a9caee04e1f2b4be41883299e6275cb1b08674f 100644 --- a/tests/src/Trackers/Authentication/DoctrineDbal/Versioned/TrackerTest.php +++ b/tests/src/Data/Trackers/Activity/DoctrineDbal/Versioned/DataTrackerTest.php @@ -2,40 +2,41 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned; +namespace SimpleSAML\Test\Module\accounting\Data\Trackers\Activity\DoctrineDbal\Versioned; use DateInterval; 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\Activity\DoctrineDbal\Versioned\Store; +use SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker; use SimpleSAML\Module\accounting\Entities\Activity; use SimpleSAML\Module\accounting\Entities\Authentication\Event; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; use SimpleSAML\Module\accounting\Exceptions\StoreException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; -use SimpleSAML\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned\Tracker; -use PHPUnit\Framework\TestCase; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned\Tracker + * @covers \SimpleSAML\Module\accounting\Data\Trackers\Activity\DoctrineDbal\VersionedDataTracker * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Builders\Bases\AbstractStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Builders\DataStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\DataStoreBuilder + * @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\Bases\DoctrineDbal\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store + * @uses \SimpleSAML\Module\accounting\Data\Stores\Accounting\Activity\DoctrineDbal\Versioned\Store\Repository * @uses \SimpleSAML\Module\accounting\Helpers\Hash * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator + * @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 */ -class TrackerTest extends TestCase +class DataTrackerTest extends TestCase { /** * @var Stub @@ -48,11 +49,7 @@ class TrackerTest extends TestCase /** * @var MockObject */ - protected $dataStoreMock; - /** - * @var Stub - */ - protected $helpersManagerStub; + protected $store; protected function setUp(): void { @@ -60,8 +57,7 @@ class TrackerTest extends TestCase $this->moduleConfigurationStub->method('getConnectionParameters') ->willReturn(ConnectionParameters::DBAL_SQLITE_MEMORY); $this->loggerMock = $this->createMock(LoggerInterface::class); - $this->dataStoreMock = $this->createMock(Store::class); - $this->helpersManagerStub = $this->createStub(HelpersManager::class); + $this->store = $this->createMock(Store::class); } /** @@ -70,24 +66,23 @@ class TrackerTest extends TestCase public function testCanCreateInstance(): void { $this->assertInstanceOf( - Tracker::class, - new Tracker( + VersionedDataTracker::class, + new VersionedDataTracker( $this->moduleConfigurationStub, $this->loggerMock, ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock + $this->store ) ); $this->assertInstanceOf( - Tracker::class, - new Tracker($this->moduleConfigurationStub, $this->loggerMock) + VersionedDataTracker::class, + new VersionedDataTracker($this->moduleConfigurationStub, $this->loggerMock) ); $this->assertInstanceOf( - Tracker::class, - Tracker::build($this->moduleConfigurationStub, $this->loggerMock) + VersionedDataTracker::class, + VersionedDataTracker::build($this->moduleConfigurationStub, $this->loggerMock) ); } @@ -98,16 +93,15 @@ class TrackerTest extends TestCase { $authenticationEventStub = $this->createStub(Event::class); - $this->dataStoreMock->expects($this->once()) + $this->store->expects($this->once()) ->method('persist') ->with($authenticationEventStub); - $tracker = new Tracker( + $tracker = new VersionedDataTracker( $this->moduleConfigurationStub, $this->loggerMock, ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock + $this->store ); $tracker->process($authenticationEventStub); @@ -118,19 +112,18 @@ class TrackerTest extends TestCase */ public function testSetupDependsOnDataStore(): void { - $this->dataStoreMock->expects($this->exactly(2)) + $this->store->expects($this->exactly(2)) ->method('needsSetup') ->willReturn(true); - $this->dataStoreMock->expects($this->once()) + $this->store->expects($this->once()) ->method('runSetup'); - $tracker = new Tracker( + $tracker = new VersionedDataTracker( $this->moduleConfigurationStub, $this->loggerMock, ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock + $this->store ); $this->assertTrue($tracker->needsSetup()); @@ -143,63 +136,37 @@ class TrackerTest extends TestCase */ public function testRunningSetupIfNotNeededLogsWarning(): void { - $this->dataStoreMock->method('needsSetup') + $this->store->method('needsSetup') ->willReturn(false); $this->loggerMock->expects($this->once()) ->method('warning'); - $tracker = new Tracker( + $tracker = new VersionedDataTracker( $this->moduleConfigurationStub, $this->loggerMock, ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock + $this->store ); $tracker->runSetup(); } - /** - * @throws StoreException - */ - public function testGetConnectedServiceProviders(): void - { - $connectedOrganizationsBagStub = $this->createStub(ConnectedServiceProvider\Bag::class); - $this->dataStoreMock->expects($this->once()) - ->method('getConnectedOrganizations') - ->willReturn($connectedOrganizationsBagStub); - - $tracker = new Tracker( - $this->moduleConfigurationStub, - $this->loggerMock, - ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock - ); - - $this->assertInstanceOf( - ConnectedServiceProvider\Bag::class, - $tracker->getConnectedServiceProviders('test') - ); - } - /** * @throws StoreException */ public function testGetActivity(): void { $activityBag = $this->createStub(Activity\Bag::class); - $this->dataStoreMock->expects($this->once()) + $this->store->expects($this->once()) ->method('getActivity') ->willReturn($activityBag); - $tracker = new Tracker( + $tracker = new VersionedDataTracker( $this->moduleConfigurationStub, $this->loggerMock, ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock + $this->store ); $this->assertInstanceOf( @@ -215,15 +182,14 @@ class TrackerTest extends TestCase { $retentionPolicy = new DateInterval('P10D'); - $this->dataStoreMock->expects($this->once()) + $this->store->expects($this->once()) ->method('deleteDataOlderThan'); - $tracker = new Tracker( + $tracker = new VersionedDataTracker( $this->moduleConfigurationStub, $this->loggerMock, ModuleConfiguration\ConnectionType::MASTER, - $this->helpersManagerStub, - $this->dataStoreMock + $this->store ); $tracker->enforceDataRetentionPolicy($retentionPolicy); diff --git a/tests/src/Trackers/Builders/AuthenticationDataTrackerBuilderTest.php b/tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php similarity index 77% rename from tests/src/Trackers/Builders/AuthenticationDataTrackerBuilderTest.php rename to tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php index f22f4bb9ae54584667df77dc439390b72efda594..ed5c86b7e479e56fe662e9bc38332178924b2b8d 100644 --- a/tests/src/Trackers/Builders/AuthenticationDataTrackerBuilderTest.php +++ b/tests/src/Data/Trackers/Builders/DataTrackerBuilderTest.php @@ -2,23 +2,22 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Trackers\Builders; +namespace SimpleSAML\Test\Module\accounting\Data\Trackers\Builders; use DateInterval; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; use SimpleSAML\Module\accounting\Entities\Authentication\Event; use SimpleSAML\Module\accounting\Exceptions\Exception; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface; use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; /** - * @covers \SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder + * @covers \SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration * @uses \SimpleSAML\Module\accounting\Services\HelpersManager */ @@ -33,7 +32,7 @@ class AuthenticationDataTrackerBuilderTest extends TestCase */ protected $moduleConfigurationStub; - protected AuthenticationDataTrackerInterface $trackerStub; + protected DataTrackerInterface $trackerStub; protected HelpersManager $helpersManager; protected function setUp(): void @@ -45,11 +44,11 @@ class AuthenticationDataTrackerBuilderTest extends TestCase $this->helpersManager = new HelpersManager(); - $this->trackerStub = new class implements AuthenticationDataTrackerInterface { + $this->trackerStub = new class implements DataTrackerInterface { public static function build( ModuleConfiguration $moduleConfiguration, LoggerInterface $logger - ): AuthenticationDataTrackerInterface { + ): DataTrackerInterface { return new self(); } @@ -75,8 +74,8 @@ class AuthenticationDataTrackerBuilderTest extends TestCase public function testCanCreateInstance(): void { $this->assertInstanceOf( - AuthenticationDataTrackerBuilder::class, - new AuthenticationDataTrackerBuilder( + \SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder::class, + new \SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder( $this->moduleConfigurationStub, $this->loggerMock, $this->helpersManager @@ -89,7 +88,7 @@ class AuthenticationDataTrackerBuilderTest extends TestCase */ public function testCanBuildAuthenticationDataTracker(): void { - $authenticationDataTrackerBuilder = new AuthenticationDataTrackerBuilder( + $authenticationDataTrackerBuilder = new \SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder( $this->moduleConfigurationStub, $this->loggerMock, $this->helpersManager @@ -102,7 +101,7 @@ class AuthenticationDataTrackerBuilderTest extends TestCase public function testBuildThrowsForInvalidTrackerClass(): void { - $authenticationDataTrackerBuilder = new AuthenticationDataTrackerBuilder( + $authenticationDataTrackerBuilder = new \SimpleSAML\Module\accounting\Data\Trackers\Builders\DataTrackerBuilder( $this->moduleConfigurationStub, $this->loggerMock, $this->helpersManager diff --git a/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/Versioned/DataTrackerTest.php b/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/Versioned/DataTrackerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..29fef34efba4c9110df8eed522144d690e0423f2 --- /dev/null +++ b/tests/src/Data/Trackers/ConnectedServices/DoctrineDbal/Versioned/DataTrackerTest.php @@ -0,0 +1,199 @@ +<?php + +declare(strict_types=1); + +namespace SimpleSAML\Test\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\Versioned; + +use DateInterval; +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\ConnectedServices\DoctrineDbal\Versioned\Store; +use SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\VersionedDataTracker; +use SimpleSAML\Module\accounting\Entities\Authentication\Event; +use SimpleSAML\Module\accounting\Entities\ConnectedService; +use SimpleSAML\Module\accounting\Exceptions\StoreException; +use SimpleSAML\Module\accounting\ModuleConfiguration; +use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; + +/** + * @covers \SimpleSAML\Module\accounting\Data\Trackers\ConnectedServices\DoctrineDbal\VersionedDataTracker + * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\Bases\AbstractStoreBuilder + * @uses \SimpleSAML\Module\accounting\Data\Stores\Builders\DataStoreBuilder + * @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\Bases\DoctrineDbal\AbstractStore + * @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\Helpers\Hash + * @uses \SimpleSAML\Module\accounting\Services\HelpersManager + * @uses \SimpleSAML\Module\accounting\Data\Stores\Connections\Bases\AbstractMigrator + * @uses \SimpleSAML\Module\accounting\Data\Stores\Bases\AbstractStore + * @uses \SimpleSAML\Module\accounting\Data\Providers\ConnectedServices\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 + */ +class DataTrackerTest 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( + VersionedDataTracker::class, + new VersionedDataTracker( + $this->moduleConfigurationStub, + $this->loggerMock, + ModuleConfiguration\ConnectionType::MASTER, + $this->store + ) + ); + + $this->assertInstanceOf( + VersionedDataTracker::class, + new VersionedDataTracker($this->moduleConfigurationStub, $this->loggerMock) + ); + + $this->assertInstanceOf( + VersionedDataTracker::class, + VersionedDataTracker::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 VersionedDataTracker( + $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 VersionedDataTracker( + $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 VersionedDataTracker( + $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 VersionedDataTracker( + $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 VersionedDataTracker( + $this->moduleConfigurationStub, + $this->loggerMock, + ModuleConfiguration\ConnectionType::MASTER, + $this->store + ); + + $tracker->enforceDataRetentionPolicy($retentionPolicy); + } +} diff --git a/tests/src/Entities/ConnectedServiceProvider/BagTest.php b/tests/src/Entities/ConnectedService/BagTest.php similarity index 73% rename from tests/src/Entities/ConnectedServiceProvider/BagTest.php rename to tests/src/Entities/ConnectedService/BagTest.php index 396c455480eb156a92f6adea27cccd33b4c71093..87c3b756fb4eb6db496b168c45f4ff51d49373c5 100644 --- a/tests/src/Entities/ConnectedServiceProvider/BagTest.php +++ b/tests/src/Entities/ConnectedService/BagTest.php @@ -2,20 +2,20 @@ declare(strict_types=1); -namespace SimpleSAML\Test\Module\accounting\Entities\ConnectedServiceProvider; +namespace SimpleSAML\Test\Module\accounting\Entities\ConnectedService; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider\Bag; +use SimpleSAML\Module\accounting\Entities\ConnectedService; +use SimpleSAML\Module\accounting\Entities\ConnectedService\Bag; use PHPUnit\Framework\TestCase; /** - * @covers \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider\Bag + * @covers \SimpleSAML\Module\accounting\Entities\ConnectedService\Bag */ class BagTest extends TestCase { public function testCanAddConnectedService(): void { - $connectedServiceProvider = $this->createStub(ConnectedServiceProvider::class); + $connectedServiceProvider = $this->createStub(ConnectedService::class); $bag = new Bag(); $this->assertEmpty($bag->getAll()); diff --git a/tests/src/Entities/ConnectedServiceProviderTest.php b/tests/src/Entities/ConnectedServiceTest.php similarity index 89% rename from tests/src/Entities/ConnectedServiceProviderTest.php rename to tests/src/Entities/ConnectedServiceTest.php index 6dcee020713a318bf28b4547a24a2e199afc6186..86c03e5834287fb44281b2825b5a06c480a708f3 100644 --- a/tests/src/Entities/ConnectedServiceProviderTest.php +++ b/tests/src/Entities/ConnectedServiceTest.php @@ -6,15 +6,15 @@ namespace SimpleSAML\Test\Module\accounting\Entities; use DateTimeImmutable; use PHPUnit\Framework\MockObject\Stub; -use SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider; +use SimpleSAML\Module\accounting\Entities\ConnectedService; use PHPUnit\Framework\TestCase; use SimpleSAML\Module\accounting\Entities\Interfaces\ServiceProviderInterface; use SimpleSAML\Module\accounting\Entities\User; /** - * @covers \SimpleSAML\Module\accounting\Entities\ConnectedServiceProvider + * @covers \SimpleSAML\Module\accounting\Entities\ConnectedService */ -class ConnectedServiceProviderTest extends TestCase +class ConnectedServiceTest extends TestCase { /** * @var Stub @@ -37,7 +37,7 @@ class ConnectedServiceProviderTest extends TestCase public function testCanCreateInstance(): void { - $connectedServiceProvider = new ConnectedServiceProvider( + $connectedServiceProvider = new ConnectedService( $this->serviceProviderStub, $this->numberOfAuthentications, $this->dateTime, diff --git a/tests/src/ModuleConfigurationTest.php b/tests/src/ModuleConfigurationTest.php index 7af5525584d0f5add55dd8c72754f785d729a78d..77a9114d9cd948b7fcc6fecf29ef15e54423b57e 100644 --- a/tests/src/ModuleConfigurationTest.php +++ b/tests/src/ModuleConfigurationTest.php @@ -6,10 +6,13 @@ namespace SimpleSAML\Test\Module\accounting; use PHPUnit\Framework\TestCase; use SimpleSAML\Configuration; +use SimpleSAML\Module\accounting\Data\Providers\Activity\DoctrineDbal\VersionedDataProvider; +use SimpleSAML\Module\accounting\Data\Stores; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\JobsStoreInterface; +use SimpleSAML\Module\accounting\Data\Stores\Jobs\DoctrineDbal\Store; +use SimpleSAML\Module\accounting\Data\Trackers; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException; use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Stores; -use SimpleSAML\Module\accounting\Trackers; use stdClass; /** @@ -56,7 +59,7 @@ class ModuleConfigurationTest extends TestCase public function testCanGetJobsStoreClass(): void { $this->assertTrue( - is_subclass_of($this->moduleConfiguration->getJobsStoreClass(), Stores\Interfaces\JobsStoreInterface::class) + is_subclass_of($this->moduleConfiguration->getJobsStoreClass(), JobsStoreInterface::class) ); } @@ -96,7 +99,9 @@ class ModuleConfigurationTest extends TestCase { $this->assertSame( 'doctrine_dbal_pdo_sqlite', - $this->moduleConfiguration->getClassConnectionKey(Stores\Jobs\DoctrineDbal\Store::class) + $this->moduleConfiguration->getClassConnectionKey( + Store::class + ) ); } @@ -105,7 +110,7 @@ class ModuleConfigurationTest extends TestCase $this->assertSame( 'doctrine_dbal_pdo_sqlite_slave', $this->moduleConfiguration->getClassConnectionKey( - Trackers\Authentication\DoctrineDbal\Versioned\Tracker::class, + VersionedDataProvider::class, ModuleConfiguration\ConnectionType::SLAVE ) ); @@ -152,7 +157,7 @@ class ModuleConfigurationTest extends TestCase $this->expectException(InvalidConfigurationException::class); $this->moduleConfiguration->getClassConnectionKey( - Stores\Jobs\DoctrineDbal\Store::class, + Store::class, 'invalid' ); } @@ -311,14 +316,14 @@ class ModuleConfigurationTest extends TestCase /** * @throws \Exception */ - public function testThrowsOnInvalidDefaultDataTrackerAndProvider(): void + public function testThrowsOnInvalidDataProvider(): void { $this->expectException(InvalidConfigurationException::class); new ModuleConfiguration( null, [ - ModuleConfiguration::OPTION_DEFAULT_DATA_TRACKER_AND_PROVIDER => 'invalid' + ModuleConfiguration::OPTION_PROVIDER_FOR_ACTIVITY => 'invalid' ] ); } diff --git a/tests/src/Providers/Builders/AuthenticationDataProviderBuilderTest.php b/tests/src/Providers/Builders/AuthenticationDataProviderBuilderTest.php deleted file mode 100644 index 089baaad05d6796f09432839820d49f4fcff0915..0000000000000000000000000000000000000000 --- a/tests/src/Providers/Builders/AuthenticationDataProviderBuilderTest.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Test\Module\accounting\Providers\Builders; - -use PHPUnit\Framework\MockObject\Stub; -use Psr\Log\LoggerInterface; -use SimpleSAML\Module\accounting\Exceptions\Exception; -use SimpleSAML\Module\accounting\ModuleConfiguration; -use SimpleSAML\Module\accounting\Providers\Builders\AuthenticationDataProviderBuilder; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Services\HelpersManager; -use SimpleSAML\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned\Tracker; -use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; - -/** - * @covers \SimpleSAML\Module\accounting\Providers\Builders\AuthenticationDataProviderBuilder - * @uses \SimpleSAML\Module\accounting\Helpers\InstanceBuilderUsingModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Builders\Bases\AbstractStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Bases\DoctrineDbal\AbstractStore - * @uses \SimpleSAML\Module\accounting\Stores\Builders\DataStoreBuilder - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Factory - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store - * @uses \SimpleSAML\Module\accounting\Trackers\Authentication\DoctrineDbal\Versioned\Tracker - * @uses \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator - * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - * @uses \SimpleSAML\Module\accounting\Stores\Bases\AbstractStore - */ -class AuthenticationDataProviderBuilderTest extends TestCase -{ - protected Stub $moduleConfigurationStub; - - protected Stub $loggerStub; - protected HelpersManager $helpersManager; - - protected function setUp(): void - { - $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class); - $connectionParams = ConnectionParameters::DBAL_SQLITE_MEMORY; - $this->moduleConfigurationStub->method('getConnectionParameters') - ->willReturn($connectionParams); - - $this->loggerStub = $this->createStub(LoggerInterface::class); - $this->helpersManager = new HelpersManager(); - } - - public function testCanCreateInstance(): void - { - $this->assertInstanceOf( - AuthenticationDataProviderBuilder::class, - new AuthenticationDataProviderBuilder( - $this->moduleConfigurationStub, - $this->loggerStub, - $this->helpersManager - ) - ); - } - - /** - * @throws Exception - */ - public function testCanBuildDataProvider(): void - { - $builder = new AuthenticationDataProviderBuilder( - $this->moduleConfigurationStub, - $this->loggerStub, - $this->helpersManager - ); - - $this->assertInstanceOf(Tracker::class, $builder->build(Tracker::class)); - } - - public function testThrowsForInvalidClass(): void - { - $this->expectException(Exception::class); - - (new AuthenticationDataProviderBuilder( - $this->moduleConfigurationStub, - $this->loggerStub, - $this->helpersManager - ))->build('invalid'); - } -} diff --git a/tests/src/Services/JobRunnerTest.php b/tests/src/Services/JobRunnerTest.php index 5c748b25b80ce2b3ce06d6c279e8e94c99219624..35b40026840382e2e8f07c85eb0015f973bb8d18 100644 --- a/tests/src/Services/JobRunnerTest.php +++ b/tests/src/Services/JobRunnerTest.php @@ -8,11 +8,14 @@ use DateInterval; use DateTimeImmutable; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\Stub; +use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; use SimpleSAML\Configuration; +use SimpleSAML\Module\accounting\Data\Stores\Builders\JobsStoreBuilder; +use SimpleSAML\Module\accounting\Data\Stores\Interfaces\JobsStoreInterface; +use SimpleSAML\Module\accounting\Data\Trackers\Interfaces\DataTrackerInterface; use SimpleSAML\Module\accounting\Entities\Authentication\Event; -use SimpleSAML\Module\accounting\Entities\Bases\AbstractPayload; use SimpleSAML\Module\accounting\Entities\Interfaces\JobInterface; use SimpleSAML\Module\accounting\Exceptions\Exception; use SimpleSAML\Module\accounting\Exceptions\StoreException; @@ -22,11 +25,7 @@ use SimpleSAML\Module\accounting\Helpers\Random; use SimpleSAML\Module\accounting\ModuleConfiguration; use SimpleSAML\Module\accounting\Services\HelpersManager; use SimpleSAML\Module\accounting\Services\JobRunner; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Builders\JobsStoreBuilder; -use SimpleSAML\Module\accounting\Stores\Interfaces\JobsStoreInterface; -use SimpleSAML\Module\accounting\Trackers\Builders\AuthenticationDataTrackerBuilder; -use SimpleSAML\Module\accounting\Trackers\Interfaces\AuthenticationDataTrackerInterface; +use SimpleSAML\Module\accounting\Services\TrackerResolver; /** * @covers \SimpleSAML\Module\accounting\Services\JobRunner @@ -58,13 +57,13 @@ class JobRunnerTest extends TestCase */ protected $rateLimiterMock; /** - * @var Stub + * @var MockObject */ - protected $authenticationDataTrackerBuilderStub; + protected $trackerResolverMock; /** * @var MockObject */ - protected $authenticationDataTrackerMock; + protected $dataTrackerMock; /** * @var Stub */ @@ -103,8 +102,8 @@ class JobRunnerTest extends TestCase $this->moduleConfigurationStub = $this->createStub(ModuleConfiguration::class); $this->sspConfigurationStub = $this->createStub(Configuration::class); $this->loggerMock = $this->createMock(LoggerInterface::class); - $this->authenticationDataTrackerBuilderStub = $this->createStub(AuthenticationDataTrackerBuilder::class); - $this->authenticationDataTrackerMock = $this->createMock(AuthenticationDataTrackerInterface::class); + $this->trackerResolverMock = $this->createMock(TrackerResolver::class); + $this->dataTrackerMock = $this->createMock(DataTrackerInterface::class); $this->jobsStoreBuilderStub = $this->createStub(JobsStoreBuilder::class); $this->cacheMock = $this->createMock(CacheInterface::class); $this->stateStub = $this->createStub(JobRunner\State::class); @@ -133,7 +132,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -169,7 +168,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -206,7 +205,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -239,7 +238,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -277,7 +276,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -318,7 +317,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -353,7 +352,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -395,7 +394,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -437,7 +436,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -486,7 +485,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -525,7 +524,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -566,7 +565,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -607,7 +606,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -643,14 +642,14 @@ class JobRunnerTest extends TestCase $this->cacheMock->expects($this->once())->method('delete'); $this->loggerMock->expects($this->once())->method('warning') - ->with($this->stringContains('Current job runner ID differs from the ID in the cached state.')); + ->with($this->stringContains('CurrentDataProvider job runner ID differs from the ID in the cached state.')); $jobRunner = new JobRunner( $this->moduleConfigurationStub, $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -694,7 +693,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -739,7 +738,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -794,7 +793,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -846,7 +845,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -864,8 +863,8 @@ class JobRunnerTest extends TestCase { $this->moduleConfigurationStub->method('getAccountingProcessingType') ->willReturn(ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS); - $this->moduleConfigurationStub->method('getDefaultDataTrackerAndProviderClass') - ->willReturn('mock'); + $this->moduleConfigurationStub->method('getProviderClasses') + ->willReturn(['mocks']); $this->randomHelperStub->method('getInt')->willReturn(123); $this->helpersManagerStub->method('getRandom')->willReturn($this->randomHelperStub); @@ -889,10 +888,8 @@ class JobRunnerTest extends TestCase $this->rateLimiterMock->expects($this->once())->method('resetBackoffPause'); - $this->authenticationDataTrackerMock->expects($this->once()) - ->method('process'); - $this->authenticationDataTrackerBuilderStub->method('build') - ->willReturn($this->authenticationDataTrackerMock); + $this->dataTrackerMock->expects($this->once())->method('process'); + $this->trackerResolverMock->method('fromModuleConfiguration')->willReturn([$this->dataTrackerMock]); $this->jobStub->method('getPayload')->willReturn($this->payloadStub); $this->jobsStoreMock->method('dequeue')->willReturn($this->jobStub); @@ -914,7 +911,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -931,8 +928,8 @@ class JobRunnerTest extends TestCase { $this->moduleConfigurationStub->method('getAccountingProcessingType') ->willReturn(ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS); - $this->moduleConfigurationStub->method('getDefaultDataTrackerAndProviderClass') - ->willReturn('mock'); + $this->moduleConfigurationStub->method('getProviderClasses') + ->willReturn(['mocks']); $this->randomHelperStub->method('getInt')->willReturn(123); $this->helpersManagerStub->method('getRandom')->willReturn($this->randomHelperStub); @@ -975,7 +972,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -993,8 +990,8 @@ class JobRunnerTest extends TestCase { $this->moduleConfigurationStub->method('getAccountingProcessingType') ->willReturn(ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS); - $this->moduleConfigurationStub->method('getDefaultDataTrackerAndProviderClass') - ->willReturn('mock'); + $this->moduleConfigurationStub->method('getProviderClasses') + ->willReturn(['mocks']); $this->moduleConfigurationStub->method('getJobRunnerShouldPauseAfterNumberOfJobsProcessed') ->willReturn(0); @@ -1022,10 +1019,9 @@ class JobRunnerTest extends TestCase $this->rateLimiterMock->expects($this->exactly(2))->method('resetBackoffPause'); $this->rateLimiterMock->expects($this->once())->method('doPause'); - $this->authenticationDataTrackerMock->expects($this->exactly(2)) + $this->dataTrackerMock->expects($this->exactly(2)) ->method('process'); - $this->authenticationDataTrackerBuilderStub->method('build') - ->willReturn($this->authenticationDataTrackerMock); + $this->trackerResolverMock->method('fromModuleConfiguration')->willReturn([$this->dataTrackerMock]); $this->jobStub->method('getPayload')->willReturn($this->payloadStub); $this->jobsStoreMock->method('dequeue')->willReturn($this->jobStub); @@ -1048,7 +1044,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -1066,8 +1062,8 @@ class JobRunnerTest extends TestCase { $this->moduleConfigurationStub->method('getAccountingProcessingType') ->willReturn(ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS); - $this->moduleConfigurationStub->method('getDefaultDataTrackerAndProviderClass') - ->willReturn('mock'); + $this->moduleConfigurationStub->method('getProviderClasses') + ->willReturn(['mocks']); $this->randomHelperStub->method('getInt')->willReturn(123); $this->helpersManagerStub->method('getRandom')->willReturn($this->randomHelperStub); @@ -1091,11 +1087,10 @@ class JobRunnerTest extends TestCase $this->rateLimiterMock->expects($this->once())->method('resetBackoffPause'); - $this->authenticationDataTrackerMock->expects($this->once()) + $this->dataTrackerMock->expects($this->once()) ->method('process') ->willThrowException(new Exception('test')); - $this->authenticationDataTrackerBuilderStub->method('build') - ->willReturn($this->authenticationDataTrackerMock); + $this->trackerResolverMock->method('fromModuleConfiguration')->willReturn([$this->dataTrackerMock]); $this->jobStub->method('getPayload')->willReturn($this->payloadStub); $this->jobsStoreMock->method('dequeue')->willReturn($this->jobStub); @@ -1115,7 +1110,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, @@ -1132,8 +1127,8 @@ class JobRunnerTest extends TestCase { $this->moduleConfigurationStub->method('getAccountingProcessingType') ->willReturn(ModuleConfiguration\AccountingProcessingType::VALUE_ASYNCHRONOUS); - $this->moduleConfigurationStub->method('getDefaultDataTrackerAndProviderClass') - ->willReturn('mock'); + $this->moduleConfigurationStub->method('getProviderClasses') + ->willReturn(['mocks']); $this->randomHelperStub->method('getInt')->willReturn(123); $this->helpersManagerStub->method('getRandom')->willReturn($this->randomHelperStub); @@ -1162,7 +1157,7 @@ class JobRunnerTest extends TestCase $this->sspConfigurationStub, $this->loggerMock, $this->helpersManagerStub, - $this->authenticationDataTrackerBuilderStub, + $this->trackerResolverMock, $this->jobsStoreBuilderStub, $this->cacheMock, $this->stateStub, diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTableTest.php b/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTableTest.php deleted file mode 100644 index 1c18391ef1039a89407fde3da8ed1edcb54f5cb4..0000000000000000000000000000000000000000 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/Migrations/Version20220801000600CreateIdpSpUserVersionTableTest.php +++ /dev/null @@ -1,107 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; - -use Doctrine\DBAL\Exception; -use Doctrine\DBAL\Schema\AbstractSchemaManager; -use PHPUnit\Framework\MockObject\Stub; -use SimpleSAML\Module\accounting\Exceptions\StoreException; -use SimpleSAML\Module\accounting\Exceptions\StoreException\MigrationException; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations; -use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; - -/** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000600CreateIdpSpUserVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection * - */ -class Version20220801000600CreateIdpSpUserVersionTableTest 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_idp_sp_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\Version20220801000600CreateIdpSpUserVersionTable($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\Version20220801000600CreateIdpSpUserVersionTable($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\Version20220801000600CreateIdpSpUserVersionTable($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 Migrations\Version20220801000600CreateIdpSpUserVersionTable($this->connectionStub); - $this->expectException(MigrationException::class); - $migration->run(); - } -} diff --git a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RepositoryTest.php b/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RepositoryTest.php deleted file mode 100644 index bf93c305831bd41442b76800c0f088688481c0ba..0000000000000000000000000000000000000000 --- a/tests/src/Stores/Data/Authentication/DoctrineDbal/Versioned/Store/RepositoryTest.php +++ /dev/null @@ -1,849 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace SimpleSAML\Test\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; - -use DateInterval; -use DateTimeImmutable; -use Exception; -use PHPUnit\Framework\MockObject\Stub; -use Psr\Log\LoggerInterface; -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\Module\accounting\Stores\Connections\Bases\AbstractMigrator; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection; -use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store; -use SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository; -use PHPUnit\Framework\TestCase; -use SimpleSAML\Test\Module\accounting\Constants\ConnectionParameters; -use SimpleSAML\Test\Module\accounting\Constants\DateTime; - -/** - * @covers \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Repository - * @uses \SimpleSAML\Module\accounting\Helpers\Filesystem - * @uses \SimpleSAML\Module\accounting\ModuleConfiguration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection - * @uses \SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Migrator - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000000CreateIdpTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000100CreateIdpVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000200CreateSpTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000300CreateSpVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000400CreateUserTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000500CreateUserVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000600CreateIdpSpUserVersionTable - * @uses \SimpleSAML\Module\accounting\Stores\Data\Authentication\DoctrineDbal\Versioned\Store\Migrations\Version20220801000700CreateAuthenticationEventTable - * @uses \SimpleSAML\Module\accounting\Services\HelpersManager - */ -class RepositoryTest extends TestCase -{ - protected Connection $connection; - protected \Doctrine\DBAL\Connection $dbal; - /** - * @var Stub - */ - protected $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; - /** - * @var Stub - */ - protected $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 . 'Stores' . - DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR . 'Authentication' . DIRECTORY_SEPARATOR . - 'DoctrineDbal' . DIRECTORY_SEPARATOR . 'Versioned' . 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->createdAt); - - $result = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative(); - - $this->assertSame($this->idpEntityId, $result[Store\TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID]); - $this->assertSame( - $this->idpEntityIdHash, - $result[Store\TableConstants::TABLE_IDP_COLUMN_NAME_ENTITY_ID_HASH_SHA256] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\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->createdAt); - $this->repository->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $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); - } - - /** - * @depends testCanInsertAndGetIdp - * @throws StoreException - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanInsertAndGetIdpVersion(array $idpResult): array - { - $idpId = (int)$idpResult[Store\TableConstants::TABLE_IDP_COLUMN_NAME_ID]; - - $this->repository->insertIdpVersion($idpId, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt); - - $result = $this->repository->getIdpVersion($idpId, $this->idpMetadataHash)->fetchAssociative(); - - $this->assertSame($this->idpMetadata, $result[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_METADATA]); - $this->assertSame( - $this->idpMetadataHash, - $result[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_METADATA_HASH_SHA256] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_CREATED_AT] - ); - - return $result; - } - - public function testInsertIdpVersionThrowsOnNonUniqueIdpMetadataHash(): void - { - $this->expectException(StoreException::class); - // IdP Metadata Hash must be unique. - $this->repository->insertIdpVersion(1, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt); - $this->repository->insertIdpVersion(1, $this->idpMetadata, $this->idpMetadataHash, $this->createdAt); - } - - public function testGetIdpVersionThrowsOnInvalidDbal(): void - { - $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); - $repository = new Repository($this->connectionStub, $this->loggerStub); - $this->expectException(StoreException::class); - - $repository->getIdpVersion(1, $this->idpMetadataHash); - } - - /** - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanInsertAndGetSp(): array - { - $this->repository->insertSp($this->spEntityId, $this->spEntityIdHash, $this->createdAt); - - $result = $this->repository->getSp($this->spEntityIdHash)->fetchAssociative(); - - $this->assertSame($this->spEntityId, $result[Store\TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID]); - $this->assertSame( - $this->spEntityIdHash, - $result[Store\TableConstants::TABLE_SP_COLUMN_NAME_ENTITY_ID_HASH_SHA256] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\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->createdAt); - $this->repository->insertSp($this->spEntityId, $this->spEntityIdHash, $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); - } - - /** - * @depends testCanInsertAndGetSp - * @throws StoreException - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanInsertAndGetSpVersion(array $spResult): array - { - $spId = (int)$spResult[Store\TableConstants::TABLE_SP_COLUMN_NAME_ID]; - - $this->repository->insertSpVersion($spId, $this->spMetadata, $this->spMetadataHash, $this->createdAt); - - $result = $this->repository->getSpVersion($spId, $this->spMetadataHash)->fetchAssociative(); - - $this->assertSame($this->spMetadata, $result[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA]); - $this->assertSame( - $this->spMetadataHash, - $result[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_METADATA_HASH_SHA256] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_CREATED_AT] - ); - - return $result; - } - - public function testInsertSpVersionThrowsOnNonUniqueMetadataHash(): void - { - $this->expectException(StoreException::class); - // SP metadata hash must be unique. - $this->repository->insertSpVersion(1, $this->spMetadata, $this->spMetadataHash, $this->createdAt); - $this->repository->insertSpVersion(1, $this->spMetadata, $this->spMetadataHash, $this->createdAt); - } - - public function testGetSpVersionThrowsOnInvalidDbal(): void - { - $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); - $repository = new Repository($this->connectionStub, $this->loggerStub); - $this->expectException(StoreException::class); - - $repository->getSpVersion(1, $this->spMetadataHash); - } - - /** - * @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[Store\TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER]); - $this->assertSame( - $this->userIdentifierHash, - $result[Store\TableConstants::TABLE_USER_COLUMN_NAME_IDENTIFIER_HASH_SHA256] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\TableConstants::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); - } - - /** - * @depends testCanInsertAndGetUser - * @throws StoreException - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanInsertAndGetUserVersion(array $userResult): array - { - $userId = (int)$userResult[Store\TableConstants::TABLE_USER_COLUMN_NAME_ID]; - - $this->repository - ->insertUserVersion($userId, $this->userAttributes, $this->userAttributesHash, $this->createdAt); - - $result = $this->repository->getUserVersion($userId, $this->userAttributesHash)->fetchAssociative(); - - $this->assertSame( - $this->userAttributes, - $result[Store\TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES] - ); - $this->assertSame( - $this->userAttributesHash, - $result[Store\TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ATTRIBUTES_HASH_SHA256] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\TableConstants::TABLE_USER_VERSION_COLUMN_NAME_CREATED_AT] - ); - - return $result; - } - - public function testInsertUserVersionThrowsOnNonUniqueAttributesHash(): void - { - $this->expectException(StoreException::class); - $this->repository - ->insertUserVersion(1, $this->userAttributes, $this->userAttributesHash, $this->createdAt); - $this->repository - ->insertUserVersion(1, $this->userAttributes, $this->userAttributesHash, $this->createdAt); - } - - public function testGetUserVersionThrowsOnInvalidDbal(): void - { - $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); - $repository = new Repository($this->connectionStub, $this->loggerStub); - $this->expectException(StoreException::class); - - $repository->getUserVersion(1, $this->userIdentifierHash); - } - - /** - * @depends testCanInsertAndGetIdpVersion - * @depends testCanInsertAndGetSpVersion - * @depends testCanInsertAndGetUserVersion - * @throws StoreException - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanInsertAndGetIdpSpUserVersion( - array $idpVersionResult, - array $spVersionResult, - array $userVersionResult - ): array { - $idpVersionId = (int)$idpVersionResult[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_ID]; - $spVersionId = (int)$spVersionResult[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID]; - $userVersionId = (int)$userVersionResult[Store\TableConstants::TABLE_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId, $this->createdAt); - $result = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId) - ->fetchAssociative(); - - $this->assertSame( - $idpVersionId, - (int)$result[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_IDP_VERSION_ID] - ); - $this->assertSame( - $spVersionId, - (int)$result[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID] - ); - $this->assertSame( - $userVersionId, - (int)$result[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_SP_VERSION_ID] - ); - $this->assertSame( - $this->createdAt->format($this->dateTimeFormat), - $result[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_CREATED_AT] - ); - - return $result; - } - - public function testInsertIdpSpUserVersionThrowsOnNonUnique(): void - { - $this->expectException(StoreException::class); - $this->repository->insertIdpSpUserVersion(1, 1, 1, $this->createdAt); - $this->repository->insertIdpSpUserVersion(1, 1, 1, $this->createdAt); - } - - 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); - } - - /** - * @depends testCanInsertAndGetIdpSpUserVersion - * @throws \Doctrine\DBAL\Exception - * @throws \Doctrine\DBAL\Exception - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - * @throws \Doctrine\DBAL\Exception - */ - public function testCanInsertAuthenticationEvent(array $idpSpUserVersionResult): void - { - $idpSpUserVersionId = - (int)$idpSpUserVersionResult[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - $createdAt = $happenedAt = new DateTimeImmutable(); - - $authenticationEventCounterQueryBuilder = $this->connection->dbal()->createQueryBuilder(); - $authenticationEventCounterQueryBuilder->select('COUNT(id) as authenticationEventCount') - ->from( - Store\TableConstants::TABLE_PREFIX . - $this->connection - ->preparePrefixedTableName(Store\TableConstants::TABLE_NAME_AUTHENTICATION_EVENT) - ); - - $this->assertSame(0, (int)$authenticationEventCounterQueryBuilder->executeQuery()->fetchOne()); - - $this->repository->insertAuthenticationEvent($idpSpUserVersionId, $happenedAt, null, null, $createdAt); - - $this->assertSame(1, (int)$authenticationEventCounterQueryBuilder->executeQuery()->fetchOne()); - } - - 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, $this->createdAt); - } - - /** - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanGetConnectedServiceProviders(): void - { - $this->repository->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $this->createdAt); - $idpResult = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative(); - $idpId = (int)$idpResult[Store\TableConstants::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[Store\TableConstants::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[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_ID]; - $spVersionId = (int)$spVersionResult[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID]; - $userVersionId = (int)$userVersionResult[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $this->createdAt, - $this->clientIpAddress, - $this->authenticationProtocolDesignation, - $this->createdAt - ); - - $resultArray = $this->repository->getConnectedServiceProviders($this->userIdentifierHash); - - $this->assertCount(1, $resultArray); - $this->assertEquals( - '1', - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] - ); - $this->assertSame( - $this->spMetadata, - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] - ); - $this->assertSame( - $this->userAttributes, - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES] - ); - - $resultArray = $this->repository->getConnectedServiceProviders($this->userIdentifierHash); - $this->assertCount(1, $resultArray); - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $this->createdAt, - $this->clientIpAddress, - $this->authenticationProtocolDesignation, - $this->createdAt - ); - $resultArray = $this->repository->getConnectedServiceProviders($this->userIdentifierHash); - $this->assertCount(1, $resultArray); - $this->assertEquals( - '2', - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] - ); - $this->assertSame( - $this->spMetadata, - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] - ); - $this->assertSame( - $this->userAttributes, - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_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, $this->createdAt); - $spResult = $this->repository->getSp($spEntityIdHashNew)->fetchAssociative(); - $spId = (int)$spResult[Store\TableConstants::TABLE_SP_COLUMN_NAME_ID]; - $this->repository->insertSpVersion($spId, $spMetadataNew, $spMetadataHashNew, $this->createdAt); - $spVersionResult = $this->repository->getSpVersion($spId, $spMetadataHashNew)->fetchAssociative(); - $spVersionId = (int)$spVersionResult[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId, $this->createdAt); - $idpSpUserVersionResult = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId) - ->fetchAssociative(); - $idpSpUserVersionId = - (int)$idpSpUserVersionResult[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $this->createdAt, - $this->clientIpAddress, - $this->authenticationProtocolDesignation, - $this->createdAt - ); - - $resultArray = $this->repository->getConnectedServiceProviders($this->userIdentifierHash); - $this->assertCount(2, $resultArray); - $this->assertEquals( - '1', - $resultArray[$spEntityIdNew] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] - ); - $this->assertSame( - $spMetadataNew, - $resultArray[$spEntityIdNew] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] - ); - $this->assertSame( - $this->userAttributes, - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_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[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $this->createdAt, - $this->clientIpAddress, - $this->authenticationProtocolDesignation, - $this->createdAt - ); - $resultArray = $this->repository->getConnectedServiceProviders($this->userIdentifierHash); - - $this->assertCount(2, $resultArray); - $this->assertEquals( - '2', - $resultArray[$spEntityIdNew] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_NUMBER_OF_AUTHENTICATIONS] - ); - $this->assertSame( - $spMetadataNew, - $resultArray[$spEntityIdNew] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_SP_METADATA] - ); - // New SP with new user attributes version.. - $this->assertSame( - $userAttributesNew, - $resultArray[$spEntityIdNew] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES] - ); - - // First SP still has old user attributes version... - $this->assertSame( - $this->userAttributes, - $resultArray[$this->spEntityId] - [Store\TableConstants::ENTITY_CONNECTED_ORGANIZATION_COLUMN_NAME_USER_ATTRIBUTES] - ); - } - - public function testGetConnectedServiceProvidersThrowsOnInvalidDbal(): void - { - $this->connectionStub->method('dbal')->willThrowException(new Exception('test')); - $repository = new Repository($this->connectionStub, $this->loggerStub); - $this->expectException(StoreException::class); - - $repository->getConnectedServiceProviders($this->userIdentifierHash); - } - - /** - * @throws StoreException - * @throws \Doctrine\DBAL\Exception - */ - public function testCanGetActivity(): void - { - $this->repository->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $this->createdAt); - $idpResult = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative(); - $idpId = (int)$idpResult[Store\TableConstants::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[Store\TableConstants::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[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_ID]; - $spVersionId = (int)$spVersionResult[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID]; - $userVersionId = (int)$userVersionResult[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $this->createdAt, - $this->clientIpAddress, - $this->authenticationProtocolDesignation, - $this->createdAt - ); - - $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0); - $this->assertCount(1, $resultArray); - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $this->createdAt, - $this->clientIpAddress, - $this->authenticationProtocolDesignation, - $this->createdAt - ); - $resultArray = $this->repository->getActivity($this->userIdentifierHash, 10, 0); - $this->assertCount(2, $resultArray); - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $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, $this->createdAt); - $spResult = $this->repository->getSp($spEntityIdHashNew)->fetchAssociative(); - $spId = (int)$spResult[Store\TableConstants::TABLE_SP_COLUMN_NAME_ID]; - $this->repository->insertSpVersion($spId, $spMetadataNew, $spMetadataHashNew, $this->createdAt); - $spVersionResult = $this->repository->getSpVersion($spId, $spMetadataHashNew)->fetchAssociative(); - $spVersionId = (int)$spVersionResult[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId, $this->createdAt); - $idpSpUserVersionResult = $this->repository->getIdpSpUserVersion($idpVersionId, $spVersionId, $userVersionId) - ->fetchAssociative(); - - $idpSpUserVersionId = - (int)$idpSpUserVersionResult[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $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->insertIdp($this->idpEntityId, $this->idpEntityIdHash, $this->createdAt); - $idpResult = $this->repository->getIdp($this->idpEntityIdHash)->fetchAssociative(); - $idpId = (int)$idpResult[Store\TableConstants::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[Store\TableConstants::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[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_VERSION_COLUMN_NAME_ID]; - $spVersionId = (int)$spVersionResult[Store\TableConstants::TABLE_SP_VERSION_COLUMN_NAME_ID]; - $userVersionId = (int)$userVersionResult[Store\TableConstants::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[Store\TableConstants::TABLE_IDP_SP_USER_VERSION_COLUMN_NAME_ID]; - - $this->repository->insertAuthenticationEvent( - $idpSpUserVersionId, - $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()); - } -}