Skip to content

Commit 7bf0e0d

Browse files
Alexey Abelalexeyabel
authored andcommitted
Implement DSN-based database connection
The DSN-based connection configuration is closer to what PHP does with PDO objects under the hood and this allows the use of more scenarios such as socket connections and more databases out-of-the-box without user_backend_sql_raw having to provide configuration parameters for each option and database.
1 parent 2b0707d commit 7bf0e0d

File tree

11 files changed

+203
-424
lines changed

11 files changed

+203
-424
lines changed

CHANGELOG.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
# Changelog
22

3-
All notable changes to this project will be documented in this file.
3+
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4+
5+
## [Unreleased]
6+
7+
### Addded
8+
9+
* DSN-based database connection mechanism. This enables support for socket-based database connections and also connections to Firebird, MS SQL, Oracle DB, ODBC, DB2, SQLite, Informix and IBM databases - basically whatever the [PHP PDO-driver](https://www.php.net/manual/en/pdo.drivers.php) supports. But PostgreSQL remains the only tested database and MySQL/MariaDB to some degree. The other databaes should "just work", but this has not been tested.
10+
* `dsn` configuration key
11+
* dependancy on PHP >=8.0
12+
13+
### Removed
14+
15+
* **Breaking**: remove configuration keys `db_type`, `db_host`, `db_port`, `db_name`, `mariadb_charset`. These settings must now be included in the DSN string. See [README.md](README.md#1database) on how to do this.
16+
* support for Nextcloud <26, because Nextcloud 26 is the first to require PHP 8.0, which this app now also requires
417

5-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
718

819
## [1.5.1] - 2024-05-01
920

README.md

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ Argon2id. Because the various formats are recognized on-the-fly your db can can
2222
have differing hash string formats at the same time, which eases migration to
2323
newer formats.
2424

25-
This app supports PostgreSQL and MariaDB/MySQL.
25+
This app primarily supports PostgreSQL and MariaDB/MySQL but the underlying PHP
26+
[mechanism](https://www.php.net/manual/en/pdo.drivers.php) also supports
27+
Firebird, MS SQL, Oracle DB, ODBC, DB2, SQLite, Informix and IBM databases. By
28+
using an appropriate DSN you should be able to connect to these databases. This
29+
has not been tested, though.
2630

2731
See [CHANGELOG.md](CHANGELOG.md) for changes in newer versions. This app follows
2832
semantic versioning and there should not be any breaking changes unless the
@@ -42,25 +46,21 @@ This app has no user interface. All configuration is done via Nextcloud's system
4246

4347
```php
4448
'user_backend_sql_raw' => array(
45-
//'db_type' => 'postgresql',
46-
//'db_host' => 'localhost',
47-
//'db_port' => '5432',
48-
'db_name' => 'theNameOfYourUserDatabase',
49-
'db_user' => 'yourDatabaseUser',
50-
'db_password' => 'thePasswordForTheDatabaseUser',
51-
//'db_password_file' => '/var/secrets/fileContainingThePasswordForTheDatabaseUser',
52-
//'mariadb_charset' => 'utf8mb4',
49+
'dsn' => 'pgsql:host=/var/run/postgresql;dbname=theNameOfYourUserDb',
50+
//'db_user' => 'yourDatabaseUser',
51+
//'db_password' => 'thePasswordForTheDatabaseUser',
52+
//'db_password_file' => '/path/to/file/ContainingThePasswordForTheDatabaseUser',
5353
'queries' => array(
54-
'get_password_hash_for_user' => 'SELECT password_hash FROM users_fqda WHERE fqda = :username',
55-
'user_exists' => 'SELECT EXISTS(SELECT 1 FROM users_fqda WHERE fqda = :username)',
56-
'get_users' => 'SELECT fqda FROM users_fqda WHERE (fqda ILIKE :search) OR (display_name ILIKE :search)',
57-
//'set_password_hash_for_user' => 'UPDATE users SET password_hash = :new_password_hash WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
58-
//'delete_user' => 'DELETE FROM users WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
59-
//'get_display_name' => 'SELECT display_name FROM users WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
60-
//'set_display_name' => 'UPDATE users SET display_name = :new_display_name WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
61-
//'count_users' => 'SELECT COUNT (*) FROM users',
62-
//'get_home' => '',
63-
//'create_user' => 'INSERT INTO users (local, domain, password_hash) VALUES (split_part(:username, \'@\', 1), split_part(:username, \'@\', 2), :password_hash)',
54+
'get_password_hash_for_user' => 'SELECT password_hash FROM users_fqda WHERE fqda = :username',
55+
'user_exists' => 'SELECT EXISTS(SELECT 1 FROM users_fqda WHERE fqda = :username)',
56+
'get_users' => 'SELECT fqda FROM users_fqda WHERE (fqda ILIKE :search) OR (display_name ILIKE :search)',
57+
//'set_password_hash_for_user' => 'UPDATE users SET password_hash = :new_password_hash WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
58+
//'delete_user' => 'DELETE FROM users WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
59+
//'get_display_name' => 'SELECT display_name FROM users WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
60+
//'set_display_name' => 'UPDATE users SET display_name = :new_display_name WHERE local = split_part(:username, \'@\', 1) AND domain = split_part(:username, \'@\', 2)',
61+
//'count_users' => 'SELECT COUNT (*) FROM users',
62+
//'get_home' => '',
63+
//'create_user' => 'INSERT INTO users (local, domain, password_hash) VALUES (split_part(:username, \'@\', 1), split_part(:username, \'@\', 2), :password_hash)',
6464
),
6565
//'hash_algorithm_for_new_passwords' => 'bcrypt',
6666
),
@@ -72,26 +72,57 @@ There are three types of configuration parameters:
7272

7373
that *User Backend SQL Raw* will connect to.
7474

75-
| key | value | default value |
76-
| ------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------- |
77-
| `db_type` | `postgresql` or `mariadb` | `postgresql` |
78-
| `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` |
79-
| `db_port` | your db port | `5432` |
80-
| `db_name` | your db name | |
81-
| `db_user` | your db user | |
82-
| `db_password` | your db password | |
83-
| `db_password_file` | path to file containing the db password | |
84-
| `mariadb_charset` | the charset for mariadb connections | `utf8mb4` |
85-
86-
* Values without a default value are mandatory, except that
87-
* only one of `db_password` or `db_passowrd_file` must be set.
88-
* Only the first line of the file specified by `db_passowrd_file` is read.
75+
* `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).
76+
* `db_user`: user that will be used to connect to the database
77+
* `db_password`: password for the user that will be used to connect to the database
78+
* `db_password_file`: Can be set to read the password from a file
79+
* Only the first line of the file specified by `db_password_file` is read.
8980
* Not more than 100 characters of the first line are read.
90-
* Whitespace-like characters are [stripped](https://www.php.net/manual/en/function.trim.php) from
81+
* Whitespace-like characters are [trimmed](https://www.php.net/manual/en/function.trim.php) from
9182
the beginning and end of the read password.
92-
* If you specify a socket as `db_host` (only for PostgreSQL), you need to put
93-
dummy values for the mandatory values, although they are not required for the
94-
socket connection. This will be fixed in a future release.
83+
84+
There are two methods to configure the database connection:
85+
86+
1. Set `dsn` to a DSN that contains the entire db connnection configuration including the db user and db password
87+
2. Set `dsn` to a DSN that contains everything **but** the db user and db password and then set `db_user` and `db_password`/`db_password_file`
88+
89+
PostgreSQL works with method 1 and 2. MySQL works only with method 2. If you use `db_password_file` also set `db_user` (even for PostgreSQL) and don't put the username in the DSN. This is because, the underlying PDO classes have some quirks and diverge from the documented behaviour. So, better don't mix both methods. `db_password_file` has higher priority than `db_password`, but lower priority than password in DSN. But it's better to only set one source for the password, for the same reasons.
90+
91+
#### Examples
92+
93+
* connect to PostgreSQL via a socket with ident authentication which requires no user or password at all:
94+
95+
```php
96+
'dsn' => 'pgsql:host=/var/run/postgresql;dbname=theNameOfYourUserDb',
97+
```
98+
99+
* connect to PostgreSQL via TCP and user/password authentication:
100+
```php
101+
'dsn' => 'pgsql:host=localhost;port=5432;dbname=theNameOfYourUserDb;user=theNameOfYourDbUser;password=thePasswordForTheDbUser',
102+
```
103+
* connect to PostgreSQL via TCP and user/password authentication and use password file:
104+
105+
```php
106+
'dsn' => 'pgsql:host=localhost;port=5432;dbname=theNameOfYourUserDb',
107+
'db_user' => 'theNameOfYourDbUser',
108+
'db_password_file' => '/path/to/password_file',
109+
```
110+
111+
* connect to MySQL via socket which requires no user or password at all:
112+
113+
```php
114+
'dsn' => 'mysql:unix_socket=/var/run/mysql/mysql.sock;dbname=theNameOfYourUserDb',
115+
```
116+
117+
* connect to MySQL via TCP and user/password authentication:
118+
119+
```php
120+
'dsn' => 'mysql:host=localhost;port=3306;dbname=testdb',
121+
'db_user' => 'theNameOfYourDbUser',
122+
'db_password' => 'thePasswordForTheDbUser', // or db_password_file instead
123+
```
124+
125+
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. They either use method 1 or method 2 AFAICS.
95126

96127
### 2. SQL Queries
97128

@@ -136,8 +167,6 @@ The config values are `md5`, `sha256`, `sha512`, `argon2i`, `argon2id` respectiv
136167
* This means, that your db can have different hash formats simultaneously. Whenever a
137168
user's password is changed, it will be updated to the configured hash algorithm. This eases
138169
migration to more modern algorithms.
139-
* Argon2i is only supported by PHP 7.2.0 and higher.
140-
* Argon2id is only supported by PHP 7.3.0 and higher.
141170

142171
## Security
143172

appinfo/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ In contrast to the app *SQL user backend*, you write the SQL queries yourself. Y
1414
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.
1515
1616
This app supports PostgreSQL and MariaDB/MySQL.]]></description>
17-
<version>1.5.1</version>
17+
<version>2.0.0</version>
1818
<licence>agpl</licence>
1919
<author mail="dev@abelonline.de" >Alexey Abel</author>
2020
<namespace>UserBackendSqlRaw</namespace>

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
}
2525
},
2626
"require": {
27-
"php": ">=7.0"
27+
"php": ">=8.0"
2828
},
2929
"require-dev": {
30-
"php": ">=7.3",
30+
"php": ">=8.0",
3131
"phpunit/phpunit": "^9"
3232
}
3333
}

