Implement fix to allow root admins to view all servers.

closes #722
This commit is contained in:
Dane Everitt 2017-11-05 12:38:39 -06:00
parent fb2909a1c7
commit 6409fffdad
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
22 changed files with 143 additions and 166 deletions

View file

@ -118,7 +118,7 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
* Return a server by UUID. * Return a server by UUID.
* *
* @param string $uuid * @param string $uuid
* @return \Illuminate\Database\Eloquent\Collection * @return \Pterodactyl\Models\Server
* *
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */

View file

@ -13,7 +13,7 @@ interface SubuserRepositoryInterface extends RepositoryInterface
* @param bool $refresh * @param bool $refresh
* @return \Pterodactyl\Models\Subuser * @return \Pterodactyl\Models\Subuser
*/ */
public function getWithServer(Subuser $subuser, bool $refresh = false): Subuser; public function loadServerAndUserRelations(Subuser $subuser, bool $refresh = false): Subuser;
/** /**
* Return a subuser with the associated permissions relationship. * Return a subuser with the associated permissions relationship.

View file

@ -93,7 +93,7 @@ class IndexController extends Controller
public function status(Request $request, $uuid) public function status(Request $request, $uuid)
{ {
$server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]); $server = $this->repository->findFirstWhere([['uuidShort', '=', $uuid]]);
$token = $this->keyProviderService->handle($server->id, $request->user()->id); $token = $this->keyProviderService->handle($server, $request->user());
if (! $server->installed) { if (! $server->installed) {
return response()->json(['status' => 20]); return response()->json(['status' => 20]);

View file

@ -11,7 +11,7 @@ namespace Pterodactyl\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AdminAuthenticate class AdminAuthenticate
{ {
@ -25,7 +25,7 @@ class AdminAuthenticate
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (! $request->user() || ! $request->user()->root_admin) { if (! $request->user() || ! $request->user()->root_admin) {
throw new HttpException(403, 'Access Denied'); throw new AccessDeniedHttpException;
} }
return $next($request); return $next($request);

View file

@ -20,11 +20,7 @@ class Authenticate
public function handle(Request $request, Closure $next) public function handle(Request $request, Closure $next)
{ {
if (! $request->user()) { if (! $request->user()) {
if ($request->ajax() || $request->expectsJson()) { throw new AuthenticationException;
throw new AuthenticationException();
} else {
return redirect()->route('auth.login');
}
} }
return $next($request); return $next($request);

View file

@ -29,6 +29,7 @@ use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class DaemonAuthenticate class DaemonAuthenticate
{ {
@ -80,7 +81,7 @@ class DaemonAuthenticate
try { try {
$node = $this->repository->findFirstWhere([['daemonSecret', '=', $token]]); $node = $this->repository->findFirstWhere([['daemonSecret', '=', $token]]);
} catch (RecordNotFoundException $exception) { } catch (RecordNotFoundException $exception) {
throw new HttpException(403); throw new AccessDeniedHttpException;
} }
$request->attributes->set('node', $node); $request->attributes->set('node', $node);

View file

@ -12,9 +12,9 @@ namespace Pterodactyl\Http\Middleware\Server;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Contracts\Session\Session; use Illuminate\Contracts\Session\Session;
use Illuminate\Auth\AuthenticationException;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class AuthenticateAsSubuser class AuthenticateAsSubuser
{ {
@ -56,9 +56,9 @@ class AuthenticateAsSubuser
$server = $request->attributes->get('server'); $server = $request->attributes->get('server');
try { try {
$token = $this->keyProviderService->handle($server->id, $request->user()->id); $token = $this->keyProviderService->handle($server, $request->user());
} catch (RecordNotFoundException $exception) { } catch (RecordNotFoundException $exception) {
throw new AuthenticationException('This account does not have permission to access this server.'); throw new AccessDeniedHttpException('This account does not have permission to access this server.');
} }
$this->session->now('server_data.token', $token); $this->session->now('server_data.token', $token);

View file

@ -92,20 +92,21 @@ class RunTaskJob extends Job implements ShouldQueue
$this->taskRepository = $taskRepository; $this->taskRepository = $taskRepository;
$task = $this->taskRepository->getTaskWithServer($this->task); $task = $this->taskRepository->getTaskWithServer($this->task);
$server = $task->server; $server = $task->getRelation('server');
$user = $server->getRelation('user');
// Perform the provided task aganist the daemon. // Perform the provided task aganist the daemon.
switch ($task->action) { switch ($task->action) {
case 'power': case 'power':
$this->powerRepository->setNode($server->node_id) $this->powerRepository->setNode($server->node_id)
->setAccessServer($server->uuid) ->setAccessServer($server->uuid)
->setAccessToken($keyProviderService->handle($server->id, $server->owner_id)) ->setAccessToken($keyProviderService->handle($server, $user))
->sendSignal($task->payload); ->sendSignal($task->payload);
break; break;
case 'command': case 'command':
$this->commandRepository->setNode($server->node_id) $this->commandRepository->setNode($server->node_id)
->setAccessServer($server->uuid) ->setAccessServer($server->uuid)
->setAccessToken($keyProviderService->handle($server->id, $server->owner_id)) ->setAccessToken($keyProviderService->handle($server, $user))
->send($task->payload); ->send($task->payload);
break; break;
default: default:

View file

@ -31,12 +31,16 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI
* @param bool $refresh * @param bool $refresh
* @return \Pterodactyl\Models\Subuser * @return \Pterodactyl\Models\Subuser
*/ */
public function getWithServer(Subuser $subuser, bool $refresh = false): Subuser public function loadServerAndUserRelations(Subuser $subuser, bool $refresh = false): Subuser
{ {
if (! $subuser->relationLoaded('server') || $refresh) { if (! $subuser->relationLoaded('server') || $refresh) {
$subuser->load('server'); $subuser->load('server');
} }
if (! $subuser->relationLoaded('user') || $refresh) {
$subuser->load('user');
}
return $subuser; return $subuser;
} }

