Skip to content

Commit 2cabb61

Browse files
committed
Add subuser deletion service
1 parent 74ea1aa commit 2cabb61

File tree

6 files changed

+276
-1
lines changed

6 files changed

+276
-1
lines changed

app/Contracts/Repository/SubuserRepositoryInterface.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,13 @@
2626

2727
interface SubuserRepositoryInterface extends RepositoryInterface
2828
{
29+
/**
30+
* Find a subuser and return with server and permissions relationships.
31+
*
32+
* @param int $id
33+
* @return \Illuminate\Database\Eloquent\Collection
34+
*
35+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
36+
*/
37+
public function getWithServerAndPermissions($id);
2938
}

app/Repositories/Eloquent/SubuserRepository.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
namespace Pterodactyl\Repositories\Eloquent;
2626

2727
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
28+
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
2829
use Pterodactyl\Models\Subuser;
30+
use Webmozart\Assert\Assert;
2931

3032
class SubuserRepository extends EloquentRepository implements SubuserRepositoryInterface
3133
{
@@ -36,4 +38,19 @@ public function model()
3638
{
3739
return Subuser::class;
3840
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function getWithServerAndPermissions($id)
46+
{
47+
Assert::numeric($id, 'First argument passed to getWithServerAndPermissions must be numeric, received %s.');
48+
49+
$instance = $this->getBuilder()->with(['server', 'permission'])->find($id, $this->getColumns());
50+
if (! $instance) {
51+
throw new RecordNotFoundException;
52+
}
53+
54+
return $instance;
55+
}
3956
}

app/Services/Subusers/SubuserCreationService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ public function handle($server, $email, array $permissions)
177177

178178
return $subuser;
179179
} catch (RequestException $exception) {
180+
$this->connection->rollBack();
180181
$response = $exception->getResponse();
181182
$this->writer->warning($exception);
182183

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
/*
3+
* Pterodactyl - Panel
4+
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
namespace Pterodactyl\Services\Subusers;
26+
27+
use Illuminate\Log\Writer;
28+
use GuzzleHttp\Exception\RequestException;
29+
use Illuminate\Database\ConnectionInterface;
30+
use Pterodactyl\Exceptions\DisplayException;
31+
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
32+
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
33+
34+
class SubuserDeletionService
35+
{
36+
/**
37+
* @var \Illuminate\Database\ConnectionInterface
38+
*/
39+
protected $connection;
40+
41+
/**
42+
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
43+
*/
44+
protected $daemonRepository;
45+
46+
/**
47+
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
48+
*/
49+
protected $repository;
50+
51+
/**
52+
* @var \Illuminate\Log\Writer
53+
*/
54+
protected $writer;
55+
56+
/**
57+
* SubuserDeletionService constructor.
58+
*
59+
* @param \Illuminate\Database\ConnectionInterface $connection
60+
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
61+
* @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
62+
* @param \Illuminate\Log\Writer $writer
63+
*/
64+
public function __construct(
65+
ConnectionInterface $connection,
66+
DaemonServerRepositoryInterface $daemonRepository,
67+
SubuserRepositoryInterface $repository,
68+
Writer $writer
69+
) {
70+
$this->connection = $connection;
71+
$this->daemonRepository = $daemonRepository;
72+
$this->repository = $repository;
73+
$this->writer = $writer;
74+
}
75+
76+
/**
77+
* Delete a subuser and their associated permissions from the Panel and Daemon.
78+
*
79+
* @param int $subuser
80+
* @return int|null
81+
*
82+
* @throws \Pterodactyl\Exceptions\DisplayException
83+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
84+
*/
85+
public function handle($subuser)
86+
{
87+
$subuser = $this->repository->getWithServerAndPermissions($subuser);
88+
89+
$this->connection->beginTransaction();
90+
$response = $this->repository->delete($subuser->id);
91+
92+
try {
93+
$this->daemonRepository->setNode($subuser->server->node_id)->setAccessServer($subuser->server->uuid)
94+
->setSubuserKey($subuser->daemonSecret, []);
95+
$this->connection->commit();
96+
97+
return $response;
98+
} catch (RequestException $exception) {
99+
$this->connection->rollBack();
100+
$response = $exception->getResponse();
101+
$this->writer->warning($exception);
102+
103+
throw new DisplayException(trans('admin/exceptions.daemon_connection_failed', [
104+
'code' => is_null($response) ? 'E_CONN_REFUSED' : $response->getStatusCode(),
105+
]));
106+
}
107+
}
108+
}