lib/AppInfo/Application.php

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
namespace OCA\UserBackendSqlRaw\AppInfo;
2323

24+
use OCA\UserBackendSqlRaw\Db;
2425
use OCP\AppFramework\Bootstrap\IBootContext;
2526
use OCP\AppFramework\Bootstrap\IBootstrap;
2627
use OCP\AppFramework\Bootstrap\IRegistrationContext;
@@ -38,22 +39,6 @@ public function __construct(array $urlParams = array())
3839

3940
public function register(IRegistrationContext $context): void
4041
{
41-
/**
42-
* "Service" in this context is simply a class that you want to be able to inject.
43-
*
44-
* We don't have to register all classes because they can be auto-wired, but OCA\UserBackendSqlRaw\Db
45-
* is an abstract class and we need to manually define what Nextcloud will return when someone
46-
* queries (requests an instance of) this class. We query Config first (this one was auto-wired) and
47-
* use it's getDbType() method to instantiate the proper Db class by name.
48-
*
49-
* Nextcloud's dependency injection is partly explained in:
50-
* https://docs.nextcloud.com/server/latest/developer_manual/basics/dependency_injection.html#how-to-deal-with-interface-and-primitive-type-parameters
51-
*/
52-
$context->registerService('OCA\UserBackendSqlRaw\Db', function (ContainerInterface $container) {
53-
/** @var \OCA\UserBackendSqlRaw\Config $config */
54-
$config = $container->get('OCA\UserBackendSqlRaw\Config');
55-
return $container->get('OCA\UserBackendSqlRaw\Dbs\\' . ucfirst($config->getDbType()));
56-
});
5742
}
5843

5944
public function boot(IBootContext $context): void

0 commit comments

Comments
 (0)