View file

@ -31,7 +31,7 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa
{ {
Assert::integerish($id, 'First argument passed to getTaskWithServer must be numeric, received %s.'); Assert::integerish($id, 'First argument passed to getTaskWithServer must be numeric, received %s.');
$instance = $this->getBuilder()->with('server')->find($id, $this->getColumns()); $instance = $this->getBuilder()->with('server.user')->find($id, $this->getColumns());
if (! $instance) { if (! $instance) {
throw new RecordNotFoundException; throw new RecordNotFoundException;
} }

View file

@ -25,39 +25,30 @@
namespace Pterodactyl\Services\DaemonKeys; namespace Pterodactyl\Services\DaemonKeys;
use Carbon\Carbon; use Carbon\Carbon;
use Webmozart\Assert\Assert; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface; use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
class DaemonKeyProviderService class DaemonKeyProviderService
{ {
/**
* @var \Carbon\Carbon
*/
protected $carbon;
/** /**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService
*/ */
protected $keyUpdateService; private $keyUpdateService;
/** /**
* @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface
*/ */
protected $repository; private $repository;
/** /**
* GetDaemonKeyService constructor. * GetDaemonKeyService constructor.
* *
* @param \Carbon\Carbon $carbon
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService $keyUpdateService
* @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository * @param \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface $repository
*/ */
public function __construct( public function __construct(DaemonKeyUpdateService $keyUpdateService, DaemonKeyRepositoryInterface $repository)
Carbon $carbon, {
DaemonKeyUpdateService $keyUpdateService,
DaemonKeyRepositoryInterface $repository
) {
$this->carbon = $carbon;
$this->keyUpdateService = $keyUpdateService; $this->keyUpdateService = $keyUpdateService;
$this->repository = $repository; $this->repository = $repository;
} }
@ -65,25 +56,24 @@ class DaemonKeyProviderService
/** /**
* Get the access key for a user on a specific server. * Get the access key for a user on a specific server.
* *
* @param int $server * @param \Pterodactyl\Models\Server $server
* @param int $user * @param \Pterodactyl\Models\User $user
* @param bool $updateIfExpired * @param bool $updateIfExpired
* @return string * @return string
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/ */
public function handle($server, $user, $updateIfExpired = true) public function handle(Server $server, User $user, $updateIfExpired = true): string
{ {
Assert::integerish($server, 'First argument passed to handle must be an integer, received %s.'); $userId = $user->root_admin ? $server->owner_id : $user->id;
Assert::integerish($user, 'Second argument passed to handle must be an integer, received %s.');
$key = $this->repository->findFirstWhere([ $key = $this->repository->findFirstWhere([
['user_id', '=', $user], ['user_id', '=', $userId],
['server_id', '=', $server], ['server_id', '=', $server->id],
]); ]);
if (! $updateIfExpired || $this->carbon->now()->diffInSeconds($key->expires_at, false) > 0) { if (! $updateIfExpired || Carbon::now()->diffInSeconds($key->expires_at, false) > 0) {
return $key->secret; return $key->secret;
} }

View file

@ -84,7 +84,7 @@ class AuthenticateUsingPasswordService
return [ return [
'server' => $server->uuid, 'server' => $server->uuid,
'token' => $this->keyProviderService->handle($server->id, $user->id), 'token' => $this->keyProviderService->handle($server, $user),
]; ];
} }
} }

View file

@ -88,14 +88,14 @@ class SubuserUpdateService
*/ */
public function handle(Subuser $subuser, array $permissions) public function handle(Subuser $subuser, array $permissions)
{ {
$subuser = $this->repository->getWithServer($subuser); $subuser = $this->repository->loadServerAndUserRelations($subuser);
$this->connection->beginTransaction(); $this->connection->beginTransaction();
$this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]); $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]);
$this->permissionService->handle($subuser->id, $permissions); $this->permissionService->handle($subuser->id, $permissions);
try { try {
$token = $this->keyProviderService->handle($subuser->server_id, $subuser->user_id, false); $token = $this->keyProviderService->handle($subuser->getRelation('server'), $subuser->getRelation('user'), false);
$this->daemonRepository->setNode($subuser->getRelation('server')->node_id)->revokeAccessKey($token); $this->daemonRepository->setNode($subuser->getRelation('server')->node_id)->revokeAccessKey($token);
} catch (RequestException $exception) { } catch (RequestException $exception) {
$this->connection->rollBack(); $this->connection->rollBack();

View file

@ -10,17 +10,16 @@
namespace Tests\Unit\Http\Controllers\Base; namespace Tests\Unit\Http\Controllers\Base;
use Mockery as m; use Mockery as m;
use Tests\TestCase;
use Illuminate\Http\Request;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Tests\Assertions\ControllerAssertionsTrait; use Tests\Assertions\ControllerAssertionsTrait;
use Tests\Unit\Http\Controllers\ControllerTestCase;
use Pterodactyl\Http\Controllers\Base\IndexController; use Pterodactyl\Http\Controllers\Base\IndexController;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface; use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class IndexControllerTest extends TestCase class IndexControllerTest extends ControllerTestCase
{ {
use ControllerAssertionsTrait; use ControllerAssertionsTrait;
@ -44,11 +43,6 @@ class IndexControllerTest extends TestCase
*/ */
protected $repository; protected $repository;
/**
* @var \Illuminate\Http\Request|\Mockery\Mock
*/
protected $request;
/** /**
* Setup tests. * Setup tests.
*/ */
@ -59,7 +53,6 @@ class IndexControllerTest extends TestCase
$this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class); $this->daemonRepository = m::mock(DaemonServerRepositoryInterface::class);
$this->keyProviderService = m::mock(DaemonKeyProviderService::class); $this->keyProviderService = m::mock(DaemonKeyProviderService::class);
$this->repository = m::mock(ServerRepositoryInterface::class); $this->repository = m::mock(ServerRepositoryInterface::class);
$this->request = m::mock(Request::class);
$this->controller = new IndexController($this->keyProviderService, $this->daemonRepository, $this->repository); $this->controller = new IndexController($this->keyProviderService, $this->daemonRepository, $this->repository);
} }
@ -69,9 +62,8 @@ class IndexControllerTest extends TestCase
*/ */
public function testIndexController() public function testIndexController()
{ {
$model = factory(User::class)->make(); $model = $this->setRequestUser();
$this->request->shouldReceive('user')->withNoArgs()->andReturn($model);
$this->request->shouldReceive('input')->with('query')->once()->andReturn('searchTerm'); $this->request->shouldReceive('input')->with('query')->once()->andReturn('searchTerm');
$this->repository->shouldReceive('search')->with('searchTerm')->once()->andReturnSelf() $this->repository->shouldReceive('search')->with('searchTerm')->once()->andReturnSelf()
->shouldReceive('filterUserAccessServers')->with( ->shouldReceive('filterUserAccessServers')->with(
@ -90,12 +82,11 @@ class IndexControllerTest extends TestCase
*/ */
public function testStatusController() public function testStatusController()
{ {
$user = factory(User::class)->make(); $user = $this->setRequestUser();
$server = factory(Server::class)->make(['suspended' => 0, 'installed' => 1]); $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 1]);
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user);
$this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server); $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('test123'); $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
$this->daemonRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() $this->daemonRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf()
->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf()
@ -114,12 +105,11 @@ class IndexControllerTest extends TestCase
*/ */
public function testStatusControllerWhenServerNotInstalled() public function testStatusControllerWhenServerNotInstalled()
{ {
$user = factory(User::class)->make(); $user = $this->setRequestUser();
$server = factory(Server::class)->make(['suspended' => 0, 'installed' => 0]); $server = factory(Server::class)->make(['suspended' => 0, 'installed' => 0]);
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user);
$this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server); $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('test123'); $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
$response = $this->controller->status($this->request, $server->uuidShort); $response = $this->controller->status($this->request, $server->uuidShort);
$this->assertIsJsonResponse($response); $this->assertIsJsonResponse($response);
@ -137,7 +127,7 @@ class IndexControllerTest extends TestCase
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user); $this->request->shouldReceive('user')->withNoArgs()->once()->andReturn($user);
$this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server); $this->repository->shouldReceive('findFirstWhere')->with([['uuidShort', '=', $server->uuidShort]])->once()->andReturn($server);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('test123'); $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('test123');
$response = $this->controller->status($this->request, $server->uuidShort); $response = $this->controller->status($this->request, $server->uuidShort);
$this->assertIsJsonResponse($response); $this->assertIsJsonResponse($response);

View file

@ -4,7 +4,6 @@ namespace Tests\Unit\Http\Middleware;
use Pterodactyl\Models\User; use Pterodactyl\Models\User;
use Pterodactyl\Http\Middleware\AdminAuthenticate; use Pterodactyl\Http\Middleware\AdminAuthenticate;
use Symfony\Component\HttpKernel\Exception\HttpException;
class AdminAuthenticateTest extends MiddlewareTestCase class AdminAuthenticateTest extends MiddlewareTestCase
{ {
@ -22,20 +21,20 @@ class AdminAuthenticateTest extends MiddlewareTestCase
/** /**
* Test that a missing user in the request triggers an error. * Test that a missing user in the request triggers an error.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/ */
public function testExceptionIsThrownIfUserDoesNotExist() public function testExceptionIsThrownIfUserDoesNotExist()
{ {
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
try { $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} catch (HttpException $exception) {
$this->assertEquals(403, $exception->getStatusCode());
}
} }
/** /**
* Test that an exception is thrown if the user is not an admin. * Test that an exception is thrown if the user is not an admin.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/ */
public function testExceptionIsThrownIfUserIsNotAnAdmin() public function testExceptionIsThrownIfUserIsNotAnAdmin()
{ {
@ -43,11 +42,7 @@ class AdminAuthenticateTest extends MiddlewareTestCase
$this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user); $this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user);
try { $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} catch (HttpException $exception) {
$this->assertEquals(403, $exception->getStatusCode());
}
} }
/** /**

View file

@ -2,7 +2,6 @@
namespace Tests\Unit\Http\Middleware; namespace Tests\Unit\Http\Middleware;
use Illuminate\Http\RedirectResponse;
use Pterodactyl\Http\Middleware\Authenticate; use Pterodactyl\Http\Middleware\Authenticate;
class AuthenticateTest extends MiddlewareTestCase class AuthenticateTest extends MiddlewareTestCase
@ -18,29 +17,13 @@ class AuthenticateTest extends MiddlewareTestCase
} }
/** /**
* Test that a logged out user results in a redirect. * Test that a logged out user results in an exception.
*
* @expectedException \Illuminate\Auth\AuthenticationException
*/ */
public function testLoggedOutUser() public function testLoggedOutUser()
{ {
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull(); $this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
$this->request->shouldReceive('ajax')->withNoArgs()->once()->andReturn(false);
$this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false);
$response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->assertInstanceOf(RedirectResponse::class, $response);
$this->assertEquals(302, $response->getStatusCode());
$this->assertEquals(route('auth.login'), $response->getTargetUrl());
}
/**
* Test that a logged out user via an API/Ajax request returns a HTTP error.
*
* @expectedException \Illuminate\Auth\AuthenticationException
*/
public function testLoggedOUtUserApiRequest()
{
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
$this->request->shouldReceive('ajax')->withNoArgs()->once()->andReturn(true);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} }

