Skip to content

Commit e676018

Browse files
[Fix] Mysql Users + Postgres Host fix (#1161)
* wip * fixes * fix
1 parent 882e428 commit e676018

18 files changed

Lines changed: 505 additions & 175 deletions

app/Actions/Database/CreateDatabaseUser.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Models\Server;
88
use App\Models\Service;
99
use App\Services\Database\Database;
10+
use Closure;
1011
use Illuminate\Support\Facades\Validator;
1112
use Illuminate\Validation\Rule;
1213
use Illuminate\Validation\ValidationException;
@@ -27,7 +28,7 @@ public function create(Server $server, array $input, array $links = []): Databas
2728
'server_id' => $server->id,
2829
'username' => $input['username'],
2930
'password' => $input['password'],
30-
'host' => (isset($input['remote']) && $input['remote']) || isset($input['host']) ? $input['host'] : 'localhost',
31+
'host' => $this->resolveHost($input),
3132
'databases' => $links,
3233
'permission' => $input['permission'] ?? 'admin',
3334
]);
@@ -56,11 +57,19 @@ public function create(Server $server, array $input, array $links = []): Databas
5657

5758
private function validate(Server $server, array $input): void
5859
{
60+
/** @var Database $handler */
61+
$handler = $server->database()->handler();
62+
$host = $this->resolveHost($input);
63+
5964
$rules = [
6065
'username' => [
6166
'required',
6267
'alpha_dash',
63-
Rule::unique('database_users', 'username')->where('server_id', $server->id),
68+
function (string $attribute, mixed $value, Closure $fail) use ($handler, $server, $host): void {
69+
if ($handler->databaseUserExists($server, (string) $value, $host)) {
70+
$fail(__('A database user with this username and host already exists.'));
71+
}
72+
},
6473
],
6574
'password' => [
6675
'required',
@@ -71,10 +80,26 @@ private function validate(Server $server, array $input): void
7180
Rule::in(['read', 'write', 'admin']),
7281
],
7382
];
74-
if (isset($input['remote']) && $input['remote']) {
75-
$rules['host'] = 'required';
83+
84+
if ($handler->usesHost()) {
85+
$rules['host'] = [
86+
isset($input['remote']) && $input['remote'] ? 'required' : 'nullable',
87+
'regex:/^[A-Za-z0-9%._:\-]*$/',
88+
];
7689
}
7790

7891
Validator::make($input, $rules)->validate();
7992
}
93+
94+
/**
95+
* @param array<string, mixed> $input
96+
*/
97+
private function resolveHost(array $input): string
98+
{
99+
if (! empty($input['host'])) {
100+
return $input['host'];
101+
}
102+
103+
return ! empty($input['remote']) ? '%' : 'localhost';
104+
}
80105
}

app/Actions/Database/UpdateDatabaseUser.php

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,11 @@ public function update(DatabaseUser $databaseUser, array $input): DatabaseUser
2424
$oldHost = $databaseUser->host;
2525
$oldPermission = $databaseUser->permission;
2626
$newPassword = $input['password'] ?? null;
27-
$newHost = null;
27+
$newHost = $this->changedHost($databaseUser, $input);
2828
$permissionChanged = false;
2929

30-
if (isset($input['remote'])) {
31-
$newHost = $input['remote'] ? ($input['host'] ?? '%') : 'localhost';
32-
if ($newHost !== $oldHost) {
33-
$databaseUser->host = $newHost;
34-
} else {
35-
$newHost = null;
36-
}
30+
if ($newHost !== null) {
31+
$databaseUser->host = $newHost;
3732
}
3833

3934
if ($newPassword) {
@@ -79,8 +74,14 @@ private function validate(DatabaseUser $databaseUser, array $input): void
7974
];
8075
}
8176

