2013-03-29 11:28:48 -04:00
< ? php
2015-03-26 06:44:34 -04:00
/**
2016-07-21 11:07:57 -04:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2015-03-26 06:44:34 -04:00
* @ author Bart Visscher < bartv @ thisnet . nl >
2021-06-04 15:52:51 -04:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2015-03-26 06:44:34 -04:00
* @ author eduardo < eduardo @ vnexu . net >
2016-07-21 11:07:57 -04:00
* @ author Joas Schilling < coding @ schilljs . com >
2017-11-06 09:56:42 -05:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2016-01-12 09:02:16 -05:00
* @ author Morris Jobke < hey @ morrisjobke . de >
2016-07-21 12:13:36 -04:00
* @ author Robin Appelman < robin @ icewind . nl >
2019-12-03 13:57:53 -05:00
* @ author Vitor Mattos < vitor @ php . rio >
2015-03-26 06:44:34 -04:00
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* 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 , version 3 ,
2019-12-03 13:57:53 -05:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2015-03-26 06:44:34 -04:00
*
*/
2013-03-29 11:28:48 -04:00
namespace OC\Setup ;
2016-07-12 07:50:54 -04:00
use OC\DatabaseException ;
2021-01-03 09:28:31 -05:00
use OC\DB\Connection ;
2016-07-12 07:50:54 -04:00
use OC\DB\QueryBuilder\Literal ;
2021-07-07 11:52:46 -04:00
use OCP\Security\ISecureRandom ;
2016-07-12 07:50:54 -04:00
2013-04-02 16:01:23 -04:00
class PostgreSQL extends AbstractDatabase {
2013-04-03 11:52:18 -04:00
public $dbprettyname = 'PostgreSQL' ;
2017-07-20 16:48:13 -04:00
/**
* @ param string $username
* @ throws \OC\DatabaseSetupException
*/
2013-04-02 16:01:23 -04:00
public function setupDatabase ( $username ) {
2016-07-12 07:50:54 -04:00
try {
2016-07-21 06:44:02 -04:00
$connection = $this -> connect ([
'dbname' => 'postgres'
]);
2023-01-29 09:54:39 -05: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 16:23:25 -05:00
2023-01-29 09:54:39 -05:00
try {
$result = $query -> execute ();
$canCreateRoles = $result -> rowCount () > 0 ;
} catch ( DatabaseException $e ) {
$canCreateRoles = false ;
}
2013-03-29 11:28:48 -04:00
2023-01-29 09:54:39 -05:00
if ( $canCreateRoles ) {
$connectionMainDatabase = $this -> connect ();
//use the admin login data for the new database user
2013-03-29 11:28:48 -04:00
2023-01-29 09:54:39 -05: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
$this -> dbPassword = \OC :: $server -> getSecureRandom () -> generate ( 30 , ISecureRandom :: CHAR_ALPHANUMERIC );
2013-03-29 11:28:48 -04:00
2023-01-29 09:54:39 -05:00
$this -> createDBUser ( $connection );
2022-10-17 22:10:00 -04:00
2023-01-29 09:54:39 -05:00
// 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
2023-03-07 18:37:19 -05:00
$connectionMainDatabase -> executeQuery ( 'GRANT CREATE ON SCHEMA public TO "' . addslashes ( $this -> dbUser ) . '"' );
2023-01-29 09:54:39 -05:00
$connectionMainDatabase -> close ();
}
2016-07-21 06:44:02 -04:00
}
2015-01-23 05:13:47 -05:00
2017-03-17 18:37:48 -04:00
$this -> config -> setValues ([
2016-07-21 06:44:02 -04:00
'dbuser' => $this -> dbUser ,
'dbpassword' => $this -> dbPassword ,
]);
2013-03-29 11:28:48 -04:00
2016-07-21 06:44:02 -04:00
//create the database
$this -> createDatabase ( $connection );
// the connection to dbname=postgres is not needed anymore
$connection -> close ();
} catch ( \Exception $e ) {
2021-04-22 09:22:50 -04: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 18:37:48 -04:00
$this -> config -> setValues ([
2016-07-21 06:44:02 -04:00
'dbuser' => $this -> dbUser ,
'dbpassword' => $this -> dbPassword ,
]);
}
2013-03-29 11:28:48 -04:00
2019-03-06 08:51:17 -05:00
// connect to the database (dbname=$this->dbname) and check if it needs to be filled
2017-03-17 18:37:48 -04:00
$this -> dbUser = $this -> config -> getValue ( 'dbuser' );
$this -> dbPassword = $this -> config -> getValue ( 'dbpassword' );
2016-07-12 07:50:54 -04:00
$connection = $this -> connect ();
try {
$connection -> connect ();
} catch ( \Exception $e ) {
2021-04-22 09:22:50 -04:00
$this -> logger -> error ( $e -> getMessage (), [
'exception' => $e ,
]);
2024-02-13 08:37:09 -05:00
throw new \OC\DatabaseSetupException ( $this -> trans -> t ( 'PostgreSQL Login and/or password not valid' ),
2021-01-07 15:04:11 -05:00
$this -> trans -> t ( 'You need to enter details of an existing account.' ), 0 , $e );
2013-03-29 11:28:48 -04:00
}
}
2021-01-03 09:28:31 -05:00
private function createDatabase ( Connection $connection ) {
2016-07-21 06:44:02 -04:00
if ( ! $this -> databaseExists ( $connection )) {
2013-03-29 11:28:48 -04:00
//The database does not exists... let's create it
2023-03-07 18:37:19 -05:00
$query = $connection -> prepare ( " CREATE DATABASE " . addslashes ( $this -> dbName ) . " OWNER \" " . addslashes ( $this -> dbUser ) . '"' );
2016-07-12 07:50:54 -04:00
try {
$query -> execute ();
} catch ( DatabaseException $e ) {
2021-04-22 09:22:50 -04:00
$this -> logger -> error ( 'Error while trying to create database' , [
'exception' => $e ,
]);
2013-03-29 11:28:48 -04:00
}
2016-07-12 07:50:54 -04:00
} else {
2016-12-09 09:36:14 -05:00
$query = $connection -> prepare ( " REVOKE ALL PRIVILEGES ON DATABASE " . addslashes ( $this -> dbName ) . " FROM PUBLIC " );
2016-07-12 07:50:54 -04:00
try {
$query -> execute ();
} catch ( DatabaseException $e ) {
2021-04-22 09:22:50 -04:00
$this -> logger -> error ( 'Error while trying to restrict database permissions' , [
'exception' => $e ,
]);
2013-03-29 11:28:48 -04:00
}
}
}
2021-01-03 09:28:31 -05:00
private function userExists ( Connection $connection ) {
2016-07-12 07:50:54 -04:00
$builder = $connection -> getQueryBuilder ();
$builder -> automaticTablePrefix ( false );
$query = $builder -> select ( '*' )
-> from ( 'pg_roles' )
-> where ( $builder -> expr () -> eq ( 'rolname' , $builder -> createNamedParameter ( $this -> dbUser )));
$result = $query -> execute ();
return $result -> rowCount () > 0 ;
}
2013-03-29 11:28:48 -04:00
2021-01-03 09:28:31 -05:00
private function databaseExists ( Connection $connection ) {
2016-07-12 07:50:54 -04:00
$builder = $connection -> getQueryBuilder ();
$builder -> automaticTablePrefix ( false );
$query = $builder -> select ( 'datname' )
-> from ( 'pg_database' )
-> where ( $builder -> expr () -> eq ( 'datname' , $builder -> createNamedParameter ( $this -> dbName )));
$result = $query -> execute ();
return $result -> rowCount () > 0 ;
}
2021-01-03 09:28:31 -05:00
private function createDBUser ( Connection $connection ) {
2016-12-06 13:04:57 -05:00
$dbUser = $this -> dbUser ;
2016-07-12 07:50:54 -04:00
try {
2016-12-06 13:04:57 -05:00
$i = 1 ;
while ( $this -> userExists ( $connection )) {
$i ++ ;
$this -> dbUser = $dbUser . $i ;
2018-01-26 17:46:40 -05:00
}
2016-12-06 13:04:57 -05:00
// create the user
2023-03-07 18:37:19 -05:00
$query = $connection -> prepare ( " CREATE USER \" " . addslashes ( $this -> dbUser ) . " \" CREATEDB PASSWORD ' " . addslashes ( $this -> dbPassword ) . " ' " );
2016-07-12 07:50:54 -04:00
$query -> execute ();
2019-04-21 15:54:40 -04:00
if ( $this -> databaseExists ( $connection )) {
2023-03-07 18:37:19 -05:00
$query = $connection -> prepare ( 'GRANT CONNECT ON DATABASE ' . addslashes ( $this -> dbName ) . ' TO "' . addslashes ( $this -> dbUser ) . '"' );
2019-04-21 15:54:40 -04:00
$query -> execute ();
}
2016-07-12 07:50:54 -04:00
} catch ( DatabaseException $e ) {
2021-04-22 09:22:50 -04:00
$this -> logger -> error ( 'Error while trying to create database user' , [
'exception' => $e ,
]);
2013-03-29 11:28:48 -04:00
}
}
}