View file

@ -60,6 +60,8 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
/** /**
* Test that passing in an invalid node daemon secret will result in a HTTP/403 * Test that passing in an invalid node daemon secret will result in a HTTP/403
* error response. * error response.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/ */
public function testResponseShouldFailIfNoNodeIsFound() public function testResponseShouldFailIfNoNodeIsFound()
{ {
@ -68,11 +70,7 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
$this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', 'test1234']])->once()->andThrow(new RecordNotFoundException); $this->repository->shouldReceive('findFirstWhere')->with([['daemonSecret', '=', 'test1234']])->once()->andThrow(new RecordNotFoundException);
try { $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} catch (HttpException $exception) {
$this->assertEquals(403, $exception->getStatusCode(), 'Assert that a status code of 403 is returned.');
}
} }
/** /**

View file

@ -42,7 +42,7 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
$user = $this->setRequestUser(); $user = $this->setRequestUser();
$this->setRequestAttribute('server', $model); $this->setRequestAttribute('server', $model);
$this->keyProviderService->shouldReceive('handle')->with($model->id, $user->id)->once()->andReturn('abc123'); $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andReturn('abc123');
$this->session->shouldReceive('now')->with('server_data.token', 'abc123')->once()->andReturnNull(); $this->session->shouldReceive('now')->with('server_data.token', 'abc123')->once()->andReturnNull();
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
@ -53,7 +53,7 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
/** /**
* Test middleware handles missing token exception. * Test middleware handles missing token exception.
* *
* @expectedException \Illuminate\Auth\AuthenticationException * @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* @expectedExceptionMessage This account does not have permission to access this server. * @expectedExceptionMessage This account does not have permission to access this server.
*/ */
public function testExceptionIsThrownIfNoTokenIsFound() public function testExceptionIsThrownIfNoTokenIsFound()
@ -62,7 +62,7 @@ class AuthenticateAsSubuserTest extends MiddlewareTestCase
$user = $this->setRequestUser(); $user = $this->setRequestUser();
$this->setRequestAttribute('server', $model); $this->setRequestAttribute('server', $model);
$this->keyProviderService->shouldReceive('handle')->with($model->id, $user->id)->once()->andThrow(new RecordNotFoundException); $this->keyProviderService->shouldReceive('handle')->with($model, $user)->once()->andThrow(new RecordNotFoundException);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} }

