Skip to content
70 changes: 41 additions & 29 deletions lib/private/Setup/MySQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
class MySQL extends AbstractDatabase {
public string $dbprettyname = 'MySQL/MariaDB';

/** @var int MySQL username length limit */
private const MAX_USERNAME_LENGTH = 16;

public function setupDatabase(): void {
//check if the database user has admin right
$connection = $this->connect(['dbname' => null]);
Expand Down Expand Up @@ -58,6 +61,38 @@ public function setupDatabase(): void {
}
}

/**
* Check whether a MySQL user account already exists.
*/
private function userExists(IDBConnection $connection, string $username): bool {
$result = $connection->executeQuery(
'SELECT user FROM mysql.user WHERE user = ?',
[$username]
);

$exists = $result->fetch() !== false;
$result->closeCursor();

return $exists;
}

/**
* Find a username starting from $base that doesn't already exist,
* respecting MySQL's 16-character username limit.
*/
private function findAvailableUsername(IDBConnection $connection, string $base): string {
$candidate = substr($base, 0, self::MAX_USERNAME_LENGTH);

$i = 1;
while ($this->userExists($connection, $candidate)) {
$suffix = (string)$i;
$candidate = substr($base, 0, self::MAX_USERNAME_LENGTH - strlen($suffix)) . $suffix;
$i++;
}

return $candidate;
}

private function createDatabase(\OC\DB\Connection $connection): void {
try {
$name = $this->dbName;
Expand Down Expand Up @@ -143,35 +178,12 @@ private function createSpecificUser(string $username, IDBConnection $connection)
//we don't have a dbuser specified in config
if ($this->dbUser !== $oldUser) {
//add prefix to the admin username to prevent collisions
$adminUser = substr('oc_' . $username, 0, 16);

$i = 1;
while (true) {
//this should be enough to check for admin rights in mysql
$query = 'SELECT user FROM mysql.user WHERE user=?';
$result = $connection->executeQuery($query, [$adminUser]);

//current dbuser has admin rights
$data = $result->fetchAll();
$result->closeCursor();
//new dbuser does not exist
if (count($data) === 0) {
//use the admin login data for the new database user
$this->dbUser = $adminUser;
$this->createDBUser($connection);
// if sharding is used we need to manually call this for every shard as those also need the user setup!
/** @var ConnectionAdapter $connection */
foreach ($connection->getInner()->getShardConnections() as $shard) {
$this->createDBUser($shard);
}

break;
} else {
//repeat with different username
$length = strlen((string)$i);
$adminUser = substr('oc_' . $username, 0, 16 - $length) . $i;
$i++;
}
$this->dbUser = $this->findAvailableUsername($connection, 'oc_' . $username);
Copy link
Copy Markdown
Member

@MichaIng MichaIng Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we fix #59514 right here?

Suggested change
$this->dbUser = $this->findAvailableUsername($connection, 'oc_' . $username);
$this->dbUser = $this->findAvailableUsername($connection, $username);

setupDatabase calls this with hardcoded oc_admin, so the prefix is doubled, resulting in a oc_oc_admin database user. Compare with PostgreSQL, where no additional oc_ is prepended.

$this->createDBUser($connection);
// if sharding is used we need to manually call this for every shard as those also need the user setup!
/** @var ConnectionAdapter $connection */
foreach ($connection->getInner()->getShardConnections() as $shard) {
$this->createDBUser($shard);
}
} else {
// Reuse existing password if a database config is already present
Expand Down
56 changes: 44 additions & 12 deletions lib/private/Setup/PostgreSQL.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use OCP\Server;

class PostgreSQL extends AbstractDatabase {
/** @var int PostgreSQL identifier length limit */
private const MAX_USERNAME_LENGTH = 63;
public $dbprettyname = 'PostgreSQL';

/**
Expand Down Expand Up @@ -103,6 +105,22 @@ public function setupDatabase(): void {
}
}

/**
* Find a role name starting from $base that doesn't already exist.
*/
private function findAvailableUsername(Connection $connection, string $base): string {
$candidate = substr($base, 0, self::MAX_USERNAME_LENGTH);
$suffix = 1;

while ($this->userExists($connection, $candidate)) {
$suffixString = (string)$suffix;
$candidate = substr($base, 0, self::MAX_USERNAME_LENGTH - strlen($suffixString)) . $suffixString;
$suffix++;
}

return $candidate;
}

private function createDatabase(Connection $connection): void {
if (!$this->databaseExists($connection)) {
//The database does not exists... let's create it
Expand All @@ -126,35 +144,49 @@ private function createDatabase(Connection $connection): void {
}
}

private function userExists(Connection $connection): bool {
/**
* Check whether a PostgreSQL role already exists.
*/
private function userExists(Connection $connection, string $username): bool {
$builder = $connection->getQueryBuilder();
$builder->automaticTablePrefix(false);
$query = $builder->select('*')

$query = $builder->select('rolname')
->from('pg_roles')
->where($builder->expr()->eq('rolname', $builder->createNamedParameter($this->dbUser)));
->where(
$builder->expr()->eq(
'rolname',
$builder->createNamedParameter($username)
)
);

$result = $query->executeQuery();
return $result->rowCount() > 0;
$exists = $result->fetch() !== false;
$result->closeCursor();

return $exists;
}

private function databaseExists(Connection $connection): bool {
$builder = $connection->getQueryBuilder();
$builder->automaticTablePrefix(false);

$query = $builder->select('datname')
->from('pg_database')
->where($builder->expr()->eq('datname', $builder->createNamedParameter($this->dbName)));
->where(
$builder->expr()->eq(
'datname',
$builder->createNamedParameter($this->dbName)
)
);

$result = $query->executeQuery();
return $result->rowCount() > 0;
}

private function createDBUser(Connection $connection): void {
$dbUser = $this->dbUser;
$this->dbUser = $this->findAvailableUsername($connection, $this->dbUser);
try {
$i = 1;
while ($this->userExists($connection)) {
$i++;
$this->dbUser = $dbUser . $i;
}

// create the user
$query = $connection->prepare('CREATE USER "' . addslashes($this->dbUser) . "\" CREATEDB PASSWORD '" . addslashes($this->dbPassword) . "'");
$query->executeStatement();
Expand Down
Loading