diff --git a/README.md b/README.md index 5339b61..545bda8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,11 @@ Argon2id. Because the various formats are recognized on-the-fly your db can can have differing hash string formats at the same time, which eases migration to newer formats. -This app supports PostgreSQL and MariaDB/MySQL. +This app primarily supports PostgreSQL and MariaDB/MySQL but the underlying PHP +[mechanism](https://www.php.net/manual/en/pdo.drivers.php) also supports +Firebird, MS SQL, Oracle DB, ODBC, DB2, SQLite, Informix and IBM databases. By +using an appropriate DSN you should be able to connect to these databases. This +has not been tested, though. See [CHANGELOG.md](CHANGELOG.md) for changes in newer versions. This app follows semantic versioning and there should not be any breaking changes unless the @@ -45,7 +49,7 @@ This app has no user interface. All configuration is done via Nextcloud's system //'db_type' => 'postgresql', //'db_host' => 'localhost', //'db_port' => '5432', - 'db_name' => 'theNameOfYourUserDatabase', + 'db_name' => 'theNameOfYourDbUser', 'db_user' => 'yourDatabaseUser', 'db_password' => 'thePasswordForTheDatabaseUser', //'db_password_file' => '/var/secrets/fileContainingThePasswordForTheDatabaseUser', @@ -70,28 +74,26 @@ There are three types of configuration parameters: ### 1. Database -that *User Backend SQL Raw* will connect to. - -| key | value | default value | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- | -| `db_type` | `postgresql` or `mariadb` | `postgresql` | -| `db_host` | your db host such as `localhost` or `db.example.com` or (only for PostgreSQL) path to socket, e.g. `/var/run/postgresql` | `localhost` | -| `db_port` | your db port | `5432` | -| `db_name` | your db name | | -| `db_user` | your db user | | -| `db_password` | your db password | | -| `db_password_file` | path to file containing the db password | | -| `mariadb_charset` | the charset for mariadb connections | `utf8mb4` | - -* Values without a default value are mandatory, except that - * only one of `db_password` or `db_passowrd_file` must be set. -* Only the first line of the file specified by `db_passowrd_file` is read. +that *User Backend SQL Raw* will connect to. There are two mutually exclusive ways to configure the database connection: +1. PostgreSQL-like + * Set `dsn` (containing user and password) and CAN specify `db_user` and (`db_password` or `db_password_file`). Values in DSN have priority. +2. MySQL-like + * Set `dsn` (not containing user and password) and MUST specify `db_user` and (`db_password` or `db_password_file`). + +* `dsn`: check how to construct DSNs for [PostgreSQL](https://www.php.net/manual/en/ref.pdo-pgsql.connection.php) and [MySQL](https://www.php.net/manual/en/ref.pdo-mysql.connection.php) Examples: + * connect to PostgreSQL via a socket with ident authentication which requires no user or password at all: `pgsql:host=/var/run/postgresql;dbname=theNameOfYourUserDb` + * connect to PostgreSQL via TCP and user/password authentication: `pgsql:host=localhost;port=5432;dbname=theNameOfYourUserDb;user=theNameOfYourDbUser;password=thePasswordForTheDbUser` + * connect to MySQL via socket which requires no user or password at all: `mysql:unix_socket=/var/run/mysql/mysql.sock;dbname=theNameOfYourUserDb` + * connect to MySQL via TCP and user/password authentication: `mysql:host=localhost;port=3306;dbname=testdb` and then also set `db_user` and (`db_password` or `db_password_file`) +* `db_user`: Needs only be set for "MySQL-type" databases and is the database user that will be used to connect to the database. +* `db_password`: Needs only be set for "MySQL-type" databases and is the password for the user that will be used to connect to the database. +* `db_password_file`: Can be set to read the password from a file. Has higher priority than `db_password`, but lower priority than password in DSN. So, for PostgreSQL-like database connections, don't specify the password in the DSN because it would override this. For MySQL-like connections, this one will have priority. + * Only the first line of the file specified by `db_password_file` is read. * Not more than 100 characters of the first line are read. - * Whitespace-like characters are [stripped](https://www.php.net/manual/en/function.trim.php) from + * Whitespace-like characters are [trimmed](https://www.php.net/manual/en/function.trim.php) from the beginning and end of the read password. -* If you specify a socket as `db_host` (only for PostgreSQL), you need to put - dummy values for the mandatory values, although they are not required for the - socket connection. This will be fixed in a future release. + +For other databases check their [PDO driver documentation pages](https://www.php.net/manual/en/pdo.drivers.php) which in-turn link to their respective DSN references. ### 2. SQL Queries diff --git a/appinfo/info.xml b/appinfo/info.xml index d4acb81..b76b455 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -14,7 +14,7 @@ In contrast to the app *SQL user backend*, you write the SQL queries yourself. Y The app uses prepared statements and is written to be secure by default to prevent SQL injections. It understands the most popular standards for password hash formats: MD5-CRYPT, SHA256-CRYPT, SHA512-CRYPT, BCrypt and the state-of-the-art Argon2i and Argon2id. Because the various formats are recognized on-the-fly your db can can have differing hash string formats at the same time, which eases migration to newer formats. This app supports PostgreSQL and MariaDB/MySQL.]]> - 1.5.1 + 2.0.0 agpl Alexey Abel UserBackendSqlRaw diff --git a/composer.json b/composer.json index dbdd2be..449b67a 100644 --- a/composer.json +++ b/composer.json @@ -24,10 +24,10 @@ } }, "require": { - "php": ">=7.0" + "php": ">=8.0" }, "require-dev": { - "php": ">=7.3", + "php": ">=8.0", "phpunit/phpunit": "^9" } } \ No newline at end of file diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index ee7d61a..c35e7f2 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -21,6 +21,7 @@ namespace OCA\UserBackendSqlRaw\AppInfo; +use OCA\UserBackendSqlRaw\Db; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; @@ -49,10 +50,9 @@ public function register(IRegistrationContext $context): void * Nextcloud's dependency injection is partly explained in: * https://docs.nextcloud.com/server/latest/developer_manual/basics/dependency_injection.html#how-to-deal-with-interface-and-primitive-type-parameters */ - $context->registerService('OCA\UserBackendSqlRaw\Db', function (ContainerInterface $container) { - /** @var \OCA\UserBackendSqlRaw\Config $config */ - $config = $container->get('OCA\UserBackendSqlRaw\Config'); - return $container->get('OCA\UserBackendSqlRaw\Dbs\\' . ucfirst($config->getDbType())); + $context->registerService(OCA\UserBackendSqlRaw\Db::class, function (ContainerInterface $container) { + // TODO: can be simplified/removed probably + return new OCA\UserBackendSqlRaw\Db(); }); } diff --git a/lib/Config.php b/lib/Config.php index 986f681..abfdf12 100644 --- a/lib/Config.php +++ b/lib/Config.php @@ -26,25 +26,14 @@ class Config { - - const DEFAULT_DB_TYPE = 'postgresql'; - const DEFAULT_DB_HOST = 'localhost'; - const DEFAULT_POSTGRESQL_PORT = '5432'; - const DEFAULT_MARIADB_PORT = '3306'; - const DEFAULT_MARIADB_CHARSET = 'utf8mb4'; const DEFAULT_HASH_ALGORITHM_FOR_NEW_PASSWORDS = 'bcrypt'; - const MAXIMUM_ALLOWED_PASSWORD_LENGTH = 100; const CONFIG_KEY = 'user_backend_sql_raw'; - const CONFIG_KEY_DB_TYPE = 'db_type'; - const CONFIG_KEY_DB_HOST = 'db_host'; - const CONFIG_KEY_DB_PORT = 'db_port'; - const CONFIG_KEY_DB_NAME = 'db_name'; + const CONFIG_KEY_DSN = 'dsn'; const CONFIG_KEY_DB_USER = 'db_user'; const CONFIG_KEY_DB_PASSWORD = 'db_password'; const CONFIG_KEY_DB_PASSWORD_FILE = 'db_password_file'; - const CONFIG_KEY_MARIADB_CHARSET = 'mariadb_charset'; const CONFIG_KEY_HASH_ALGORITHM_FOR_NEW_PASSWORDS = 'hash_algorithm_for_new_passwords'; const CONFIG_KEY_QUERIES = 'queries'; @@ -81,56 +70,29 @@ public function __construct(LoggerInterface $logger, IConfig $nextCloudConfigura . self::CONFIG_KEY . ' which should contain the configuration ' . 'for the app user_backend_sql_raw.'); } - } - - /** - * @return string db type to connect to - */ - public function getDbType() - { - $dbTypeFromConfig = $this->getConfigValueOrDefaultValue(self::CONFIG_KEY_DB_TYPE - , self::DEFAULT_DB_TYPE); - - $normalizedDbType = $this->normalize($dbTypeFromConfig); - - if (!$this->dbTypeIsSupported($normalizedDbType)) { - throw new \UnexpectedValueException('The config key ' - . self::CONFIG_KEY_DB_TYPE . ' is set to ' . $dbTypeFromConfig . '. This ' - . 'value is invalid. Only postgresql and mariadb are supported.'); - } - - return $normalizedDbType; - } - /** - * @return string db host to connect to - */ - public function getDbHost() - { - return $this->getConfigValueOrDefaultValue(self::CONFIG_KEY_DB_HOST - , self::DEFAULT_DB_HOST); + $this->warnAboutObsoleteConfigKeys(); } - /** - * @return int db port to connect to - */ - public function getDbPort() + public function warnAboutObsoleteConfigKeys() { - - $defaultPortForCurrentDb = ($this->getDbType() === 'mariadb') - ? self::DEFAULT_MARIADB_PORT - : self::DEFAULT_POSTGRESQL_PORT; - - return $this->getConfigValueOrDefaultValue(self::CONFIG_KEY_DB_PORT - , $defaultPortForCurrentDb); + $obsolete_keys = array("db_type", "db_host", "db_port", "db_name", "mariadb_charset"); + foreach ($obsolete_keys as $key) { + // not using getConfigValueOrFalse() here, because we want to also catch empty strings + if (array_key_exists(key: $key, array:$this->appConfiguration)) { + $this->logger->warning("The configuration key '{$key}' is obsolete since " + . "version 2.0.0. It has no effect and can be removed."); + } + } } /** - * @return string db name to connect to + * @return string dsn to use for db connection + * @throws \UnexpectedValueException */ - public function getDbName() + public function getDsn() { - return $this->getConfigValueOrThrowException(self::CONFIG_KEY_DB_NAME); + return $this->getConfigValueOrThrowException(self::CONFIG_KEY_DSN); } /** @@ -138,7 +100,7 @@ public function getDbName() */ public function getDbUser() { - return $this->getConfigValueOrThrowException(self::CONFIG_KEY_DB_USER); + return $this->getConfigValueOrFalse(self::CONFIG_KEY_DB_USER); } /** @@ -147,24 +109,17 @@ public function getDbUser() */ public function getDbPassword() { - $password = $this->getConfigValueOrFalse(self::CONFIG_KEY_DB_PASSWORD); $passwordFilePath = $this->getConfigValueOrFalse(self::CONFIG_KEY_DB_PASSWORD_FILE); $passwordIsSet = $password !== false; $passwordFileIsSet = $passwordFilePath !== false; - if ($passwordIsSet === $passwordFileIsSet) { // expression is a "not XOR" - throw new \UnexpectedValueException('Exactly one of ' . self::CONFIG_KEY_DB_PASSWORD . ' or ' . self::CONFIG_KEY_DB_PASSWORD_FILE . ' must be set (not be empty) in the config.'); - } - - if ($passwordIsSet) { - $this->logger->debug("Will use db password specified directly in config.php."); - return $password; - } - + // Password from file (db_password_file) has higher priority than password from config (db_password). if ($passwordFileIsSet) { - $this->logger->debug("Will use db password stored in file " . $passwordFilePath) . "."; + $this->logger->debug("Will use db password stored in file " . $passwordFilePath) + . ". Password from config file will not be considered. Password from DSN still has " + ."priority."; $error_message_prefix = "Specified db password file with path {$passwordFilePath}"; if (!file_exists($passwordFilePath)) { @@ -189,17 +144,19 @@ public function getDbPassword() fclose($file); $this->logger->debug("Successfully read db password from file " . $passwordFilePath) . "."; return trim($first_line); + } elseif ($passwordIsSet) { + $this->logger->debug("Will use db password specified in config.php. Password from file" + ." was not specified. Password from DSN still has priority."); + return $password; + } else { + return false; } - } + // Priority of password in the DSN over both passwords read here is + // implemented in the PDO implementation of PHP. It will simply ignore + // the password given as a parameter during PDO object creation and use + // the one from the DSN, if the DSN contains it. - /** - * @return string charset for mariadb connection - */ - public function getMariadbCharset() - { - return $this->getConfigValueOrDefaultValue(self::CONFIG_KEY_MARIADB_CHARSET - , self::DEFAULT_MARIADB_CHARSET); } /** @@ -219,23 +176,6 @@ public function getHashAlgorithmForNewPasswords() . 'to ' . $hashAlgorithmFromConfig . '. This value is invalid. Only ' . 'md5, sha256, sha512, bcrypt, argon2i and argon2id are supported.'); } - - if ($normalizedHashAlgorithm === 'argon2i' - && version_compare(PHP_VERSION, '7.2.0', '<')) { - throw new \UnexpectedValueException( - 'You specified Argon2i as the hash algorithm for new ' - . 'passwords. Argon2i is only available in PHP version 7.2.0 and' - . ' higher, but your PHP version is ' . PHP_VERSION . '.'); - } - - if ($normalizedHashAlgorithm === 'argon2id' - && version_compare(PHP_VERSION, '7.3.0', '<')) { - throw new \UnexpectedValueException( - 'You specified Argon2id as the hash algorithm for new ' - . 'passwords. Argon2id is only available in PHP version 7.3.0 and' - . ' higher, but your PHP version is ' . PHP_VERSION . '.'); - } - return $normalizedHashAlgorithm; } @@ -364,16 +304,6 @@ private function getQueryStringOrFalse($configKey) return $this->getValueOrFalse($queryArray[$configKey] ?? false); } - /** - * @param $dbType string db descriptor to check - * @return bool whether the db is supported - */ - private function dbTypeIsSupported($dbType) - { - return $dbType === 'postgresql' - || $dbType === 'mariadb'; - } - /** * Checks whether hash algorithm is supported for writing. * @param $hashAlgorithm string hash algorithm descriptor to check @@ -400,4 +330,5 @@ private function normalize($string) { return strtolower(preg_replace("/[-_]/", "", $string)); } + } diff --git a/lib/Db.php b/lib/Db.php index 82345b6..f52c186 100644 --- a/lib/Db.php +++ b/lib/Db.php @@ -21,6 +21,8 @@ namespace OCA\UserBackendSqlRaw; +use OCA\UserBackendSqlRaw\Config; +use Psr\Log\LoggerInterface; use \PDO; /** @@ -28,7 +30,7 @@ * creation. * @package OCA\UserBackendSqlRaw */ -abstract class Db +class Db { /** @var Config */ protected $config; @@ -36,8 +38,12 @@ abstract class Db /** @var PDO */ private $dbHandle; - public function __construct(Config $config) + /* @var LoggerInterface */ + private $logger; + + public function __construct(LoggerInterface $logger, Config $config) { + $this->logger = $logger; $this->config = $config; } @@ -48,7 +54,6 @@ public function __construct(Config $config) public function getDbHandle() { if (is_null($this->dbHandle)) { - $this->dbHandle = $this->createDbHandle(); // Some methods of the backend are called by Nextcloud in a way that // suppresses exceptions, probably to avoid leaking passwords to log @@ -64,16 +69,26 @@ public function getDbHandle() } /** - * Returns a new PDO db handle for the specific db type + * Returns a new PDO db handle * @return PDO a new PDO object */ - abstract protected function createDbHandle(); - - /** - * Returns a Data Source Name (DSN) that is used by a PDO object for - * creating the connection to a database. This is db specific. - * @return string the DSN string - */ - abstract protected function assembleDsn(); + protected function createDbHandle() + { + $user = $this->config->getDbUser(); + $password = $this->config->getDbPassword(); + if ($user xor $password) { + throw new \UnexpectedValueException("You set only one of `db_user` and `db_password`" + . " but not the other. You must either set both or none. If none are provided, " + . "then both values are read from the DSN."); + } elseif ($user and $password) { + // for e.g. MySQL + return new PDO($this->config->getDsn(), $user, $password); + } else { + // for e.g. PostgreSQL + return new PDO($this->config->getDsn()); + } + throw new \LogicException("This should never happen. If you see this error, please report" + . " it as a bug."); + } } diff --git a/lib/Dbs/Mariadb.php b/lib/Dbs/Mariadb.php deleted file mode 100644 index d50c5ca..0000000 --- a/lib/Dbs/Mariadb.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCA\UserBackendSqlRaw\Dbs; - -use OCA\UserBackendSqlRaw\Db; -use \PDO; - - -class Mariadb extends Db { - - protected function createDbHandle() { - return new PDO($this->assembleDsn(), - $this->config->getDbUser(), - $this->config->getDbPassword()); - } - - protected function assembleDsn() { - return 'mysql:host=' . $this->config->getDbHost() - . ';port=' . $this->config->getDbPort() - . ';dbname=' . $this->config->getDbName() - . ';charset=' . $this->config->getMariadbCharset(); - } -} diff --git a/lib/Dbs/Postgresql.php b/lib/Dbs/Postgresql.php deleted file mode 100644 index 4bab004..0000000 --- a/lib/Dbs/Postgresql.php +++ /dev/null @@ -1,40 +0,0 @@ - - * - * @license GNU AGPL version 3 or any later version - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - */ - -namespace OCA\UserBackendSqlRaw\Dbs; - -use OCA\UserBackendSqlRaw\Db; -use \PDO; - -class Postgresql extends Db { - - protected function createDbHandle() { - return new PDO($this->assembleDsn()); - } - - protected function assembleDsn() { - return 'pgsql:host=' . $this->config->getDbHost() - . ';port=' . $this->config->getDbPort() - . ';dbname=' . $this->config->getDbName() - . ';user=' . $this->config->getDbUser() - . ';password=' . $this->config->getDbPassword(); - } -} \ No newline at end of file diff --git a/tests/Dbs/SqliteMemoryTestDb.php b/tests/Dbs/SqliteMemoryTestDb.php index ba2d238..00a6400 100644 --- a/tests/Dbs/SqliteMemoryTestDb.php +++ b/tests/Dbs/SqliteMemoryTestDb.php @@ -28,10 +28,10 @@ class SqliteMemoryTestDb extends Db { protected function createDbHandle() { - return new PDO($this->assembleDsn()); + return new PDO($this->getDsn()); } - protected function assembleDsn() + protected function getDsn() { return 'sqlite::memory:'; } diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php index c797750..7f23fa5 100644 --- a/tests/Unit/ConfigTest.php +++ b/tests/Unit/ConfigTest.php @@ -59,105 +59,19 @@ public function testThrowsExceptionIfMandatorySettingIsNotSet() $this->expectException(\UnexpectedValueException::class); $config = new Config($this->logStub, $this->nextcloudConfigStub); - $config->getDbName(); + $config->getDsn(); } public function testThrowsExceptionIfMandatorySettingIsEmpty() { $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'db_name' => '', + 'dsn' => '', )); $this->expectException(\UnexpectedValueException::class); $config = new Config($this->logStub, $this->nextcloudConfigStub); - $config->getDbName(); - } - - public function testThrowsExceptionIfDbPasswordAndDbPasswordFileAreBothSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'db_password' => 'such_secret', - // Specify a file that is always there, so that test does not fail due to missing db password file. - 'db_password_file' => '/dev/zero', - )); - - $this->expectException(\UnexpectedValueException::class); - $config = new Config($this->logStub, $this->nextcloudConfigStub); - $config->getDbPassword(); - } - - // Tests that check if default values are uses correctly - - public function testDefaultDbTypeIsUsedWhenThatParameterIsNotSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'the_configuration_is_not_empty' => 'but also contains no usable keys', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedDbType = Config::DEFAULT_DB_TYPE; - $actualDbType = $config->getDbType(); - self::assertEquals($expectedDbType, $actualDbType); - } - - public function testDefaultHostIsUsedWhenThisParameterIsNotSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'the_configuration_is_not_empty' => 'but also contains no usable keys', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedHost = Config::DEFAULT_DB_HOST; - $actualHost = $config->getDbHost(); - self::assertEquals($expectedHost, $actualHost); - } - - public function testDefaultPostgresqlPortIsUsedWhenThisParameterIsNotSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'the_configuration_is_not_empty' => 'but also contains no usable keys', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedPort = Config::DEFAULT_POSTGRESQL_PORT; - $actualPort = $config->getDbPort(); - self::assertEquals($expectedPort, $actualPort); - } - - public function testDefaultMariaPortIsUsedWhenThisParameterIsNotSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'db_type' => 'mariadb', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedPort = Config::DEFAULT_MARIADB_PORT; - $actualPort = $config->getDbPort(); - self::assertEquals($expectedPort, $actualPort); - } - - public function testDefaultMariadbCharsetIsUsedWhenThisParameterIsNotSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'the_configuration_is_not_empty' => 'but also contains no usable keys', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedCharset = Config::DEFAULT_MARIADB_CHARSET; - $actualCharset = $config->getMariadbCharset(); - self::assertEquals($expectedCharset, $actualCharset); + $config->getDsn(); } public function testDefaultHashAlgorithmForNewPasswordsIsUsedWhenThisParameterIsNotSet() @@ -191,60 +105,32 @@ public function testEmptyQueryParameterReturnsFalse() // Tests that check if actual (non-default) values are returned - public function testDbTypeIsReturnedWhenThisParameterIsSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'db_type' => 'mariadb', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedDbType = 'mariadb'; - $actualDbType = $config->getDbType(); - self::assertEquals($expectedDbType, $actualDbType); - } - - public function testDbHostDomainNameIsReturnedWhenThisParameterIsSet() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'db_host' => 'nextcloud.mycompany.com', - )); - - $config = new Config($this->logStub, $this->nextcloudConfigStub); - - $expectedHost = 'nextcloud.mycompany.com'; - $actualHost = $config->getDbHost(); - self::assertEquals($expectedHost, $actualHost); - } - - public function testDbHostIpIsReturnedWhenThisParameterIsSet() + public function testDsnIsReturnedWhenThisParameterIsSet() { $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'db_host' => '43.100.4.0', + 'dsn' => 'pgsql:host=/var/run/postgresql;dbname=theName_OfYourUserDb', )); $config = new Config($this->logStub, $this->nextcloudConfigStub); - $expectedHost = '43.100.4.0'; - $actualHost = $config->getDbHost(); - self::assertEquals($expectedHost, $actualHost); + $expectedDsn = 'pgsql:host=/var/run/postgresql;dbname=theName_OfYourUserDb'; + $actualDsn = $config->getDsn(); + self::assertEquals($expectedDsn, $actualDsn); } - public function testDbPortIsReturnedWhenThisParameterIsSet() + public function testPasswordIsReturnedWhenThisParameterIsSet() { + $expectedPassword = '!I_am V-e rySecr3t9!&äßZ'; $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'db_port' => '54321', + 'db_password' => $expectedPassword, )); $config = new Config($this->logStub, $this->nextcloudConfigStub); - $expectedPort = '54321'; - $actualPort = $config->getDbPort(); - self::assertEquals($expectedPort, $actualPort); + $actualPassword = $config->getDbPassword(); + self::assertEquals($expectedPassword, $actualPassword); } public function testDBPasswordFromPasswordFileIsReturned() @@ -252,12 +138,12 @@ public function testDBPasswordFromPasswordFileIsReturned() $expectedPassword = 'v_erY-secr3ttt 909!&äßZ'; - $db_password_file = tempnam("/tmp", "user_backend_sql_raw-db_password_file"); - if ($db_password_file === false) { + $dbPasswordFile = tempnam("/tmp", "user_backend_sql_raw-db_password_file"); + if ($dbPasswordFile === false) { self::fail("Temporary db password file could not be created."); } - $file = fopen($db_password_file, "w"); + $file = fopen($dbPasswordFile, "w"); if ($file === false) { self::fail("Temporary db password file could not be opened for writing."); } @@ -267,27 +153,27 @@ public function testDBPasswordFromPasswordFileIsReturned() $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'db_password_file' => $db_password_file, + 'db_password_file' => $dbPasswordFile, )); $config = new Config($this->logStub, $this->nextcloudConfigStub); $actualPassword = $config->getDbPassword(); - unlink($db_password_file); + unlink($dbPasswordFile); self::assertEquals($expectedPassword, $actualPassword); } - public function testDBPasswordFromPasswordFileIsTrimmed() - { + public function testDBPasswordFromPasswordFileIsTrimmed() + { $expectedPassword = 'secret'; - $db_password_file = tempnam("/tmp", "user_backend_sql_raw-db_password_file"); - if ($db_password_file === false) { + $dbPasswordFile = tempnam("/tmp", "user_backend_sql_raw-db_password_file"); + if ($dbPasswordFile === false) { self::fail("Temporary db password file could not be created."); } - $file = fopen($db_password_file, "w"); + $file = fopen($dbPasswordFile, "w"); if ($file === false) { self::fail("Temporary db password file could not be opened for writing."); } @@ -298,28 +184,45 @@ public function testDBPasswordFromPasswordFileIsTrimmed() $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'db_password_file' => $db_password_file, + 'db_password_file' => $dbPasswordFile, )); $config = new Config($this->logStub, $this->nextcloudConfigStub); $actualPassword = $config->getDbPassword(); - unlink($db_password_file); + unlink($dbPasswordFile); self::assertEquals($expectedPassword, $actualPassword); } - public function testMariaDbCharsetIsReturnedWhenThisParameterIsSet() + public function testPasswordFromPasswordFileOverridesPasswordFromConfig() { + $passwordFromFile = 'password_from_file'; + $passwordFromConfig = 'password_from_config'; + + $dbPasswordFile = tempnam("/tmp", "user_backend_sql_raw-db_password_file"); + if ($dbPasswordFile === false) { + self::fail("Temporary db password file could not be created."); + } + + $file = fopen($dbPasswordFile, "w"); + if ($file === false) { + self::fail("Temporary db password file could not be opened for writing."); + } + + fwrite($file, "{$passwordFromFile}"); + fclose($file); + $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'mariadb_charset' => 'latin2_czech_cs', + 'db_password' => $passwordFromConfig, + 'db_password_file' => $dbPasswordFile, )); $config = new Config($this->logStub, $this->nextcloudConfigStub); - $expectedCharset = 'latin2_czech_cs'; - $actualCharset = $config->getMariadbCharset(); - self::assertEquals($expectedCharset, $actualCharset); + $actualPassword = $config->getDbPassword(); + unlink($dbPasswordFile); + self::assertEquals($passwordFromFile, $actualPassword); } public function testHashAlgorithmForNewPasswordsIsReturnedWhenThisParameterIsSet() @@ -352,7 +255,7 @@ public function testQueryIsReturnedWhenItIsSet() self::assertEquals($expectedQuery, $actualReturnValue); } - // Test that check whether invalid values four countable types are + // Tests that check whether invalid values for countable types are // recognized public function testExceptionIsThrownForUnsupportedHashAlgorithmForNewPasswords() @@ -364,33 +267,20 @@ public function testExceptionIsThrownForUnsupportedHashAlgorithmForNewPasswords( $this->expectException(\UnexpectedValueException::class); $config = new Config($this->logStub, $this->nextcloudConfigStub); - $config->getDbPassword(); - } - - public function testExceptionIsThrownForUnsupportedDbType() - { - $this->nextcloudConfigStub->method('getSystemValue') - ->willReturn(array( - 'db_type' => 'oracle', - )); - - $this->expectException(\UnexpectedValueException::class); - $config = new Config($this->logStub, $this->nextcloudConfigStub); - $config->getDbType(); + $config->getHashAlgorithmForNewPasswords(); } // Test that checks if multiple parameters are recognized simultaneously. // Previous tests only tested single parameters. public function testMultipleParametersAreRecognizedSimultaneously() { + $expectedDsn = 'pgsql:host=/var/run/postgresql;dbname=theName_OfYourUserDb'; + $expectedPassword = '!me SoSec35?äöß1'; // db_type will be left empty to test default value $this->nextcloudConfigStub->method('getSystemValue') ->willReturn(array( - 'db_type' => '', - 'db_port' => '4567', - 'db_user' => 'JohnDoe', - 'db_password' => 'bpd_N(z6%aT&$ 'db.cluster.de', + 'dsn' => $expectedDsn, + 'db_password' => $expectedPassword, 'queries' => array( 'user_exists' => 'SELECT EXISTS(SELECT 1 FROM virtual_users_fqda WHERE fqda = :username)', 'create_user' => 'INSERT INTO virtual_users (local, domain, password_hash) VALUES (split_part(:username, \'@\', 1), split_part(:username, \'@\', 2), :password_hash)', @@ -400,11 +290,8 @@ public function testMultipleParametersAreRecognizedSimultaneously() $config = new Config($this->logStub, $this->nextcloudConfigStub); - self::assertEquals('postgresql', $config->getDbType()); - self::assertEquals('4567', $config->getDbPort()); - self::assertEquals('JohnDoe', $config->getDbUser()); - self::assertEquals('bpd_N(z6%aT&$getDbPassword()); - self::assertEquals('db.cluster.de', $config->getDbHost()); + self::assertEquals($expectedDsn, $config->getDsn()); + self::assertEquals($expectedPassword, $config->getDbPassword()); self::assertEquals( 'SELECT EXISTS(SELECT 1 FROM virtual_users_fqda WHERE fqda = :username)' , $config->getQueryUserExists());