View file

@ -13,6 +13,7 @@ use Mockery as m;
use Carbon\Carbon; use Carbon\Carbon;
use Tests\TestCase; use Tests\TestCase;
use Pterodactyl\Models\Task; use Pterodactyl\Models\Task;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Schedule; use Pterodactyl\Models\Schedule;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
@ -63,6 +64,7 @@ class RunTaskJobTest extends TestCase
{ {
parent::setUp(); parent::setUp();
Bus::fake(); Bus::fake();
Carbon::setTestNow();
$this->commandRepository = m::mock(CommandRepositoryInterface::class); $this->commandRepository = m::mock(CommandRepositoryInterface::class);
$this->config = m::mock(Repository::class); $this->config = m::mock(Repository::class);
@ -81,17 +83,15 @@ class RunTaskJobTest extends TestCase
*/ */
public function testPowerAction() public function testPowerAction()
{ {
Carbon::setTestNow();
$schedule = factory(Schedule::class)->make(); $schedule = factory(Schedule::class)->make();
$task = factory(Task::class)->make(['action' => 'power', 'sequence_id' => 1]); $task = factory(Task::class)->make(['action' => 'power', 'sequence_id' => 1]);
$server = factory(Server::class)->make(); $task->setRelation('server', $server = factory(Server::class)->make());
$task->server = $server; $server->setRelation('user', factory(User::class)->make());
$this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $server->owner_id)->once()->andReturn('123456'); $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456');
$this->powerRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() $this->powerRepository->shouldReceive('setNode')->with($task->server->node_id)->once()->andReturnSelf()
->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($task->server->uuid)->once()->andReturnSelf()
->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf() ->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf()
->shouldReceive('sendSignal')->with($task->payload)->once()->andReturnNull(); ->shouldReceive('sendSignal')->with($task->payload)->once()->andReturnNull();
@ -113,17 +113,15 @@ class RunTaskJobTest extends TestCase
*/ */
public function testCommandAction() public function testCommandAction()
{ {
Carbon::setTestNow();
$schedule = factory(Schedule::class)->make(); $schedule = factory(Schedule::class)->make();
$task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]);
$server = factory(Server::class)->make(); $task->setRelation('server', $server = factory(Server::class)->make());
$task->server = $server; $server->setRelation('user', factory(User::class)->make());
$this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $server->owner_id)->once()->andReturn('123456'); $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456');
$this->commandRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() $this->commandRepository->shouldReceive('setNode')->with($task->server->node_id)->once()->andReturnSelf()
->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($task->server->uuid)->once()->andReturnSelf()
->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf() ->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf()
->shouldReceive('send')->with($task->payload)->once()->andReturnNull(); ->shouldReceive('send')->with($task->payload)->once()->andReturnNull();
@ -145,17 +143,15 @@ class RunTaskJobTest extends TestCase
*/ */
public function testNextTaskQueuedIfExists() public function testNextTaskQueuedIfExists()
{ {
Carbon::setTestNow();
$schedule = factory(Schedule::class)->make(); $schedule = factory(Schedule::class)->make();
$task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]); $task = factory(Task::class)->make(['action' => 'command', 'sequence_id' => 1]);
$server = factory(Server::class)->make(); $task->setRelation('server', $server = factory(Server::class)->make());
$task->server = $server; $server->setRelation('user', factory(User::class)->make());
$this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $server->owner_id)->once()->andReturn('123456'); $this->keyProviderService->shouldReceive('handle')->with($server, $server->user)->once()->andReturn('123456');
$this->commandRepository->shouldReceive('setNode')->with($server->node_id)->once()->andReturnSelf() $this->commandRepository->shouldReceive('setNode')->with($task->server->node_id)->once()->andReturnSelf()
->shouldReceive('setAccessServer')->with($server->uuid)->once()->andReturnSelf() ->shouldReceive('setAccessServer')->with($task->server->uuid)->once()->andReturnSelf()
->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf() ->shouldReceive('setAccessToken')->with('123456')->once()->andReturnSelf()
->shouldReceive('send')->with($task->payload)->once()->andReturnNull(); ->shouldReceive('send')->with($task->payload)->once()->andReturnNull();
@ -187,7 +183,8 @@ class RunTaskJobTest extends TestCase
public function testInvalidActionPassedToJob() public function testInvalidActionPassedToJob()
{ {
$task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]); $task = factory(Task::class)->make(['action' => 'invalid', 'sequence_id' => 1]);
$task->server = []; $task->setRelation('server', $server = factory(Server::class)->make());
$server->setRelation('user', factory(User::class)->make());
$this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task); $this->taskRepository->shouldReceive('getTaskWithServer')->with($task->id)->once()->andReturn($task);

