Skip to content

Commit 7882250

Browse files
committed
Add more middleware tests
1 parent 133fd17 commit 7882250

13 files changed

+515
-48
lines changed

app/Http/Middleware/Authenticate.php

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,26 @@
44

55
use Closure;
66
use Illuminate\Http\Request;
7-
use Illuminate\Contracts\Auth\Guard;
7+
use Illuminate\Auth\AuthenticationException;
88

99
class Authenticate
1010
{
11-
/**
12-
* The Guard implementation.
13-
*
14-
* @var \Illuminate\Contracts\Auth\Guard
15-
*/
16-
protected $auth;
17-
18-
/**
19-
* Create a new filter instance.
20-
*
21-
* @param \Illuminate\Contracts\Auth\Guard $auth
22-
*/
23-
public function __construct(Guard $auth)
24-
{
25-
$this->auth = $auth;
26-
}
27-
2811
/**
2912
* Handle an incoming request.
3013
*
3114
* @param \Illuminate\Http\Request $request
3215
* @param \Closure $next
3316
* @return mixed
17+
*
18+
* @throws \Illuminate\Auth\AuthenticationException
3419
*/
3520
public function handle(Request $request, Closure $next)
3621
{
37-
if ($this->auth->guest()) {
38-
if ($request->ajax()) {
39-
return response('Unauthorized.', 401);
22+
if (! $request->user()) {
23+
if ($request->ajax() || $request->expectsJson()) {
24+
throw new AuthenticationException();
4025
} else {
41-
return redirect()->guest('auth/login');
26+
return redirect()->route('auth.login');
4227
}
4328
}
4429

app/Http/Middleware/DaemonAuthenticate.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111

1212
use Closure;
1313
use Illuminate\Http\Request;
14-
use Pterodactyl\Models\Node;
15-
use Symfony\Component\HttpKernel\Exception\HttpException;
1614
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
17-
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
15+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
1816

1917
class DaemonAuthenticate
2018
{
@@ -56,15 +54,10 @@ public function handle(Request $request, Closure $next)
5654
}
5755

5856
if (! $request->header('X-Access-Node')) {
59-
throw new HttpException(403);
60-
}
61-
62-
try {
63-
$node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]);
64-
} catch (RecordNotFoundException $exception) {
65-
throw new HttpException(401);
57+
throw new AccessDeniedHttpException;
6658
}
6759

60+
$node = $this->repository->findWhere(['daemonSecret' => $request->header('X-Access-Node')]);
6861
$request->attributes->set('node', $node);
6962

7063
return $next($request);

app/Http/Middleware/LanguageMiddleware.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111

1212
use Closure;
1313
use Illuminate\Http\Request;
14-
use Illuminate\Support\Facades\App;
14+
use Illuminate\Foundation\Application;
1515
use Illuminate\Contracts\Config\Repository;
1616

1717
class LanguageMiddleware
1818
{
19+
/**
20+
* @var \Illuminate\Foundation\Application
21+
*/
22+
private $app;
23+
1924
/**
2025
* @var \Illuminate\Contracts\Config\Repository
2126
*/
@@ -24,10 +29,12 @@ class LanguageMiddleware
2429
/**
2530
* LanguageMiddleware constructor.
2631
*
32+
* @param \Illuminate\Foundation\Application $app
2733
* @param \Illuminate\Contracts\Config\Repository $config
2834
*/
29-
public function __construct(Repository $config)
35+
public function __construct(Application $app, Repository $config)
3036
{
37+
$this->app = $app;
3138
$this->config = $config;
3239
}
3340

@@ -40,7 +47,7 @@ public function __construct(Repository $config)
4047
*/
4148
public function handle(Request $request, Closure $next)
4249
{
43-
App::setLocale($this->config->get('app.locale', 'en'));
50+
$this->app->setLocale($this->config->get('app.locale', 'en'));
4451

4552
return $next($request);
4653
}

app/Http/Middleware/RedirectIfAuthenticated.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
class RedirectIfAuthenticated
1010
{
1111
/**
12-
* @var \Illuminate\Contracts\Auth\Guard
12+
* @var \Illuminate\Auth\AuthManager
1313
*/
1414
private $authManager;
1515

@@ -34,7 +34,7 @@ public function __construct(AuthManager $authManager)
3434
public function handle(Request $request, Closure $next, string $guard = null)
3535
{
3636
if ($this->authManager->guard($guard)->check()) {
37-
return redirect(route('index'));
37+
return redirect()->route('index');
3838
}
3939

4040
return $next($request);

app/Http/Middleware/RequireTwoFactorAuthentication.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,27 +73,23 @@ public function __construct(AlertsMessageBag $alert, Settings $settings)
7373
*/
7474
public function handle(Request $request, Closure $next)
7575
{
76-
// Ignore non-users
7776
if (! $request->user()) {
7877
return $next($request);
7978
}
8079

81-
// Skip the 2FA pages
8280
if (in_array($request->route()->getName(), $this->except)) {
8381
return $next($request);
8482
}
8583

86-
// Get the setting
8784
switch ((int) $this->settings->get('2fa', 0)) {
8885
case self::LEVEL_NONE:
8986
return $next($request);
90-
87+
break;
9188
case self::LEVEL_ADMIN:
92-
if (! $request->user()->root_admin) {
89+
if (! $request->user()->root_admin || $request->user()->use_totp) {
9390
return $next($request);
9491
}
9592
break;
96-
9793
case self::LEVEL_ALL:
9894
if ($request->user()->use_totp) {
9995
return $next($request);

database/factories/ModelFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
$factory->define(Pterodactyl\Models\Node::class, function (Faker\Generator $faker) {
7575
return [
7676
'id' => $faker->unique()->randomNumber(),
77+
'uuid' => $faker->unique()->uuid,
7778
'public' => true,
7879
'name' => $faker->firstName,
7980
'fqdn' => $faker->ipv4,

tests/Traits/Http/MocksMiddlewareClosure.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88

99
trait MocksMiddlewareClosure
1010
{
11-
/**
12-
* @var \Illuminate\Http\Request
13-
*/
14-
protected $request;
15-
1611
/**
1712
* Provide a closure to be used when validating that the response from the middleware
1813
* is the same request object we passed into it.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Tests\Unit\Http\Middleware;
4+
5+
use Pterodactyl\Models\User;
6+
use Pterodactyl\Http\Middleware\AdminAuthenticate;
7+
use Symfony\Component\HttpKernel\Exception\HttpException;
8+
9+
class AdminAuthenticateTest extends MiddlewareTestCase
10+
{
11+
/**
12+
* Test that an admin is authenticated.
13+
*/
14+
public function testAdminsAreAuthenticated()
15+
{
16+
$user = factory(User::class)->make(['root_admin' => 1]);
17+
18+
$this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user);
19+
20+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
21+
}
22+
23+
/**
24+
* Test that a missing user in the request triggers an error.
25+
*/
26+
public function testExceptionIsThrownIfUserDoesNotExist()
27+
{
28+
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
29+
30+
try {
31+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
32+
} catch (HttpException $exception) {
33+
$this->assertEquals(403, $exception->getStatusCode());
34+
}
35+
}
36+
37+
/**
38+
* Test that an exception is thrown if the user is not an admin.
39+
*/
40+
public function testExceptionIsThrownIfUserIsNotAnAdmin()
41+
{
42+
$user = factory(User::class)->make(['root_admin' => 0]);
43+
44+
$this->request->shouldReceive('user')->withNoArgs()->twice()->andReturn($user);
45+
46+
try {
47+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
48+
} catch (HttpException $exception) {
49+
$this->assertEquals(403, $exception->getStatusCode());
50+
}
51+
}
52+
53+
/**
54+
* Return an instance of the middleware using mocked dependencies.
55+
*
56+
* @return \Pterodactyl\Http\Middleware\AdminAuthenticate
57+
*/
58+
private function getMiddleware(): AdminAuthenticate
59+
{
60+
return new AdminAuthenticate();
61+
}
62+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Tests\Unit\Http\Middleware;
4+
5+
use Illuminate\Http\RedirectResponse;
6+
use Pterodactyl\Http\Middleware\Authenticate;
7+
8+
class AuthenticateTest extends MiddlewareTestCase
9+
{
10+
/**
11+
* Test that a logged in user validates correctly.
12+
*/
13+
public function testLoggedInUser()
14+
{
15+
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturn(true);
16+
17+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
18+
}
19+
20+
/**
21+
* Test that a logged out user results in a redirect.
22+
*/
23+
public function testLoggedOutUser()
24+
{
25+
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
26+
$this->request->shouldReceive('ajax')->withNoArgs()->once()->andReturn(false);
27+
$this->request->shouldReceive('expectsJson')->withNoArgs()->once()->andReturn(false);
28+
29+
$response = $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
30+
$this->assertInstanceOf(RedirectResponse::class, $response);
31+
$this->assertEquals(302, $response->getStatusCode());
32+
$this->assertEquals(route('auth.login'), $response->getTargetUrl());
33+
}
34+
35+
/**
36+
* Test that a logged out user via an API/Ajax request returns a HTTP error.
37+
*
38+
* @expectedException \Illuminate\Auth\AuthenticationException
39+
*/
40+
public function testLoggedOUtUserApiRequest()
41+
{
42+
$this->request->shouldReceive('user')->withNoArgs()->once()->andReturnNull();
43+
$this->request->shouldReceive('ajax')->withNoArgs()->once()->andReturn(true);
44+
45+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
46+
}
47+
48+
/**
49+
* Return an instance of the middleware using mocked dependencies.
50+
*
51+
* @return \Pterodactyl\Http\Middleware\Authenticate
52+
*/
53+
private function getMiddleware(): Authenticate
54+
{
55+
return new Authenticate();
56+
}
57+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
namespace Tests\Unit\Http\Middleware;
4+
5+
use Mockery as m;
6+
use Pterodactyl\Models\Node;
7+
use Pterodactyl\Http\Middleware\DaemonAuthenticate;
8+
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
9+
10+
class DaemonAuthenticateTest extends MiddlewareTestCase
11+
{
12+
/**
13+
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface|\Mockery\Mock
14+
*/
15+
private $repository;
16+
17+
/**
18+
* Setup tests.
19+
*/
20+
public function setUp()
21+
{
22+
parent::setUp();
23+
24+
$this->repository = m::mock(NodeRepositoryInterface::class);
25+
}
26+
27+
/**
28+
* Test a valid daemon connection.
29+
*/
30+
public function testValidDaemonConnection()
31+
{
32+
$node = factory(Node::class)->make();
33+
34+
$this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.name');
35+
$this->request->shouldReceive('header')->with('X-Access-Node')->twice()->andReturn($node->uuid);
36+
37+
$this->repository->shouldReceive('findWhere')->with(['daemonSecret' => $node->uuid])->once()->andReturn($node);
38+
39+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
40+
$this->assertRequestHasAttribute('node');
41+
$this->assertRequestAttributeEquals($node, 'node');
42+
}
43+
44+
/**
45+
* Test that ignored routes do not continue through the middleware.
46+
*/
47+
public function testIgnoredRouteShouldContinue()
48+
{
49+
$this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('daemon.configuration');
50+
51+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
52+
$this->assertRequestMissingAttribute('node');
53+
}
54+
55+
/**
56+
* Test that a request missing a X-Access-Node header causes an exception.
57+
*
58+
* @expectedException \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
59+
*/
60+
public function testExceptionThrownIfMissingHeader()
61+
{
62+
$this->request->shouldReceive('route->getName')->withNoArgs()->once()->andReturn('random.name');
63+
$this->request->shouldReceive('header')->with('X-Access-Node')->once()->andReturn(false);
64+
65+
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
66+
}
67+
68+
/**
69+
* Return an instance of the middleware using mocked dependencies.
70+
*
71+
* @return \Pterodactyl\Http\Middleware\DaemonAuthenticate
72+
*/
73+
private function getMiddleware(): DaemonAuthenticate
74+
{
75+
return new DaemonAuthenticate($this->repository);
76+
}
77+
}

0 commit comments

Comments
 (0)