2013-03-29 16:28:48 +01:00
< ? php
2024-05-23 09:26:56 +02:00
2015-03-26 11:44:34 +01:00
/**
2024-05-23 09:26:56 +02:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2015-03-26 11:44:34 +01:00
*/
2013-03-29 16:28:48 +01:00
namespace OC\Setup ;
2016-07-12 13:50:54 +02:00
use OC\DatabaseException ;
2021-01-03 15:28:31 +01:00
use OC\DB\Connection ;
2016-07-12 13:50:54 +02:00
use OC\DB\QueryBuilder\Literal ;
2021-07-07 17:52:46 +02:00
use OCP\Security\ISecureRandom ;
2016-07-12 13:50:54 +02:00
2013-04-02 22:01:23 +02:00
class PostgreSQL extends AbstractDatabase {
2013-04-03 17:52:18 +02:00
public $dbprettyname = 'PostgreSQL' ;
2017-07-20 22:48:13 +02:00
/**
* @ param string $username
* @ throws \OC\DatabaseSetupException
*/
2013-04-02 22:01:23 +02:00
public function setupDatabase ( $username ) {
2016-07-12 13:50:54 +02:00
try {
2016-07-21 12:44:02 +02:00
$connection = $this -> connect ([
'dbname' => 'postgres'
]);
2023-01-29 15:54:39 +01:00
if ( $this -> tryCreateDbUser ) {
//check for roles creation rights in postgresql
$builder = $connection -> getQueryBuilder ();
$builder -> automaticTablePrefix ( false );
$query = $builder
-> select ( 'rolname' )
-> from ( 'pg_roles' )
-> where ( $builder -> expr () -> eq ( 'rolcreaterole' , new Literal ( 'TRUE' )))
-> andWhere ( $builder -> expr () -> eq ( 'rolname' , $builder -> createNamedParameter ( $this -> dbUser )));
2014-01-04 19:23:25 -02:00
2023-01-29 15:54:39 +01:00
try {
$result = $query -> execute ();
$canCreateRoles = $result -> rowCount () > 0 ;
} catch ( DatabaseException $e ) {
$canCreateRoles = false ;
}
2023-06-13 00:29:52 +08:00
2023-01-29 15:54:39 +01:00
if ( $canCreateRoles ) {
$connectionMainDatabase = $this -> connect ();
//use the admin login data for the new database user
2013-03-29 16:28:48 +01:00
2023-01-29 15:54:39 +01:00
//add prefix to the postgresql user name to prevent collisions
$this -> dbUser = 'oc_' . strtolower ( $username );
//create a new password so we don't need to store the admin config in the config file
2023-08-29 16:29:33 -05:00
$this -> dbPassword = \OC :: $server -> get ( ISecureRandom :: class ) -> generate ( 30 , ISecureRandom :: CHAR_ALPHANUMERIC );
2013-03-29 16:28:48 +01:00
2023-01-29 15:54:39 +01:00
$this -> createDBUser ( $connection );
}
2016-07-21 12:44:02 +02:00
}
2015-01-23 11:13:47 +01:00
2017-03-17 16:37:48 -06:00
$this -> config -> setValues ([
2016-07-21 12:44:02 +02:00
'dbuser' => $this -> dbUser ,
'dbpassword' => $this -> dbPassword ,
]);
2013-03-29 16:28:48 +01:00
2016-07-21 12:44:02 +02:00
//create the database
$this -> createDatabase ( $connection );
// the connection to dbname=postgres is not needed anymore
$connection -> close ();
2023-06-12 18:13:34 +08:00
2023-06-13 00:29:52 +08:00
if ( $this -> tryCreateDbUser ) {
if ( $canCreateRoles ) {
// Go to the main database and grant create on the public schema
// The code below is implemented to make installing possible with PostgreSQL version 15:
// https://www.postgresql.org/docs/release/15.0/
// From the release notes: For new databases having no need to defend against insider threats, granting CREATE permission will yield the behavior of prior releases
// Therefore we assume that the database is only used by one user/service which is Nextcloud
// Additional services should get installed in a separate database in order to stay secure
// Also see https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PATTERNS
$connectionMainDatabase -> executeQuery ( 'GRANT CREATE ON SCHEMA public TO "' . addslashes ( $this -> dbUser ) . '"' );
$connectionMainDatabase -> close ();
}
2023-06-12 18:13:34 +08:00
}
2016-07-21 12:44:02 +02:00
} catch ( \Exception $e ) {
2021-04-22 15:22:50 +02:00
$this -> logger -> warning ( 'Error trying to connect as "postgres", assuming database is setup and tables need to be created' , [
'exception' => $e ,
]);
2017-03-17 16:37:48 -06:00
$this -> config -> setValues ([
2016-07-21 12:44:02 +02:00
'dbuser' => $this -> dbUser ,
'dbpassword' => $this -> dbPassword ,
]);
}
2013-03-29 16:28:48 +01:00
2019-03-06 14:51:17 +01:00
// connect to the database (dbname=$this->dbname) and check if it needs to be filled
2017-03-17 16:37:48 -06:00
$this -> dbUser = $this -> config -> getValue ( 'dbuser' );
$this -> dbPassword = $this -> config -> getValue ( 'dbpassword' );
2016-07-12 13:50:54 +02:00
$connection = $this -> connect ();
try {
$connection -> connect ();
} catch ( \Exception $e ) {
2021-04-22 15:22:50 +02:00
$this -> logger -> error ( $e -> getMessage (), [
'exception' => $e ,
]);
2024-02-13 14:37:09 +01:00
throw new \OC\DatabaseSetupException ( $this -> trans -> t ( 'PostgreSQL Login and/or password not valid' ),
2021-01-07 21:04:11 +01:00
$this -> trans -> t ( 'You need to enter details of an existing account.' ), 0 , $e );
2013-03-29 16:28:48 +01:00
}
}
2021-01-03 15:28:31 +01:00
private function createDatabase ( Connection $connection ) {
2016-07-21 12:44:02 +02:00
if ( ! $this -> databaseExists ( $connection )) {
2013-03-29 16:28:48 +01:00
//The database does not exists... let's create it
2023-03-08 00:37:19 +01:00
$query = $connection -> prepare ( 'CREATE DATABASE ' . addslashes ( $this -> dbName ) . ' OWNER "' . addslashes ( $this -> dbUser ) . '"' );
2016-07-12 13:50:54 +02:00
try {
$query -> execute ();
} catch ( DatabaseException $e ) {
2021-04-22 15:22:50 +02:00
$this -> logger -> error ( 'Error while trying to create database' , [
'exception' => $e ,
]);
2013-03-29 16:28:48 +01:00
}
2016-07-12 13:50:54 +02:00
} else {
2016-12-09 15:36:14 +01:00
$query = $connection -> prepare ( 'REVOKE ALL PRIVILEGES ON DATABASE ' . addslashes ( $this -> dbName ) . ' FROM PUBLIC' );
2016-07-12 13:50:54 +02:00
try {
$query -> execute ();
} catch ( DatabaseException $e ) {
2021-04-22 15:22:50 +02:00
$this -> logger -> error ( 'Error while trying to restrict database permissions' , [
'exception' => $e ,
]);
2013-03-29 16:28:48 +01:00
}
}
}
2021-01-03 15:28:31 +01:00
private function userExists ( Connection $connection ) {
2016-07-12 13:50:54 +02:00
$builder = $connection -> getQueryBuilder ();
$builder -> automaticTablePrefix ( false );
$query = $builder -> select ( '*' )
-> from ( 'pg_roles' )
-> where ( $builder -> expr () -> eq ( 'rolname' , $builder -> createNamedParameter ( $this -> dbUser )));
2024-10-17 15:42:21 +02:00
$result = $query -> executeQuery ();
2016-07-12 13:50:54 +02:00
return $result -> rowCount () > 0 ;
}
2013-03-29 16:28:48 +01:00
2021-01-03 15:28:31 +01:00
private function databaseExists ( Connection $connection ) {
2016-07-12 13:50:54 +02:00
$builder = $connection -> getQueryBuilder ();
$builder -> automaticTablePrefix ( false );
$query = $builder -> select ( 'datname' )
-> from ( 'pg_database' )
-> where ( $builder -> expr () -> eq ( 'datname' , $builder -> createNamedParameter ( $this -> dbName )));
2024-10-17 15:42:21 +02:00
$result = $query -> executeQuery ();
2016-07-12 13:50:54 +02:00
return $result -> rowCount () > 0 ;
}
2021-01-03 15:28:31 +01:00
private function createDBUser ( Connection $connection ) {
2016-12-06 19:04:57 +01:00
$dbUser = $this -> dbUser ;
2016-07-12 13:50:54 +02:00
try {
2016-12-06 19:04:57 +01:00
$i = 1 ;
while ( $this -> userExists ( $connection )) {
$i ++ ;
$this -> dbUser = $dbUser . $i ;
2018-01-26 23:46:40 +01:00
}
2016-12-06 19:04:57 +01:00
// create the user
2023-03-08 00:37:19 +01:00
$query = $connection -> prepare ( 'CREATE USER "' . addslashes ( $this -> dbUser ) . " \" CREATEDB PASSWORD ' " . addslashes ( $this -> dbPassword ) . " ' " );
2016-07-12 13:50:54 +02:00
$query -> execute ();
2019-04-21 16:54:40 -03:00
if ( $this -> databaseExists ( $connection )) {
2023-03-08 00:37:19 +01:00
$query = $connection -> prepare ( 'GRANT CONNECT ON DATABASE ' . addslashes ( $this -> dbName ) . ' TO "' . addslashes ( $this -> dbUser ) . '"' );
2019-04-21 16:54:40 -03:00
$query -> execute ();
}
2016-07-12 13:50:54 +02:00
} catch ( DatabaseException $e ) {
2021-04-22 15:22:50 +02:00
$this -> logger -> error ( 'Error while trying to create database user' , [
'exception' => $e ,
]);
2013-03-29 16:28:48 +01:00
}
}
}