View file

@ -12,6 +12,8 @@ namespace Tests\Unit\Services\DaemonKeys;
use Mockery as m; use Mockery as m;
use Carbon\Carbon; use Carbon\Carbon;
use Tests\TestCase; use Tests\TestCase;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\DaemonKey; use Pterodactyl\Models\DaemonKey;
use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService; use Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService;
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService; use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
@ -19,25 +21,15 @@ use Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface;
class DaemonKeyProviderServiceTest extends TestCase class DaemonKeyProviderServiceTest extends TestCase
{ {
/**
* @var \Carbon\Carbon|\Mockery\Mock
*/
protected $carbon;
/** /**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService|\Mockery\Mock * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyUpdateService|\Mockery\Mock
*/ */
protected $keyUpdateService; private $keyUpdateService;
/** /**
* @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock * @var \Pterodactyl\Contracts\Repository\DaemonKeyRepositoryInterface|\Mockery\Mock
*/ */
protected $repository; private $repository;
/**
* @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
*/
protected $service;
/** /**
* Setup tests. * Setup tests.
@ -45,29 +37,46 @@ class DaemonKeyProviderServiceTest extends TestCase
public function setUp() public function setUp()
{ {
parent::setUp(); parent::setUp();
Carbon::setTestNow();
$this->carbon = new Carbon();
$this->carbon->setTestNow();
$this->keyUpdateService = m::mock(DaemonKeyUpdateService::class); $this->keyUpdateService = m::mock(DaemonKeyUpdateService::class);
$this->repository = m::mock(DaemonKeyRepositoryInterface::class); $this->repository = m::mock(DaemonKeyRepositoryInterface::class);
$this->service = new DaemonKeyProviderService($this->carbon, $this->keyUpdateService, $this->repository);
} }
/** /**
* Test that a key is returned. * Test that a key is returned correctly as a non-admin.
*/ */
public function testKeyIsReturned() public function testKeyIsReturned()
{ {
$server = factory(Server::class)->make();
$user = factory(User::class)->make(['root_admin' => 0]);
$key = factory(DaemonKey::class)->make(); $key = factory(DaemonKey::class)->make();
$this->repository->shouldReceive('findFirstWhere')->with([ $this->repository->shouldReceive('findFirstWhere')->with([
['user_id', '=', $key->user_id], ['user_id', '=', $user->id],
['server_id', '=', $key->server_id], ['server_id', '=', $server->id],
])->once()->andReturn($key); ])->once()->andReturn($key);
$response = $this->service->handle($key->server_id, $key->user_id); $response = $this->getService()->handle($server, $user);
$this->assertNotEmpty($response);
$this->assertEquals($key->secret, $response);
}
/**
* Test that an admin user gets the server owner's key as the response.
*/
public function testServerOwnerKeyIsReturnedIfUserIsAdministrator()
{
$server = factory(Server::class)->make();
$user = factory(User::class)->make(['root_admin' => 1]);
$key = factory(DaemonKey::class)->make();
$this->repository->shouldReceive('findFirstWhere')->with([
['user_id', '=', $server->owner_id],
['server_id', '=', $server->id],
])->once()->andReturn($key);
$response = $this->getService()->handle($server, $user);
$this->assertNotEmpty($response); $this->assertNotEmpty($response);
$this->assertEquals($key->secret, $response); $this->assertEquals($key->secret, $response);
} }
@ -77,20 +86,20 @@ class DaemonKeyProviderServiceTest extends TestCase
*/ */
public function testExpiredKeyIsUpdated() public function testExpiredKeyIsUpdated()
{ {
$key = factory(DaemonKey::class)->make([ $server = factory(Server::class)->make();
'expires_at' => $this->carbon->subHour(), $user = factory(User::class)->make(['root_admin' => 0]);
]); $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
$this->repository->shouldReceive('findFirstWhere')->with([ $this->repository->shouldReceive('findFirstWhere')->with([
['user_id', '=', $key->user_id], ['user_id', '=', $user->id],
['server_id', '=', $key->server_id], ['server_id', '=', $server->id],
])->once()->andReturn($key); ])->once()->andReturn($key);
$this->keyUpdateService->shouldReceive('handle')->with($key->id)->once()->andReturn(true); $this->keyUpdateService->shouldReceive('handle')->with($key->id)->once()->andReturn('abc123');
$response = $this->service->handle($key->server_id, $key->user_id); $response = $this->getService()->handle($server, $user);
$this->assertNotEmpty($response); $this->assertNotEmpty($response);
$this->assertTrue($response); $this->assertEquals('abc123', $response);
} }
/** /**
@ -98,17 +107,27 @@ class DaemonKeyProviderServiceTest extends TestCase
*/ */
public function testExpiredKeyIsNotUpdated() public function testExpiredKeyIsNotUpdated()
{ {
$key = factory(DaemonKey::class)->make([ $server = factory(Server::class)->make();
'expires_at' => $this->carbon->subHour(), $user = factory(User::class)->make(['root_admin' => 0]);
]); $key = factory(DaemonKey::class)->make(['expires_at' => Carbon::now()->subHour()]);
$this->repository->shouldReceive('findFirstWhere')->with([ $this->repository->shouldReceive('findFirstWhere')->with([
['user_id', '=', $key->user_id], ['user_id', '=', $user->id],
['server_id', '=', $key->server_id], ['server_id', '=', $server->id],
])->once()->andReturn($key); ])->once()->andReturn($key);
$response = $this->service->handle($key->server_id, $key->user_id, false); $response = $this->getService()->handle($server, $user, false);
$this->assertNotEmpty($response); $this->assertNotEmpty($response);
$this->assertEquals($key->secret, $response); $this->assertEquals($key->secret, $response);
} }
/**
* Return an instance of the service with mocked dependencies.
*
* @return \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
*/
private function getService(): DaemonKeyProviderService
{
return new DaemonKeyProviderService($this->keyUpdateService, $this->repository);
}
} }