database/factories/ModelFactory.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
$factory->define(Pterodactyl\Models\Server::class, function (Faker\Generator $faker) {
1717
return [
1818
'id' => $faker->unique()->randomNumber(),
19-
'uuid' => $faker->uuid,
19+
'node_id' => $faker->randomNumber(),
20+
'uuid' => $faker->unique()->uuid,
2021
'uuidShort' => str_random(8),
2122
'name' => $faker->firstName,
2223
'description' => implode(' ', $faker->sentences()),
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
/**
3+
* Pterodactyl - Panel
4+
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
namespace Tests\Unit\Services\Subusers;
26+
27+
use GuzzleHttp\Exception\RequestException;
28+
use Illuminate\Database\ConnectionInterface;
29+
use Illuminate\Log\Writer;
30+
use Mockery as m;
31+
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
32+
use Pterodactyl\Exceptions\DisplayException;
33+
use Pterodactyl\Models\Server;
34+
use Pterodactyl\Models\Subuser;
35+
use Pterodactyl\Services\Subusers\SubuserDeletionService;
36+
use Tests\TestCase;
37+
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
38+
39+
class SubuserDeletionServiceTest extends TestCase
40+
{
41+
/**
42+
* @var \Illuminate\Database\ConnectionInterface
43+
*/
44+
protected $connection;
45+
46+
/**
47+
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
48+
*/
49+
protected $daemonRepository;
50+
51+
/**
52+
* @var \GuzzleHttp\Exception\RequestException
53+
*/
54+
protected $exception;
55+
56+
/**
57+
* @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
58+
*/
59+
protected $repository;
60+
61+
/**
62+
* @var \Pterodactyl\Services\Subusers\SubuserDeletionService
63+
*/
64+
protected $service;
65+
66+
/**
67+
* @var \Illuminate\Log\Writer
68+
*/
69+
protected $writer;
70+
71+
/**
72+
* Setup tests.
73+
*/
74+
public function setUp()
75+
{
76+
parent::setUp();
77+
78+
$this->connection = m::mock(ConnectionInterface::class);
79+
$this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class);
80+
$this->exception = m::mock(RequestException::class);
81+
$this->repository = m::mock(SubuserRepositoryInterface::class);
82+
$this->writer = m::mock(Writer::class);
83+
84+
$this->service = new SubuserDeletionService(
85+
$this->connection,
86+
$this->daemonRepository,
87+
$this->repository,
88+
$this->writer
89+
);
90+
}
91+
92+
/**
93+
* Test that a subuser is deleted correctly.
94+
*/
95+
public function testSubuserIsDeleted()
96+
{
97+
$subuser = factory(Subuser::class)->make();
98+
$subuser->server = factory(Server::class)->make();
99+
100+
$this->repository->shouldReceive('getWithServerAndPermissions')->with($subuser->id)->once()->andReturn($subuser);
101+
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
102+
$this->repository->shouldReceive('delete')->with($subuser->id)->once()->andReturn(1);
103+
104+
$this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andReturnSelf()
105+
->shouldReceive('setAccessServer')->with($subuser->server->uuid)->once()->andReturnSelf()
106+
->shouldReceive('setSubuserKey')->with($subuser->daemonSecret, [])->once()->andReturnNull();
107+
108+
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
109+
110+
$response = $this->service->handle($subuser->id);
111+
$this->assertEquals(1, $response);
112+
}
113+
114+
/**
115+
* Test that an exception caused by the daemon is properly handled.
116+
*/
117+
public function testExceptionIsThrownIfDaemonConnectionFails()
118+
{
119+
$subuser = factory(Subuser::class)->make();
120+
$subuser->server = factory(Server::class)->make();
121+
122+
$this->repository->shouldReceive('getWithServerAndPermissions')->with($subuser->id)->once()->andReturn($subuser);
123+
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
124+
$this->repository->shouldReceive('delete')->with($subuser->id)->once()->andReturn(1);
125+
126+
$this->daemonRepository->shouldReceive('setNode->setAccessServer->setSubuserKey')->once()->andThrow($this->exception);
127+
128+
$this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull();
129+
$this->exception->shouldReceive('getResponse')->withNoArgs()->once()->andReturnNull();
130+
$this->writer->shouldReceive('warning')->with($this->exception)->once()->andReturnNull();
131+
132+
try {
133+
$this->service->handle($subuser->id);
134+
} catch (DisplayException $exception) {
135+
$this->assertEquals(trans('admin/exceptions.daemon_connection_failed', ['code' => 'E_CONN_REFUSED']), $exception->getMessage());
136+
}
137+
}
138+
139+
}

0 commit comments

Comments
 (0)