82-
if (isset($input['remote']) && $input['remote']) {
83-
$rules['host'] = 'required';
77+
/** @var Database $handler */
78+
$handler = $databaseUser->server->database()->handler();
79+
80+
if ($handler->usesHost()) {
81+
$rules['host'] = [
82+
isset($input['remote']) && $input['remote'] ? 'required' : 'nullable',
83+
'regex:/^[A-Za-z0-9%._:\-]*$/',
84+
];
8485
}
8586

8687
$rules['permission'] = [
@@ -89,6 +90,35 @@ private function validate(DatabaseUser $databaseUser, array $input): void
8990
];
9091

9192
Validator::make($input, $rules)->validate();
93+
94+
$newHost = $this->changedHost($databaseUser, $input);
95+
if ($newHost !== null && $handler->databaseUserExists($databaseUser->server, $databaseUser->username, $newHost, $databaseUser)) {
96+
throw ValidationException::withMessages([
97+
'host' => __('A database user with this username and host already exists.'),
98+
]);
99+
}
100+
}
101+
102+
/**
103+
* Resolves the new host when it is being changed, or null when unchanged or
104+
* the database engine does not use hosts (e.g. PostgreSQL).
105+
*
106+
* @param array<string, mixed> $input
107+
*/
108+
private function changedHost(DatabaseUser $databaseUser, array $input): ?string
109+
{
110+
if (! isset($input['remote'])) {
111+
return null;
112+
}
113+
114+
$handler = $databaseUser->server->database()?->handler();
115+
if (! $handler instanceof Database || ! $handler->usesHost()) {
116+
return null;
117+
}
118+
119+
$resolved = $input['remote'] ? (! empty($input['host']) ? $input['host'] : '%') : 'localhost';
120+
121+
return $resolved !== $databaseUser->host ? $resolved : null;
92122
}
93123

94124
private function updatePermissions(DatabaseUser $databaseUser, string $oldHost, ?string $newHost): void

app/Enums/DatabaseUserPermission.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace App\Enums;
44

55
use App\Contracts\VitoEnum;
6+
use Forjed\InertiaTable\Contracts\HasTableDisplay;
67

7-
enum DatabaseUserPermission: string implements VitoEnum
8+
enum DatabaseUserPermission: string implements HasTableDisplay, VitoEnum
89
{
910
case READ = 'read';
1011
case WRITE = 'write';

app/Enums/DatabaseUserStatus.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
namespace App\Enums;
44

55
use App\Contracts\VitoEnum;
6+
use Forjed\InertiaTable\Contracts\HasTableDisplay;
67

7-
enum DatabaseUserStatus: string implements VitoEnum
8+
enum DatabaseUserStatus: string implements HasTableDisplay, VitoEnum
89
{
910
case READY = 'ready';
1011
case CREATING = 'creating';

app/Http/Controllers/DatabaseUserController.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
use App\Http\Resources\DatabaseUserResource;
1212
use App\Models\DatabaseUser;
1313
use App\Models\Server;
14+
use App\Services\Database\Database;
15+
use App\Tables\Servers\DatabaseUserTable;
1416
use Illuminate\Http\RedirectResponse;
1517
use Illuminate\Http\Request;
1618
use Illuminate\Http\Resources\Json\ResourceCollection;
@@ -33,9 +35,15 @@ public function index(Server $server): Response
3335
{
3436
$this->authorize('viewAny', [DatabaseUser::class, $server]);
3537

38+
/** @var Database $handler */
39+
$handler = $server->database()->handler();
40+
3641
return Inertia::render('database-users/index', [
3742
'databases' => DatabaseResource::collection($server->databases()->get()),
38-
'databaseUsers' => DatabaseUserResource::collection($server->databaseUsers()->simplePaginate(config('web.pagination_size'))),
43+
'usesHost' => $handler->usesHost(),
44+
'databaseUsers' => DatabaseUserTable::make($server->databaseUsers())
45+
->withHost($handler->usesHost())
46+
->paginate(),
3947
]);
4048
}
4149

app/Http/Resources/DatabaseUserResource.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ public function toArray(Request $request): array
2020
'username' => $this->username,
2121
'databases' => $this->databases,
2222
'host' => $this->host,
23-
'permission' => $this->permission,
23+
'permission' => $this->permission->getText(),
24+
'permission_color' => $this->permission->getColor(),
2425
'status' => $this->status->getText(),
2526
'status_color' => $this->status->getColor(),
2627
'created_at' => $this->created_at,

app/Services/Database/AbstractDatabase.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use App\Exceptions\ServiceInstallationFailed;
77
use App\Exceptions\SSHError;
88
use App\Models\BackupFile;
9+
use App\Models\DatabaseUser;
10+
use App\Models\Server;
911
use App\Services\AbstractService;
1012
use Closure;
1113
use Illuminate\Contracts\View\View;
@@ -35,6 +37,20 @@ protected function getScriptView(string $script): string
3537
return 'ssh.services.database.'.$this->service->name.'.'.$script;
3638
}
3739

40+
public function usesHost(): bool
41+
{
42+
return true;
43+
}
44+
45+
public function databaseUserExists(Server $server, string $username, string $host, ?DatabaseUser $ignore = null): bool
46+
{
47+
return $server->databaseUsers()
48+
->where('username', $username)
49+
->where('host', $host)
50+
->when($ignore, fn ($query) => $query->whereKeyNot($ignore->id))
51+
->exists();
52+
}
53+
3854
public function creationRules(array $input): array
3955
{
4056
return [

app/Services/Database/Database.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
namespace App\Services\Database;
44

55
use App\Models\BackupFile;
6+
use App\Models\DatabaseUser;
7+
use App\Models\Server;
68
use App\Services\ServiceInterface;
79

810
interface Database extends ServiceInterface
911
{
12+
public function usesHost(): bool;
13+
14+
public function databaseUserExists(Server $server, string $username, string $host, ?DatabaseUser $ignore = null): bool;
15+
1016
public function create(string $name, string $charset, string $collation): void;
1117

1218
public function delete(string $name): void;

app/Services/Database/Postgresql.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace App\Services\Database;
44

55
use App\DTOs\ServiceLog;
6+
use App\Models\DatabaseUser;
7+
use App\Models\Server;
68
use App\Services\HasLogs;
79
use Illuminate\Contracts\View\View;
810

@@ -23,6 +25,19 @@ class Postgresql extends AbstractDatabase implements HasLogs
2325

2426
protected bool $removeLastRow = true;
2527

28+
public function usesHost(): bool
29+
{
30+
return false;
31+
}
32+
33+
public function databaseUserExists(Server $server, string $username, string $host, ?DatabaseUser $ignore = null): bool
34+
{
35+
return $server->databaseUsers()
36+
->where('username', $username)
37+
->when($ignore, fn ($query) => $query->whereKeyNot($ignore->id))
38+
->exists();
39+
}
40+
2641
public static function id(): string
2742
{
2843
return 'postgresql';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Tables\Servers;
4+
5+
use Forjed\InertiaTable\Column;
6+
use Forjed\InertiaTable\Columns\ActionsColumn;
7+
use Forjed\InertiaTable\Columns\ComponentColumn;
8+
use Forjed\InertiaTable\Columns\DateTimeColumn;
9+
use Forjed\InertiaTable\Columns\EnumColumn;
10+
use Forjed\InertiaTable\Columns\TextColumn;
11+
use Forjed\InertiaTable\Table;
12+
13+
class DatabaseUserTable extends Table
14+
{
15+
protected bool $showHost = false;
16+
17+
public function withHost(bool $showHost = true): static
18+
{
19+
$this->showHost = $showHost;
20+
21+
return $this;
22+
}
23+
24+
protected function query(): void
25+
{
26+
$this->perPage = config('web.pagination_size');
27+
$this->query->latest();
28+
}
29+
30+
protected function columns(): array
31+
{
32+
return [
33+
TextColumn::make('username', 'Username')->sortable(),
34+
$this->showHost
35+
? TextColumn::make('host', 'Host')->sortable()
36+
: Column::data('host'),
37+
EnumColumn::make('permission', 'Permission'),
38+
ComponentColumn::create('databases', 'Linked databases', 'DatabaseUserDatabases'),
39+
DateTimeColumn::make('created_at', 'Created at')->sortable()->toLocal(),
40+
EnumColumn::make('status', 'Status')->sortable(),
41+
Column::data('id'),
42+
Column::data('server_id'),
43+
ActionsColumn::make(),
44+
];
45+
}
46+
}

0 commit comments

Comments
 (0)