View file

@ -55,7 +55,7 @@ class AuthenticateUsingPasswordServiceTest extends TestCase
$this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf(); $this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf();
$this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('server_token'); $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('server_token');
$response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); $response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort);
$this->assertNotEmpty($response); $this->assertNotEmpty($response);
@ -80,7 +80,7 @@ class AuthenticateUsingPasswordServiceTest extends TestCase
$this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf(); $this->repository->shouldReceive('withColumns')->with(['id', 'node_id', 'owner_id', 'uuid'])->once()->andReturnSelf();
$this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server); $this->repository->shouldReceive('getByUuid')->with($server->uuidShort)->once()->andReturn($server);
$this->keyProviderService->shouldReceive('handle')->with($server->id, $user->id)->once()->andReturn('server_token'); $this->keyProviderService->shouldReceive('handle')->with($server, $user)->once()->andReturn('server_token');
$response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort); $response = $this->getService()->handle($user->username, 'password', 1, $server->uuidShort);
$this->assertNotEmpty($response); $this->assertNotEmpty($response);

View file

@ -11,6 +11,7 @@ namespace Tests\Unit\Services\Subusers;
use Mockery as m; use Mockery as m;
use Tests\TestCase; use Tests\TestCase;
use Pterodactyl\Models\User;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Models\Subuser; use Pterodactyl\Models\Subuser;
use Tests\Traits\MocksRequestException; use Tests\Traits\MocksRequestException;
@ -81,13 +82,14 @@ class SubuserUpdateServiceTest extends TestCase
{ {
$subuser = factory(Subuser::class)->make(); $subuser = factory(Subuser::class)->make();
$subuser->setRelation('server', factory(Server::class)->make()); $subuser->setRelation('server', factory(Server::class)->make());
$subuser->setRelation('user', factory(User::class)->make());
$this->repository->shouldReceive('getWithServer')->with($subuser)->once()->andReturn($subuser); $this->repository->shouldReceive('loadServerAndUserRelations')->with($subuser)->once()->andReturn($subuser);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturnNull(); $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturnNull();
$this->permissionService->shouldReceive('handle')->with($subuser->id, ['some-permission'])->once()->andReturnNull(); $this->permissionService->shouldReceive('handle')->with($subuser->id, ['some-permission'])->once()->andReturnNull();
$this->keyProviderService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id, false)->once()->andReturn('test123'); $this->keyProviderService->shouldReceive('handle')->with($subuser->server, $subuser->user, false)->once()->andReturn('test123');
$this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andReturnSelf(); $this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andReturnSelf();
$this->daemonRepository->shouldReceive('revokeAccessKey')->with('test123')->once()->andReturnNull(); $this->daemonRepository->shouldReceive('revokeAccessKey')->with('test123')->once()->andReturnNull();
@ -106,13 +108,14 @@ class SubuserUpdateServiceTest extends TestCase
$subuser = factory(Subuser::class)->make(); $subuser = factory(Subuser::class)->make();
$subuser->setRelation('server', factory(Server::class)->make()); $subuser->setRelation('server', factory(Server::class)->make());
$subuser->setRelation('user', factory(User::class)->make());
$this->repository->shouldReceive('getWithServer')->with($subuser)->once()->andReturn($subuser); $this->repository->shouldReceive('loadServerAndUserRelations')->with($subuser)->once()->andReturn($subuser);
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturnNull(); $this->permissionRepository->shouldReceive('deleteWhere')->with([['subuser_id', '=', $subuser->id]])->once()->andReturnNull();
$this->permissionService->shouldReceive('handle')->with($subuser->id, [])->once()->andReturnNull(); $this->permissionService->shouldReceive('handle')->with($subuser->id, [])->once()->andReturnNull();
$this->keyProviderService->shouldReceive('handle')->with($subuser->server_id, $subuser->user_id, false)->once()->andReturn('test123'); $this->keyProviderService->shouldReceive('handle')->with($subuser->server, $subuser->user, false)->once()->andReturn('test123');
$this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andThrow($this->getExceptionMock()); $this->daemonRepository->shouldReceive('setNode')->with($subuser->server->node_id)->once()->andThrow($this->getExceptionMock());
$this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('rollBack')->withNoArgs()->once()->andReturnNull();