From c431a7b617c983066ede9a1c471ba6c677182937 Mon Sep 17 00:00:00 2001 From: Anush <33168386+AnushK-Fro@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:57:28 -0500 Subject: [PATCH 01/13] Add support for setting passwords on Windows Server and utilize QEMU Guest Agent for setting passwords on Linux --- app/Jobs/Server/SyncWindowsSettings.php | 71 +++++++++++++++++++ .../Server/ProxmoxGuestAgentRepository.php | 61 ++++++++++++++++ app/Services/Servers/ServerAuthService.php | 28 +++++--- .../Servers/ServerBuildDispatchService.php | 16 ++++- 4 files changed, 165 insertions(+), 11 deletions(-) create mode 100644 app/Jobs/Server/SyncWindowsSettings.php create mode 100644 app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php diff --git a/app/Jobs/Server/SyncWindowsSettings.php b/app/Jobs/Server/SyncWindowsSettings.php new file mode 100644 index 00000000000..71521b21417 --- /dev/null +++ b/app/Jobs/Server/SyncWindowsSettings.php @@ -0,0 +1,71 @@ +serverId}", + )]; + } + + public function handle(ServerAuthService $service): void + { + $server = Server::findOrFail($this->serverId); + + $service->updateWindowsPassword($server, $this->password); + } + + /** + * Determine the time at which the job should retry. + * + * @return \DateTime + */ + public function retryAfter(): \DateTime + { + return now()->addSeconds(20); + } + + /** + * Determine the time at which the job should timeout. + * + * @return \DateTime + */ + public function retryUntil(): \DateTime + { + return now()->addSeconds($this->timeout * $this->tries); + } + + /** + * Handle a job failure. + * + * @return void + */ + public function failed(): void + { + // Mark the job as completed + $this->delete(); + } +} \ No newline at end of file diff --git a/app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php b/app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php new file mode 100644 index 00000000000..d9752f5f2b4 --- /dev/null +++ b/app/Repositories/Proxmox/Server/ProxmoxGuestAgentRepository.php @@ -0,0 +1,61 @@ +server, Server::class); + + $response = $this->getHttpClient() + ->withUrlParameters([ + 'node' => $this->node->cluster, + 'server' => $this->server->vmid, + ]) + ->get('/api2/json/nodes/{node}/qemu/{server}/agent/get-osinfo') + ->json(); + + return $this->getData($response); + } + + /** + * Update Guest Agent password for Administrator user. + * + * @param string $password + * @return mixed + * + * @throws ProxmoxConnectionException + */ + public function updateGuestAgentPassword(string $username, string $password) + { + Assert::isInstanceOf($this->server, Server::class); + + $params = [ + 'username' => $username, + 'password' => $password, + ]; + + $response = $this->getHttpClient() + ->withUrlParameters([ + 'node' => $this->node->cluster, + 'server' => $this->server->vmid, + ]) + ->post('/api2/json/nodes/{node}/qemu/{server}/agent/set-user-password', $params) + ->json(); + + return $this->getData($response); + } +} \ No newline at end of file diff --git a/app/Services/Servers/ServerAuthService.php b/app/Services/Servers/ServerAuthService.php index 590948888e1..e53f2defad6 100644 --- a/app/Services/Servers/ServerAuthService.php +++ b/app/Services/Servers/ServerAuthService.php @@ -4,20 +4,32 @@ use Convoy\Models\Server; use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository; +use Convoy\Repositories\Proxmox\Server\ProxmoxGuestAgentRepository; class ServerAuthService { - public function __construct(private ProxmoxConfigRepository $configRepository) + public function __construct(private ProxmoxConfigRepository $configRepository, private ProxmoxGuestAgentRepository $guestAgentRepository) { } public function updatePassword(Server $server, string $password) { - // if (!empty($password)) { - $this->configRepository->setServer($server)->update(['cipassword' => $password]); - // } else { - // $this->configRepository->setServer($server)->update(['delete' => 'cipassword']); - // } + try { + $OsInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); + if (str_contains($OsInfo["result"]["name"], "Windows")) { + $username = "Administrator"; + } else { + $username = "root"; + } + $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); + $this->configRepository->setServer($server)->update(['cipassword' => $password]); + } catch (\Exception $e) { + $this->configRepository->setServer($server)->update(['cipassword' => $password]); + } + } + + public function updateWindowsPassword(Server $server, string $password) { + $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword("Administrator", $password); } public function getSSHKeys(Server $server) @@ -29,10 +41,10 @@ public function getSSHKeys(Server $server) public function updateSSHKeys(Server $server, ?string $keys) { - if (! empty($keys)) { + if (!empty($keys)) { $this->configRepository->setServer($server)->update(['sshkeys' => rawurlencode($keys)]); } else { $this->configRepository->setServer($server)->update(['delete' => 'sshkeys']); } } -} +} \ No newline at end of file diff --git a/app/Services/Servers/ServerBuildDispatchService.php b/app/Services/Servers/ServerBuildDispatchService.php index 528d81e78fb..bf2e4a312d7 100644 --- a/app/Services/Servers/ServerBuildDispatchService.php +++ b/app/Services/Servers/ServerBuildDispatchService.php @@ -15,6 +15,7 @@ use Convoy\Jobs\Server\SendPowerCommandJob; use Convoy\Jobs\Server\WaitUntilVmIsCreatedJob; use Convoy\Jobs\Server\WaitUntilVmIsDeletedJob; +use Convoy\Jobs\Server\SyncWindowsSettings; use Convoy\Data\Server\Deployments\ServerDeploymentData; class ServerBuildDispatchService @@ -67,11 +68,20 @@ private function getChainedBuildJobs(ServerDeploymentData $deployment): array ]; } - if (! empty($deployment->account_password)) { + $startOnce = false; + + // TODO: Readd the start_on_completion check + + if (!empty($deployment->account_password)) { $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); + if (str_contains($deployment->template->name, "Windows")) { + $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); + $startOnce = true; + $jobs[] = new SyncWindowsSettings($deployment->server->id, $deployment->account_password); + } } - if ($deployment->start_on_completion) { + if (!$startOnce) { $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); } @@ -91,4 +101,4 @@ public function getChainedDeleteJobs(Server $server): array new WaitUntilVmIsDeletedJob($server->id), ]; } -} +} \ No newline at end of file From 3c6200ca7dad14b5a80e9e768a1295e5e8d31545 Mon Sep 17 00:00:00 2001 From: Anush <33168386+AnushK-Fro@users.noreply.github.com> Date: Sat, 9 Nov 2024 21:06:26 -0500 Subject: [PATCH 02/13] Increases pagination limit to 999999 for IP Addresses --- app/Http/Controllers/Admin/AddressPools/AddressController.php | 2 +- app/Http/Controllers/Admin/Nodes/AddressController.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Admin/AddressPools/AddressController.php b/app/Http/Controllers/Admin/AddressPools/AddressController.php index e2c4f2409e1..5f287fc98e1 100644 --- a/app/Http/Controllers/Admin/AddressPools/AddressController.php +++ b/app/Http/Controllers/Admin/AddressPools/AddressController.php @@ -48,7 +48,7 @@ public function index(Request $request, AddressPool $addressPool) AllowedFilter::exact('server_id')->nullable(), ], ) - ->paginate(min($request->query('per_page', 50), 100))->appends( + ->paginate(min($request->query('per_page', 50), 999999))->appends( $request->query(), ); diff --git a/app/Http/Controllers/Admin/Nodes/AddressController.php b/app/Http/Controllers/Admin/Nodes/AddressController.php index c7bd5afb942..ff633ec02b3 100644 --- a/app/Http/Controllers/Admin/Nodes/AddressController.php +++ b/app/Http/Controllers/Admin/Nodes/AddressController.php @@ -25,7 +25,7 @@ public function index(Request $request, Node $node) new FiltersAddressWildcard(), ), AllowedFilter::exact('server_id')->nullable()], ) - ->paginate(min($request->query('per_page', 50), 100))->appends( + ->paginate(min($request->query('per_page', 50), 999999))->appends( $request->query(), ); From 059a6557a2a2b8ed5435c6c12db3f2678bd56ae5 Mon Sep 17 00:00:00 2001 From: Anush <33168386+AnushK-Fro@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:00:28 -0500 Subject: [PATCH 03/13] Fixes problem with test not passing --- app/Services/Servers/ServerAuthService.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/Services/Servers/ServerAuthService.php b/app/Services/Servers/ServerAuthService.php index e53f2defad6..7c2caac9070 100644 --- a/app/Services/Servers/ServerAuthService.php +++ b/app/Services/Servers/ServerAuthService.php @@ -16,12 +16,15 @@ public function updatePassword(Server $server, string $password) { try { $OsInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); - if (str_contains($OsInfo["result"]["name"], "Windows")) { - $username = "Administrator"; - } else { - $username = "root"; + if (is_array($OsInfo) && isset($OsInfo["result"]["name"])) { + if (str_contains($OsInfo["result"]["name"], "Windows")) { + $username = "Administrator"; + } else { + $username = "root"; + } + + $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); } - $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); $this->configRepository->setServer($server)->update(['cipassword' => $password]); } catch (\Exception $e) { $this->configRepository->setServer($server)->update(['cipassword' => $password]); From c01ca604e3b02833b82e64e7908184ee965b2c8e Mon Sep 17 00:00:00 2001 From: Mohammad Raska <42745054+Adekabang@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:27:49 -0500 Subject: [PATCH 04/13] fix:unauth user access users --- resources/scripts/routers/AdminUserRouter.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/resources/scripts/routers/AdminUserRouter.tsx b/resources/scripts/routers/AdminUserRouter.tsx index b64b2d609ce..702356fd951 100644 --- a/resources/scripts/routers/AdminUserRouter.tsx +++ b/resources/scripts/routers/AdminUserRouter.tsx @@ -24,10 +24,15 @@ export const routes: Route[] = [ { path: ':id', element: lazyLoad(lazy(() => import('./AdminUserRouter'))), - loader: ({ params }) => - query(getKey(parseInt(params.id!)), () => - getUser(parseInt(params.id!)) - ), + loader: async ({ params }) => { + try { + const id = parseInt(params.id!); + const user = await query(getKey(id), () => getUser(id)); + return user; + } catch (error) { + return null; + } + }, children: [ { path: 'settings', From 8329e4e797b556a1e740a4f9ee5723e0222eaaf6 Mon Sep 17 00:00:00 2001 From: Mohammad Raska <42745054+Adekabang@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:28:38 -0500 Subject: [PATCH 05/13] fix:unauth user access nodes --- resources/scripts/routers/AdminNodeRouter.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/resources/scripts/routers/AdminNodeRouter.tsx b/resources/scripts/routers/AdminNodeRouter.tsx index bd99474054e..ebbe9fbe8ef 100644 --- a/resources/scripts/routers/AdminNodeRouter.tsx +++ b/resources/scripts/routers/AdminNodeRouter.tsx @@ -25,10 +25,15 @@ export const routes: Route[] = [ }, { path: ':nodeId', - loader: ({ params }) => - query(getPoolKey(parseInt(params.nodeId!)), () => - getNode(parseInt(params.nodeId!)) - ), + loader: async ({ params }) => { + try { + const nodeId = parseInt(params.nodeId!); + const node = await query(getPoolKey(nodeId), () => getNode(nodeId)); + return node; + } catch (error) { + return null; + } + }, element: lazyLoad( lazy(() => import('@/routers/AdminNodeRouter')) ), From 2181f63be1de23e272c81bff05946cd6dd02684d Mon Sep 17 00:00:00 2001 From: Mohammad Raska <42745054+Adekabang@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:29:17 -0500 Subject: [PATCH 06/13] fix:unauth user access IPAM --- resources/scripts/routers/AdminIpamRouter.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/resources/scripts/routers/AdminIpamRouter.tsx b/resources/scripts/routers/AdminIpamRouter.tsx index 9378e3f7068..6312e0b08d5 100644 --- a/resources/scripts/routers/AdminIpamRouter.tsx +++ b/resources/scripts/routers/AdminIpamRouter.tsx @@ -31,10 +31,15 @@ export const routes: Route[] = [ }, { path: ':poolId', - loader: ({ params }) => - query(getPoolKey(parseInt(params.poolId!)), () => - getAddressPool(parseInt(params.poolId!)) - ), + loader: async ({ params }) => { + try { + const poolId = parseInt(params.poolId!); + const pool = await query(getPoolKey(poolId), () => getAddressPool(poolId)); + return pool; + } catch (error) { + return null; + } + }, element: lazyLoad(lazy(() => import('./AdminIpamRouter'))), handle: { crumb: data => ({ From 8bdecd12692189d4c44665a0ee173ec7b228bc90 Mon Sep 17 00:00:00 2001 From: Mohammad Raska <42745054+Adekabang@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:44:47 -0500 Subject: [PATCH 07/13] fix:unauth user access IPAM settings --- resources/scripts/routers/AdminIpamRouter.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/resources/scripts/routers/AdminIpamRouter.tsx b/resources/scripts/routers/AdminIpamRouter.tsx index 6312e0b08d5..ca22c7a7310 100644 --- a/resources/scripts/routers/AdminIpamRouter.tsx +++ b/resources/scripts/routers/AdminIpamRouter.tsx @@ -54,17 +54,21 @@ export const routes: Route[] = [ }, { path: 'addresses', - loader: ({ params }) => { - const id = parseInt(params.poolId!) - const page = params.page ? parseInt(params.page) : 1 - - return query(getAddressesKey(id, page, ''), () => - getAddresses(id, { - page, - query: '', - include: ['server'], - }) - ) + loader: async ({ params }) => { + try { + const id = parseInt(params.poolId!) + const page = params.page ? parseInt(params.page) : 1 + const addresses = await query(getAddressesKey(id, page, ''), () => + getAddresses(id, { + page, + query: '', + include: ['server'], + }) + ) + return addresses + } catch (error) { + return null + } }, element: lazyLoad( lazy( From bc9aada574d3c52c35bd48666161808364dc9c55 Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Tue, 31 Dec 2024 22:43:44 -0600 Subject: [PATCH 08/13] Refactor Guest Agent Password Changing --- app/Jobs/Server/SyncWindowsSettings.php | 71 ------------------- app/Services/Servers/ServerAuthService.php | 40 +++++------ .../Servers/ServerBuildDispatchService.php | 56 ++++++++------- 3 files changed, 50 insertions(+), 117 deletions(-) delete mode 100644 app/Jobs/Server/SyncWindowsSettings.php diff --git a/app/Jobs/Server/SyncWindowsSettings.php b/app/Jobs/Server/SyncWindowsSettings.php deleted file mode 100644 index 71521b21417..00000000000 --- a/app/Jobs/Server/SyncWindowsSettings.php +++ /dev/null @@ -1,71 +0,0 @@ -serverId}", - )]; - } - - public function handle(ServerAuthService $service): void - { - $server = Server::findOrFail($this->serverId); - - $service->updateWindowsPassword($server, $this->password); - } - - /** - * Determine the time at which the job should retry. - * - * @return \DateTime - */ - public function retryAfter(): \DateTime - { - return now()->addSeconds(20); - } - - /** - * Determine the time at which the job should timeout. - * - * @return \DateTime - */ - public function retryUntil(): \DateTime - { - return now()->addSeconds($this->timeout * $this->tries); - } - - /** - * Handle a job failure. - * - * @return void - */ - public function failed(): void - { - // Mark the job as completed - $this->delete(); - } -} \ No newline at end of file diff --git a/app/Services/Servers/ServerAuthService.php b/app/Services/Servers/ServerAuthService.php index 7c2caac9070..cac708546eb 100644 --- a/app/Services/Servers/ServerAuthService.php +++ b/app/Services/Servers/ServerAuthService.php @@ -5,6 +5,7 @@ use Convoy\Models\Server; use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository; use Convoy\Repositories\Proxmox\Server\ProxmoxGuestAgentRepository; +use Illuminate\Support\Str; class ServerAuthService { @@ -12,42 +13,41 @@ public function __construct(private ProxmoxConfigRepository $configRepository, p { } - public function updatePassword(Server $server, string $password) + public function updatePassword(Server $server, string $password): void { + // Always store CIPassword first + $this->configRepository->setServer($server)->update(['cipassword' => $password]); + try { - $OsInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); - if (is_array($OsInfo) && isset($OsInfo["result"]["name"])) { - if (str_contains($OsInfo["result"]["name"], "Windows")) { - $username = "Administrator"; - } else { - $username = "root"; - } - - $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword($username, $password); + $osInfo = $this->guestAgentRepository->setServer($server)->guestAgentOs(); + + // If we have valid OS info, decide which username to use + if (is_array($osInfo) && isset($osInfo['result']['name'])) { + $osName = $osInfo['result']['name']; + $username = Str::contains(Str::lower($osName), 'windows') ? 'Administrator' : 'root'; + + $this->guestAgentRepository + ->setServer($server) + ->updateGuestAgentPassword($username, $password); } - $this->configRepository->setServer($server)->update(['cipassword' => $password]); } catch (\Exception $e) { - $this->configRepository->setServer($server)->update(['cipassword' => $password]); + // Optionally log or handle exceptions } } - public function updateWindowsPassword(Server $server, string $password) { - $this->guestAgentRepository->setServer($server)->updateGuestAgentPassword("Administrator", $password); - } - - public function getSSHKeys(Server $server) + public function getSSHKeys(Server $server): string { $raw = collect($this->configRepository->setServer($server)->getConfig())->where('key', '=', 'sshkeys')->first()['value'] ?? ''; return rawurldecode($raw); } - public function updateSSHKeys(Server $server, ?string $keys) + public function updateSSHKeys(Server $server, ?string $keys): void { - if (!empty($keys)) { + if (! empty($keys)) { $this->configRepository->setServer($server)->update(['sshkeys' => rawurlencode($keys)]); } else { $this->configRepository->setServer($server)->update(['delete' => 'sshkeys']); } } -} \ No newline at end of file +} diff --git a/app/Services/Servers/ServerBuildDispatchService.php b/app/Services/Servers/ServerBuildDispatchService.php index bf2e4a312d7..42a6357f2a0 100644 --- a/app/Services/Servers/ServerBuildDispatchService.php +++ b/app/Services/Servers/ServerBuildDispatchService.php @@ -2,21 +2,21 @@ namespace Convoy\Services\Servers; -use Convoy\Models\Server; +use Convoy\Data\Server\Deployments\ServerDeploymentData; +use Convoy\Enums\Server\PowerAction; use Convoy\Enums\Server\State; use Convoy\Enums\Server\Status; -use Illuminate\Support\Facades\Bus; -use Convoy\Enums\Server\PowerAction; -use Convoy\Jobs\Server\SyncBuildJob; use Convoy\Jobs\Server\BuildServerJob; use Convoy\Jobs\Server\DeleteServerJob; use Convoy\Jobs\Server\MonitorStateJob; -use Convoy\Jobs\Server\UpdatePasswordJob; use Convoy\Jobs\Server\SendPowerCommandJob; +use Convoy\Jobs\Server\SyncBuildJob; +use Convoy\Jobs\Server\UpdatePasswordJob; use Convoy\Jobs\Server\WaitUntilVmIsCreatedJob; use Convoy\Jobs\Server\WaitUntilVmIsDeletedJob; -use Convoy\Jobs\Server\SyncWindowsSettings; -use Convoy\Data\Server\Deployments\ServerDeploymentData; +use Convoy\Models\Server; +use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Str; class ServerBuildDispatchService { @@ -56,35 +56,39 @@ public function rebuild(ServerDeploymentData $deployment): void private function getChainedBuildJobs(ServerDeploymentData $deployment): array { - if ($deployment->should_create_server) { - $jobs = [ + // Base jobs: either create a new server or sync an existing one + $jobs = $deployment->should_create_server + ? [ new BuildServerJob($deployment->server->id, $deployment->template->id), new WaitUntilVmIsCreatedJob($deployment->server->id), new SyncBuildJob($deployment->server->id), - ]; - } else { - $jobs = [ + ] + : [ new SyncBuildJob($deployment->server->id), ]; - } - $startOnce = false; - - // TODO: Readd the start_on_completion check + if (Str::contains(Str::lower($deployment->template->name), 'windows')) { + $jobs = [ + ...$jobs, + new SendPowerCommandJob($deployment->server->id, PowerAction::START), + new MonitorStateJob($deployment->server->id, State::RUNNING), + ]; - if (!empty($deployment->account_password)) { - $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); - if (str_contains($deployment->template->name, "Windows")) { + if (! empty($deployment->account_password)) { + $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); + } + } else { + // For non-Windows, update password first if provided + if (! empty($deployment->account_password)) { + $jobs[] = new UpdatePasswordJob($deployment->server->id, $deployment->account_password); + } + // Then power on if user wants to start on completion + if ($deployment->start_on_completion) { $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); - $startOnce = true; - $jobs[] = new SyncWindowsSettings($deployment->server->id, $deployment->account_password); } } - if (!$startOnce) { - $jobs[] = new SendPowerCommandJob($deployment->server->id, PowerAction::START); - } - + // Final callback to clear the status $jobs[] = function () use ($deployment) { Server::findOrFail($deployment->server->id)->update(['status' => null]); }; @@ -101,4 +105,4 @@ public function getChainedDeleteJobs(Server $server): array new WaitUntilVmIsDeletedJob($server->id), ]; } -} \ No newline at end of file +} From ea798dd7ef444d335e9530f23dabaa8d2a276910 Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:21:01 -0600 Subject: [PATCH 09/13] Add support for other realmtypes --- app/Enums/Node/Access/RealmType.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Enums/Node/Access/RealmType.php b/app/Enums/Node/Access/RealmType.php index 9d37c5bfd21..0b251a81058 100644 --- a/app/Enums/Node/Access/RealmType.php +++ b/app/Enums/Node/Access/RealmType.php @@ -6,4 +6,7 @@ enum RealmType: string { case PAM = 'pam'; case PVE = 'pve'; + case LDAP = 'ldap'; + case AD = 'ad'; + case OPEN_ID = 'openid'; } From 92c11ecfec1f890757e4c49fdd980ad91d96c98a Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:25:10 -0600 Subject: [PATCH 10/13] Fix error with parsing non-existent nameserver prop --- app/Services/Servers/CloudinitService.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Services/Servers/CloudinitService.php b/app/Services/Servers/CloudinitService.php index 8544d5d76f6..ea92dc4131e 100644 --- a/app/Services/Servers/CloudinitService.php +++ b/app/Services/Servers/CloudinitService.php @@ -2,12 +2,12 @@ namespace Convoy\Services\Servers; -use Convoy\Models\Server; -use Illuminate\Support\Arr; -use Convoy\Data\Server\Proxmox\Config\AddressConfigData; use Convoy\Data\Server\Deployments\CloudinitAddressConfigData; -use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository; +use Convoy\Data\Server\Proxmox\Config\AddressConfigData; use Convoy\Exceptions\Repository\Proxmox\ProxmoxConnectionException; +use Convoy\Models\Server; +use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository; +use Illuminate\Support\Arr; /** * Class SnapshotService @@ -46,9 +46,9 @@ public function updateHostname(Server $server, string $hostname) public function getNameservers(Server $server) { - $nameservers = collect($this->configRepository->setServer($server)->getConfig())->where('key', '=', 'nameserver')->firstOrFail()['value']; + $nameservers = collect($this->configRepository->setServer($server)->getConfig())->where('key', '=', 'nameserver')->first(); - return $nameservers ? explode(' ', $nameservers) : []; + return $nameservers ? explode(' ', $nameservers['value']) : []; } public function updateNameservers(Server $server, array $nameservers) From 4bbddf476d5ad670786b9565cc773532bf228406 Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Tue, 31 Dec 2024 23:37:50 -0600 Subject: [PATCH 11/13] Add v4.3.0-rc.1 CHANGELOG.md --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f358f4659f0..ff44be2d5a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v4.3.0-rc.1 + +> [!IMPORTANT] +> The source between v4 and v10 will begin to diverge starting here. Once v10 is complete, v4's commit history will be +> abandoned. We will not be introducing any changes to the database structure in v4 to prevent any conflicts with v10's +> database structure. + +### Changes + +- Added guest agent support for changing Windows user passwords #120 +- Servers will now automatically start after unsuspension #119 +- Fixed parsing of user realm types #126 +- Fixed broken redirect when unauthenticated while accessing certain admin routes #123 +- Fixed fetching of nameservers when there are none present #125 + ## v4.2.4 ### Changes From dc09e2df1f253585391be3c3b4a66f5455b4c7d3 Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:30:18 -0400 Subject: [PATCH 12/13] Revoke API Permissions After Demotion --- app/Http/Controllers/Admin/UserController.php | 43 ++++++++++++------- app/Models/User.php | 17 +++++--- app/Providers/RouteServiceProvider.php | 36 ++++++++-------- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index de8cf9cbb43..a915cff4362 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -7,10 +7,10 @@ use Convoy\Http\Requests\Admin\Users\StoreUserRequest; use Convoy\Http\Requests\Admin\Users\UpdateUserRequest; use Convoy\Models\Filters\FiltersUserWildcard; -use Convoy\Models\SSOToken; use Convoy\Models\User; use Convoy\Services\Api\JWTService; use Convoy\Transformers\Admin\UserTransformer; +use Illuminate\Database\ConnectionInterface; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Spatie\QueryBuilder\AllowedFilter; @@ -18,22 +18,26 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use function is_null; + class UserController extends ApiController { - public function __construct(private JWTService $JWTService) - { + public function __construct( + private JWTService $JWTService, + private ConnectionInterface $connection, + ) { } public function index(Request $request) { $users = QueryBuilder::for(User::query()) - ->withCount(['servers']) - ->allowedFilters( - [AllowedFilter::exact('id'), 'name', AllowedFilter::exact( - 'email', - ), AllowedFilter::custom('*', new FiltersUserWildcard())], - ) - ->paginate(min($request->query('per_page', 50), 100))->appends( + ->withCount(['servers']) + ->allowedFilters( + [AllowedFilter::exact('id'), 'name', AllowedFilter::exact( + 'email', + ), AllowedFilter::custom('*', new FiltersUserWildcard())], + ) + ->paginate(min($request->query('per_page', 50), 100))->appends( $request->query(), ); @@ -61,12 +65,19 @@ public function store(StoreUserRequest $request) public function update(UpdateUserRequest $request, User $user) { - $user->update([ - 'name' => $request->name, - 'email' => $request->email, - 'root_admin' => $request->root_admin, - ...(is_null($request->password) ? [] : ['password' => Hash::make($request->password)]), - ]); + $this->connection->transaction(function () use ($request, $user) { + $requestRootAdmin = $request->boolean('root_admin'); + if ($user->root_admin !== $requestRootAdmin && ! $requestRootAdmin) { + $user->tokens()->delete(); + } + + $user->update([ + 'name' => $request->name, + 'email' => $request->email, + 'root_admin' => $request->root_admin, + ...(is_null($request->password) ? [] : ['password' => Hash::make($request->password)]), + ]); + }); $user->loadCount(['servers']); diff --git a/app/Models/User.php b/app/Models/User.php index fbbec703a3e..ced3e07e931 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Foundation\Auth\Access\Authorizable; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; @@ -21,7 +22,7 @@ */ class User extends Model implements AuthenticatableContract, AuthorizableContract { - use HasApiTokens, HasFactory, Notifiable, Authenticatable, Authorizable; + use Authenticatable, Authorizable, HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. @@ -75,11 +76,10 @@ public function toReactObject(): array } public function createToken( - string $name, + string $name, ApiKeyType $type, - array $abilities = ['*'], - ): NewAccessToken - { + array $abilities = ['*'], + ): NewAccessToken { $token = $this->tokens()->create([ 'type' => $type, 'name' => $name, @@ -87,7 +87,12 @@ public function createToken( 'abilities' => $abilities, ]); - return new NewAccessToken($token, $token->getKey() . '|' . $plainTextToken); + return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken); + } + + public function tokens(): MorphMany + { + return $this->morphMany(PersonalAccessToken::class, 'tokenable'); } public function servers(): HasMany diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 712194c95ff..741bc975c1d 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -26,7 +26,7 @@ public function boot(): void { Route::bind('server', function ($value) { return Server::query()->where(strlen($value) === 8 ? 'uuid_short' : 'uuid', $value) - ->firstOrFail(); + ->firstOrFail(); }); $this->routes(function () { @@ -34,32 +34,32 @@ public function boot(): void Route::middleware('guest')->group(base_path('routes/auth.php')); Route::middleware(['auth.session']) - ->group(base_path('routes/base.php')); + ->group(base_path('routes/base.php')); Route::middleware(['auth'])->prefix('/api/client') - ->as('client.') - ->scopeBindings() - ->group(base_path('routes/api-client.php')); + ->as('client.') + ->scopeBindings() + ->group(base_path('routes/api-client.php')); Route::middleware(['auth', AdminAuthenticate::class]) - ->prefix('/api/admin') - ->as('admin.') - ->scopeBindings() - ->group(base_path('routes/api-admin.php')); + ->prefix('/api/admin') + ->as('admin.') + ->scopeBindings() + ->group(base_path('routes/api-admin.php')); }); Route::middleware(['api'])->group(function () { - Route::middleware(['auth:sanctum']) - ->prefix('/api/application') - ->as('application.') - ->scopeBindings() - ->group(base_path('routes/api-application.php')); + Route::middleware(['auth:sanctum', AdminAuthenticate::class]) + ->prefix('/api/application') + ->as('application.') + ->scopeBindings() + ->group(base_path('routes/api-application.php')); Route::middleware([CotermAuthenticate::class]) - ->prefix('/api/coterm') - ->as('coterm.') - ->scopeBindings() - ->group(base_path('routes/api-coterm.php')); + ->prefix('/api/coterm') + ->as('coterm.') + ->scopeBindings() + ->group(base_path('routes/api-coterm.php')); }); }); } From d50d792805d3fc66a1308966a0c91568ae1fbc6f Mon Sep 17 00:00:00 2001 From: Eric Wang <37554696+ericwang401@users.noreply.github.com> Date: Thu, 27 Mar 2025 11:33:38 -0400 Subject: [PATCH 13/13] Add v4.3.1 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff44be2d5a5..eae9e0c65f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v4.3.1 + +### Changes + +- Tokens will be revoked when an administrator privileges are removed #132 + +#### From v4.3.0-rc.1 + +- Added guest agent support for changing Windows user passwords #120 + - This feature is still experimental. Please provide feedback on + our [Discord community](https://discord.convoypanel.com/) and report bugs on + our [GitHub + repository](https://github.com/ConvoyPanel/panel/issues). +- Servers will now automatically start after unsuspension #119 +- Fixed parsing of user realm types #126 +- Fixed broken redirect when unauthenticated while accessing certain admin routes #123 +- Fixed fetching of nameservers when there are none present #125 + ## v4.3.0-rc.1 > [!IMPORTANT]