Skip to content
Snippets Groups Projects
Unverified Commit 86faf7ea authored by Marko Ivancic's avatar Marko Ivancic Committed by GitHub
Browse files

Add Doctrine DBAL JobsStore migrations (#1)

parent 17b33f6c
No related branches found
No related tags found
1 merge request!1Relate histories
Showing
with 1052 additions and 39 deletions
/.idea/ /.idea/
/vendor/ /vendor/
/build/ /build/
\ No newline at end of file .phpunit.result.cache
\ No newline at end of file
# simplesamlphp-module-accounting # simplesamlphp-module-accounting
SimpleSAMLphp module providing user accounting functionality SimpleSAMLphp module providing user accounting functionality
## TODO
- [ ] MySQL store
- [ ] MySQL Job Store
- [ ] Cron hooks
\ No newline at end of file
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
"ext-pdo": "*", "ext-pdo": "*",
"ext-pdo_mysql": "*", "ext-pdo_mysql": "*",
"ext-pdo_sqlite": "*", "ext-pdo_sqlite": "*",
"doctrine/dbal": "^3",
"simplesamlphp/composer-module-installer": "^1" "simplesamlphp/composer-module-installer": "^1"
}, },
"require-dev": { "require-dev": {
...@@ -37,5 +38,12 @@ ...@@ -37,5 +38,12 @@
"squizlabs/php_codesniffer": "^3", "squizlabs/php_codesniffer": "^3",
"simplesamlphp/simplesamlphp": "^2@beta", "simplesamlphp/simplesamlphp": "^2@beta",
"simplesamlphp/simplesamlphp-test-framework": "^1" "simplesamlphp/simplesamlphp-test-framework": "^1"
},
"scripts": {
"pre-commit": [
"vendor/bin/phpcs -p",
"vendor/bin/psalm",
"vendor/bin/phpunit"
]
} }
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "1de774adafcb72fec83aa2a75d809c3f", "content-hash": "c3bebdab1f5cd01e6c038f6defc7bdbe",
"packages": [ "packages": [
{ {
"name": "composer/ca-bundle", "name": "composer/ca-bundle",
...@@ -559,6 +559,347 @@ ...@@ -559,6 +559,347 @@
], ],
"time": "2022-02-25T21:32:43+00:00" "time": "2022-02-25T21:32:43+00:00"
}, },
{
"name": "doctrine/cache",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb",
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb",
"shasum": ""
},
"require": {
"php": "~7.1 || ^8.0"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
"cache/integration-tests": "dev-master",
"doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"symfony/cache": "^4.4 || ^5.4 || ^6",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
"homepage": "https://www.doctrine-project.org/projects/cache.html",
"keywords": [
"abstraction",
"apcu",
"cache",
"caching",
"couchdb",
"memcached",
"php",
"redis",
"xcache"
],
"support": {
"issues": "https://github.com/doctrine/cache/issues",
"source": "https://github.com/doctrine/cache/tree/2.2.0"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
"type": "tidelift"
}
],
"time": "2022-05-20T20:07:39+00:00"
},
{
"name": "doctrine/dbal",
"version": "3.3.6",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/9e7f76dd1cde81c62574fdffa5a9c655c847ad21",
"reference": "9e7f76dd1cde81c62574fdffa5a9c655c847ad21",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3|^1",
"doctrine/event-manager": "^1.0",
"php": "^7.3 || ^8.0",
"psr/cache": "^1|^2|^3",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/coding-standard": "9.0.0",
"jetbrains/phpstorm-stubs": "2022.1",
"phpstan/phpstan": "1.6.3",
"phpstan/phpstan-strict-rules": "^1.2",
"phpunit/phpunit": "9.5.20",
"psalm/plugin-phpunit": "0.16.1",
"squizlabs/php_codesniffer": "3.6.2",
"symfony/cache": "^5.2|^6.0",
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
"vimeo/psalm": "4.23.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"bin": [
"bin/doctrine-dbal"
],
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
}
],
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
"keywords": [
"abstraction",
"database",
"db2",
"dbal",
"mariadb",
"mssql",
"mysql",
"oci8",
"oracle",
"pdo",
"pgsql",
"postgresql",
"queryobject",
"sasql",
"sql",
"sqlite",
"sqlserver",
"sqlsrv"
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.3.6"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
"type": "tidelift"
}
],
"time": "2022-05-02T17:21:01+00:00"
},
{
"name": "doctrine/deprecations",
"version": "v1.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
},
"time": "2022-05-02T15:47:09+00:00"
},
{
"name": "doctrine/event-manager",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/event-manager.git",
"reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f",
"reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"doctrine/common": "<2.9@dev"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
"phpunit/phpunit": "^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "lib/Doctrine/Common"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
"keywords": [
"event",
"event dispatcher",
"event manager",
"event system",
"events"
],
"support": {
"issues": "https://github.com/doctrine/event-manager/issues",
"source": "https://github.com/doctrine/event-manager/tree/1.1.x"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
"type": "tidelift"
}
],
"time": "2020-05-29T18:28:51+00:00"
},
{ {
"name": "gettext/gettext", "name": "gettext/gettext",
"version": "v5.6.1", "version": "v5.6.1",
...@@ -853,16 +1194,16 @@ ...@@ -853,16 +1194,16 @@
}, },
{ {
"name": "phpmailer/phpmailer", "name": "phpmailer/phpmailer",
"version": "v6.6.0", "version": "v6.6.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git", "url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1" "reference": "b52ed06864fdda81b82ec8bf564cf15d45ed4f95"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/e43bac82edc26ca04b36143a48bde1c051cfd5b1", "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/b52ed06864fdda81b82ec8bf564cf15d45ed4f95",
"reference": "e43bac82edc26ca04b36143a48bde1c051cfd5b1", "reference": "b52ed06864fdda81b82ec8bf564cf15d45ed4f95",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
...@@ -874,8 +1215,8 @@ ...@@ -874,8 +1215,8 @@
"require-dev": { "require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.2", "doctrine/annotations": "^1.2",
"php-parallel-lint/php-console-highlighter": "^0.5.0", "php-parallel-lint/php-console-highlighter": "^1.0.0",
"php-parallel-lint/php-parallel-lint": "^1.3.1", "php-parallel-lint/php-parallel-lint": "^1.3.2",
"phpcompatibility/php-compatibility": "^9.3.5", "phpcompatibility/php-compatibility": "^9.3.5",
"roave/security-advisories": "dev-latest", "roave/security-advisories": "dev-latest",
"squizlabs/php_codesniffer": "^3.6.2", "squizlabs/php_codesniffer": "^3.6.2",
...@@ -919,7 +1260,7 @@ ...@@ -919,7 +1260,7 @@
"description": "PHPMailer is a full-featured email creation and transfer class for PHP", "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"support": { "support": {
"issues": "https://github.com/PHPMailer/PHPMailer/issues", "issues": "https://github.com/PHPMailer/PHPMailer/issues",
"source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.0" "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.2"
}, },
"funding": [ "funding": [
{ {
...@@ -927,7 +1268,7 @@ ...@@ -927,7 +1268,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-02-28T15:31:21+00:00" "time": "2022-06-14T09:27:21+00:00"
}, },
{ {
"name": "psr/cache", "name": "psr/cache",
...@@ -1450,16 +1791,16 @@ ...@@ -1450,16 +1791,16 @@
}, },
{ {
"name": "simplesamlphp/saml2", "name": "simplesamlphp/saml2",
"version": "v4.6.1", "version": "v4.6.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/simplesamlphp/saml2.git", "url": "https://github.com/simplesamlphp/saml2.git",
"reference": "5e46819fdb76657f13e05a8f264d06efd9163c3d" "reference": "bfc9c79dd6b728a41d1de988f545f6e64728a51d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/simplesamlphp/saml2/zipball/5e46819fdb76657f13e05a8f264d06efd9163c3d", "url": "https://api.github.com/repos/simplesamlphp/saml2/zipball/bfc9c79dd6b728a41d1de988f545f6e64728a51d",
"reference": "5e46819fdb76657f13e05a8f264d06efd9163c3d", "reference": "bfc9c79dd6b728a41d1de988f545f6e64728a51d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
...@@ -1467,7 +1808,7 @@ ...@@ -1467,7 +1808,7 @@
"ext-openssl": "*", "ext-openssl": "*",
"ext-zlib": "*", "ext-zlib": "*",
"php": ">=7.1 || ^8.0", "php": ">=7.1 || ^8.0",
"psr/log": "~1.1", "psr/log": "~1.1 || ^2.0 || ^3.0",
"robrichards/xmlseclibs": "^3.1.1", "robrichards/xmlseclibs": "^3.1.1",
"webmozart/assert": "^1.9" "webmozart/assert": "^1.9"
}, },
...@@ -1502,9 +1843,9 @@ ...@@ -1502,9 +1843,9 @@
"description": "SAML2 PHP library from SimpleSAMLphp", "description": "SAML2 PHP library from SimpleSAMLphp",
"support": { "support": {
"issues": "https://github.com/simplesamlphp/saml2/issues", "issues": "https://github.com/simplesamlphp/saml2/issues",
"source": "https://github.com/simplesamlphp/saml2/tree/v4.6.1" "source": "https://github.com/simplesamlphp/saml2/tree/v4.6.3"
}, },
"time": "2022-05-23T20:49:55+00:00" "time": "2022-06-13T14:04:10+00:00"
}, },
{ {
"name": "simplesamlphp/simplesamlphp", "name": "simplesamlphp/simplesamlphp",
...@@ -6899,16 +7240,16 @@ ...@@ -6899,16 +7240,16 @@
}, },
{ {
"name": "squizlabs/php_codesniffer", "name": "squizlabs/php_codesniffer",
"version": "3.6.2", "version": "3.7.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
"reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" "reference": "a2cd51b45bcaef9c1f2a4bda48f2dd2fa2b95563"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/a2cd51b45bcaef9c1f2a4bda48f2dd2fa2b95563",
"reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", "reference": "a2cd51b45bcaef9c1f2a4bda48f2dd2fa2b95563",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
...@@ -6951,7 +7292,7 @@ ...@@ -6951,7 +7292,7 @@
"source": "https://github.com/squizlabs/PHP_CodeSniffer", "source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
}, },
"time": "2021-12-12T21:44:58+00:00" "time": "2022-06-13T06:31:38+00:00"
}, },
{ {
"name": "symfony/phpunit-bridge", "name": "symfony/phpunit-bridge",
......
<?php <?php
declare(strict_types=1);
use SimpleSAML\Module\accounting\ModuleConfiguration;
use SimpleSAML\Module\accounting\Stores;
$config = [ $config = [
'test-config' => 'test-value', /**
* User ID attribute, one that is always available and that is unique to all users.
* If this attribute is not available, accounting will not be performed for that user.
*
* Examples:
* urn:oasis:names:tc:SAML:attribute:subject-id
* eduPersonUniqueId
* eduPersonPrincipalName
*/
ModuleConfiguration::OPTION_USER_ID_ATTRIBUTE => 'urn:oasis:names:tc:SAML:attribute:subject-id',
/**
* Accounting processing type. There are two possible types: 'synchronous' and 'asynchronous'.
* - 'synchronous': accounting processing will be performed during authentication itself (slower)
* - 'asynchronous': for each authentication event a new job will be created for later processing (faster,
* but requires setting up job storage and a cron entry).
*/
ModuleConfiguration::OPTION_ACCOUNTING_PROCESSING_TYPE =>
ModuleConfiguration\AccountingProcessingType::VALUE_SYNCHRONOUS,
/**
* Jobs store. Determines which of the available stores will be used to store jobs in case the 'asynchronous'
* accounting processing type was set.
*/
ModuleConfiguration::OPTION_JOBS_STORE => Stores\Jobs\DoctrineDbal\JobsStore::class,
/**
* Store connection for particular store. Can be used to set different connections for different stores.
*/
ModuleConfiguration::OPTION_STORE_TO_CONNECTION_KEY_MAP => [
Stores\Jobs\DoctrineDbal\JobsStore::class => 'doctrine_dbal_pdo_mysql',
],
/**
* Store connections and their parameters.
*
* Any compatible Doctrine DBAL implementation can be used:
* https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html
* There are additional parameters for: table prefix.
* Examples for mysql and sqlite are provided below.
*/
ModuleConfiguration::OPTION_ALL_STORE_CONNECTIONS_AND_PARAMETERS => [
'doctrine_dbal_pdo_mysql' => [
'driver' => 'pdo_mysql', // (string): The built-in driver implementation to use
'user' => 'user', // (string): Username to use when connecting to the database.
'password' => 'password', // (string): Password to use when connecting to the database.
'host' => 'host', // (string): Hostname of the database to connect to.
'port' => 3306, // (integer): Port of the database to connect to.
'dbname' => 'dbname', // (string): Name of the database/schema to connect to.
//'unix_socket' => 'unix_socet', // (string): Name of the socket used to connect to the database.
'charset' => 'utf8', // (string): The charset used when connecting to the database.
//'url' => 'mysql://user:secret@localhost/mydb?charset=utf8', // ...alternative way of providing parameters.
// Additional parameters not originaly avaliable in Doctrine DBAL
'table_prefix' => '', // (string): Prefix for each table.
],
'doctrine_dbal_pdo_sqlite' => [
'driver' => 'pdo_sqlite', // (string): The built-in driver implementation to use
'path' => '/path/to/db.sqlite', // (string): The filesystem path to the database file.
// Mutually exclusive with memory. path takes precedence.
'memory' => false, // (boolean): True if the SQLite database should be in-memory (non-persistent).
// Mutually exclusive with path. path takes precedence.
//'url' => 'sqlite:////path/to/db.sqlite // ...alternative way of providing path parameter.
//'url' => 'sqlite:///:memory:' // ...alternative way of providing memory parameter.
// Additional parameters not originaly avaliable in Doctrine DBAL
'table_prefix' => '', // (string): Prefix for each table.
],
],
]; ];
<?php <?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Controller; namespace SimpleSAML\Module\accounting\Controller;
use SimpleSAML\Configuration; use SimpleSAML\Configuration;
......
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Exceptions;
class InvalidConfigurationException extends \ValueError
{
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Exceptions;
class InvalidValueException extends \ValueError
{
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Exceptions;
class MigrationException extends \Exception
{
}
<?php
namespace SimpleSAML\Module\accounting\Exceptions\ModuleConfiguration;
class InvalidConfigurationNameException extends \InvalidArgumentException
{
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Helpers;
use SimpleSAML\Module\accounting\Exceptions\InvalidValueException;
class FilesystemHelper
{
public static function getRealPath(string $path): string
{
$path = realpath($path);
if ($path === false || ! (is_dir($path) || is_file($path))) {
throw new InvalidValueException('Given path can not be translated to real path.');
}
return $path;
}
}
<?php <?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting; namespace SimpleSAML\Module\accounting;
use Exception;
use SimpleSAML\Configuration; use SimpleSAML\Configuration;
use SimpleSAML\Module\accounting\Exceptions\ModuleConfiguration\InvalidConfigurationNameException; use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException;
class ModuleConfiguration class ModuleConfiguration
{ {
protected Configuration $configuration; /**
* Default file name for module configuration. Can be overridden, for example, for testing purposes.
*/
public const FILE_NAME = 'module_accounting.php'; public const FILE_NAME = 'module_accounting.php';
public const OPTION_USER_ID_ATTRIBUTE = 'user_id_attribute';
public const OPTION_ACCOUNTING_PROCESSING_TYPE = 'accounting_processing_type';
public const OPTION_JOBS_STORE = 'jobs_store';
public const OPTION_ALL_STORE_CONNECTIONS_AND_PARAMETERS = 'all_store_connection_and_parameters';
public const OPTION_STORE_TO_CONNECTION_KEY_MAP = 'store_to_connection_key_map';
/** /**
* @throws \Exception * Contains configuration from module configuration file.
*/
protected Configuration $configuration;
/**
* @throws Exception
*/ */
public function __construct(string $fileName = null) public function __construct(string $fileName = null)
{ {
...@@ -22,23 +37,75 @@ class ModuleConfiguration ...@@ -22,23 +37,75 @@ class ModuleConfiguration
} }
/** /**
* @param string $name * Get configuration option from module configuration file.
*
* @param string $option
* @return mixed * @return mixed
*/ */
public function getValue(string $name) public function get(string $option)
{ {
if (! $this->configuration->hasValue($name)) { if (! $this->configuration->hasValue($option)) {
throw new InvalidConfigurationNameException(sprintf('Config name does not exist (%s).', $name)); throw new InvalidConfigurationException(
sprintf('Configuration option does not exist (%s).', $option)
);
} }
return $this->configuration->getValue($name); return $this->configuration->getValue($option);
} }
/** /**
* Get underlying SimpleSAMLphp Configuration instance.
*
* @return Configuration * @return Configuration
*/ */
public function getConfiguration(): Configuration public function getConfiguration(): Configuration
{ {
return $this->configuration; return $this->configuration;
} }
public function getStoreConnection(string $store): string
{
$connectionMap = $this->getStoreToConnectionMap();
if (! isset($connectionMap[$store])) {
throw new InvalidConfigurationException(
sprintf('Connection for store %s is not set.', $store)
);
}
return (string) $connectionMap[$store];
}
public function getStoreToConnectionMap(): array
{
return $this->getConfiguration()->getArray(self::OPTION_STORE_TO_CONNECTION_KEY_MAP);
}
public function getAllStoreConnectionsAndParameters(): array
{
return $this->getConfiguration()->getArray(self::OPTION_ALL_STORE_CONNECTIONS_AND_PARAMETERS);
}
public function getStoreConnectionParameters(string $connection): array
{
$connections = $this->getAllStoreConnectionsAndParameters();
if (! isset($connections[$connection]) || ! is_array($connections[$connection])) {
throw new InvalidConfigurationException(
sprintf('Settings for connection %s not set', $connection)
);
}
return $connections[$connection];
}
public function getModuleSourceDirectory(): string
{
return __DIR__;
}
public function getModuleRootDirectory(): string
{
return dirname(__DIR__);
}
} }
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\ModuleConfiguration;
class AccountingProcessingType
{
public const VALUE_SYNCHRONOUS = 'synchronous';
public const VALUE_ASYNCHRONOUS = 'asynchronous';
public const VALID_OPTIONS = [
self::VALUE_SYNCHRONOUS,
self::VALUE_ASYNCHRONOUS,
];
}
<?php
namespace SimpleSAML\Module\accounting\Services;
use SimpleSAML\Logger;
class LoggerService
{
/**
* Log an emergency message.
*
* @param string $message The message to log.
*/
public function emergency(string $message): void
{
Logger::emergency($message);
}
/**
* Log a critical message.
*
* @param string $message The message to log.
*/
public function critical(string $message): void
{
Logger::critical($message);
}
/**
* Log an alert message.
*
* @param string $message The message to log.
*/
public function alert(string $message): void
{
Logger::alert($message);
}
/**
* Log an error message.
*
* @param string $message The message to log.
*/
public function error(string $message): void
{
Logger::error($message);
}
/**
* Log a warning message.
*
* @param string $message The message to log.
*/
public function warning(string $message): void
{
Logger::warning($message);
}
/**
* Log a notice message.
*
* @param string $message The message to log.
*/
public function notice(string $message): void
{
Logger::notice($message);
}
/**
* Log an info message (a bit less verbose than debug messages).
*
* @param string $message The message to log.
*/
public function info(string $message): void
{
Logger::info($message);
}
/**
* Log a debug message (very verbose messages).
*
* @param string $message The message to log.
*/
public function debug(string $message): void
{
Logger::debug($message);
}
/**
* Log a statistics message.
*
* @param string $message The message to log.
*/
public function stats(string $message): void
{
Logger::stats($message);
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Stores\Connections\Bases;
use SimpleSAML\Module\accounting\Exceptions\InvalidValueException;
use SimpleSAML\Module\accounting\Exceptions\MigrationException;
use SimpleSAML\Module\accounting\Helpers\FilesystemHelper;
use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface;
abstract class AbstractMigrator
{
public const DEFAULT_MIGRATIONS_DIRECTORY_NAME = 'Migrations';
/**
* @param string $directory
* @param string $namespace
* @return class-string[]
*/
public function gatherMigrationClassesFromDirectory(string $directory, string $namespace): array
{
$directory = FilesystemHelper::getRealPath($directory);
// Get files without dot directories
$files = array_values(array_diff(scandir($directory), ['..', '.']));
array_walk($files, function (string &$file) use ($namespace) {
// Remove .php extension from filename
$file = basename($file, '.php');
// Prepend namespace for each entry
$file = $namespace . '\\' . $file;
});
// Migration classes must follow proper interfaces, so do validate each of them and discard invalid ones.
/** @var class-string[] $migrationClasses */
$migrationClasses = array_filter($files, function (string $file) {
try {
$this->validateMigrationClass($file);
return true;
} catch (InvalidValueException $exception) {
return false;
}
});
return $migrationClasses;
}
/**
* @param class-string[] $migrationClasses
* @return void
*/
public function runMigrationClasses(array $migrationClasses): void
{
foreach ($migrationClasses as $migrationClass) {
$this->validateMigrationClass($migrationClass);
$migration = $this->buildMigrationClassInstance($migrationClass);
try {
$migration->run();
} catch (\Throwable $exception) {
$message = sprintf(
'Could not run migration class %s. Error was: %s',
$migrationClass,
$exception->getMessage()
);
throw new MigrationException($message);
}
$this->markImplementedMigrationClass($migrationClass);
}
}
/**
* @return class-string[]
*/
public function getNonImplementedMigrationClasses(string $directory, string $namespace): array
{
return array_diff(
$this->gatherMigrationClassesFromDirectory($directory, $namespace),
$this->getImplementedMigrationClasses()
);
}
/**
* @param string $directory
* @param string $namespace
* @return bool
*/
public function hasNonImplementedMigrationClasses(string $directory, string $namespace): bool
{
return ! empty($this->getNonImplementedMigrationClasses($directory, $namespace));
}
public function runNonImplementedMigrationClasses(string $directory, string $namespace): void
{
$this->runMigrationClasses($this->getNonImplementedMigrationClasses($directory, $namespace));
}
public function validateMigrationClass(string $migrationClass): void
{
if (! is_subclass_of($migrationClass, MigrationInterface::class)) {
throw new InvalidValueException(
sprintf('Migration class does not implement MigrationInterface (%s)', $migrationClass)
);
}
}
/**
* @param class-string $migrationClass
* @return MigrationInterface
*/
abstract protected function buildMigrationClassInstance(string $migrationClass): MigrationInterface;
/**
* @param class-string $migrationClass
* @return void
*/
abstract protected function markImplementedMigrationClass(string $migrationClass): void;
/**
* @return class-string[]
*/
abstract public function getImplementedMigrationClasses(): array;
}
<?php
namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface;
use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Connection;
abstract class AbstractMigration implements MigrationInterface
{
protected Connection $connection;
protected AbstractSchemaManager $schemaManager;
public function __construct(Connection $connection)
{
$this->connection = $connection;
$this->schemaManager = $this->connection->dbal()->createSchemaManager();
}
}
<?php
declare(strict_types=1);
namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Exception;
use SimpleSAML\Module\accounting\Exceptions\InvalidConfigurationException;
use SimpleSAML\Module\accounting\Stores\Interfaces\ConnectionInterface;
class Connection implements ConnectionInterface
{
public const PARAMETER_TABLE_PREFIX = 'table_prefix';
protected \Doctrine\DBAL\Connection $dbal;
protected ?string $tablePrefix;
public function __construct(array $parameters)
{
try {
/** @psalm-suppress MixedArgumentTypeCoercion */
$this->dbal = DriverManager::getConnection($parameters);
} catch (Exception $e) {
throw new InvalidConfigurationException(
'Could not initiate Doctrine DBAL connection with given parameters.'
);
}
$this->tablePrefix = $this->getTablePrefixFromParameters($parameters);
}
public function getTablePrefix(): string
{
return $this->tablePrefix ?? '';
}
public function dbal(): \Doctrine\DBAL\Connection
{
return $this->dbal;
}
protected function getTablePrefixFromParameters(array $settings): ?string
{
if (! isset($settings[self::PARAMETER_TABLE_PREFIX])) {
return null;
}
if (! is_string($settings[self::PARAMETER_TABLE_PREFIX])) {
throw new InvalidConfigurationException('Connection table prefix must be string (if set).');
}
return $settings[self::PARAMETER_TABLE_PREFIX];
}
public function preparePrefixedTableName(string $tableName): string
{
return $this->getTablePrefix() . $tableName;
}
}
<?php
namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal;
use SimpleSAML\Module\accounting\ModuleConfiguration;
use SimpleSAML\Module\accounting\Services\LoggerService;
class Factory
{
protected ModuleConfiguration $moduleConfiguration;
protected LoggerService $loggerService;
public function __construct(ModuleConfiguration $moduleConfiguration, LoggerService $loggerService)
{
$this->moduleConfiguration = $moduleConfiguration;
$this->loggerService = $loggerService;
}
public function buildConnection(string $connectionKey): Connection
{
return new Connection($this->moduleConfiguration->getStoreConnectionParameters($connectionKey));
}
public function buildMigrator(Connection $connection, LoggerService $loggerService = null): Migrator
{
return new Migrator($connection, $loggerService ?? $this->loggerService);
}
}
<?php
namespace SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Types\Types;
use SimpleSAML\Module\accounting\Exceptions\InvalidValueException;
use SimpleSAML\Module\accounting\Services\LoggerService;
use SimpleSAML\Module\accounting\Stores\Connections\Bases\AbstractMigrator;
use SimpleSAML\Module\accounting\Stores\Connections\DoctrineDbal\Bases\AbstractMigration;
use SimpleSAML\Module\accounting\Stores\Interfaces\MigrationInterface;
class Migrator extends AbstractMigrator
{
public const TABLE_NAME = 'migrations';
public const COLUMN_NAME_ID = 'id';
public const COLUMN_NAME_VERSION = 'version';
public const COLUMN_NAME_CREATED_AT = 'created_at';
protected Connection $connection;
protected LoggerService $loggerService;
protected AbstractSchemaManager $schemaManager;
protected string $prefixedTableName;
public function __construct(Connection $connection, LoggerService $loggerService)
{
$this->connection = $connection;
$this->loggerService = $loggerService;
$this->schemaManager = $this->connection->dbal()->createSchemaManager();
$this->prefixedTableName = $this->connection->preparePrefixedTableName(self::TABLE_NAME);
}
public function needsSetup(): bool
{
return ! $this->schemaManager->tablesExist([$this->prefixedTableName]);
}
public function runSetup(): void
{
if (! $this->needsSetup()) {
$this->loggerService->warning('Migrator setup has been called, however setup is not needed.');
return;
}
$this->createMigrationsTable();
}
protected function createMigrationsTable(): void
{
$table = new Table($this->prefixedTableName);
$table->addColumn(self::COLUMN_NAME_ID, Types::BIGINT)
->setAutoincrement(true)
->setUnsigned(true);
$table->addColumn(self::COLUMN_NAME_VERSION, Types::STRING);
$table->addColumn(self::COLUMN_NAME_CREATED_AT, Types::DATETIMETZ_IMMUTABLE);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex([self::COLUMN_NAME_VERSION]);
$this->schemaManager->createTable($table);
}
protected function buildMigrationClassInstance(string $migrationClass): MigrationInterface
{
$this->validateDoctrineDbalMigrationClass($migrationClass);
/** @var MigrationInterface $migration */
$migration = (new \ReflectionClass($migrationClass))->newInstance($this->connection);
return $migration;
}
protected function validateDoctrineDbalMigrationClass(string $migrationClass): void
{
if (! is_subclass_of($migrationClass, AbstractMigration::class)) {
throw new InvalidValueException('Migration class is not Doctrine DBAL migration.');
}
}
protected function markImplementedMigrationClass(string $migrationClass): void
{
$queryBuilder = $this->connection->dbal()->createQueryBuilder();
$queryBuilder->insert($this->prefixedTableName)
->values(
[
self::COLUMN_NAME_VERSION => ':' . self::COLUMN_NAME_VERSION,
self::COLUMN_NAME_CREATED_AT => ':' . self::COLUMN_NAME_CREATED_AT,
]
)
->setParameters(
[
self::COLUMN_NAME_VERSION => $migrationClass,
self::COLUMN_NAME_CREATED_AT => new \DateTimeImmutable(),
],
[
self::COLUMN_NAME_VERSION => Types::STRING,
self::COLUMN_NAME_CREATED_AT => Types::DATETIMETZ_IMMUTABLE
]
);
$queryBuilder->executeStatement();
}
public function getImplementedMigrationClasses(): array
{
$queryBuilder = $this->connection->dbal()->createQueryBuilder();
$queryBuilder->select(self::COLUMN_NAME_VERSION)
->from($this->prefixedTableName);
/** @var class-string[] $migrationClasses */
$migrationClasses = $queryBuilder->executeQuery()->fetchFirstColumn();
return $migrationClasses;
}
}
<?php
namespace SimpleSAML\Module\accounting\Stores\Interfaces;
interface ConnectionInterface
{
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment