Completed
Push — master ( 224127...d5dc6c )
by Alejandro
28s
created

errorIsReturnedWhenVerificationFails()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
4
namespace ShlinkioTest\Shlink\Rest\Middleware;
5
6
use Exception;
7
use Fig\Http\Message\RequestMethodInterface;
8
use PHPUnit\Framework\TestCase;
9
use Prophecy\Argument;
10
use Prophecy\Prophecy\ObjectProphecy;
11
use Psr\Container\ContainerExceptionInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Psr\Http\Message\ServerRequestInterface;
14
use Psr\Http\Server\MiddlewareInterface;
15
use Psr\Http\Server\RequestHandlerInterface;
16
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
17
use Shlinkio\Shlink\Rest\Authentication\Plugin\AuthenticationPluginInterface;
18
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPlugin;
19
use Shlinkio\Shlink\Rest\Authentication\RequestToHttpAuthPluginInterface;
20
use Shlinkio\Shlink\Rest\Exception\NoAuthenticationException;
21
use Shlinkio\Shlink\Rest\Exception\VerifyAuthenticationException;
22
use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware;
23
use Shlinkio\Shlink\Rest\Util\RestUtils;
24
use Zend\Diactoros\Response;
25
use Zend\Diactoros\ServerRequestFactory;
26
use Zend\Expressive\Router\Route;
27
use Zend\Expressive\Router\RouteResult;
28
use Zend\I18n\Translator\Translator;
29
use function implode;
30
use function sprintf;
31
use function Zend\Stratigility\middleware;
32
33
class AuthenticationMiddlewareTest extends TestCase
34
{
35
    /**
36
     * @var AuthenticationMiddleware
37
     */
38
    protected $middleware;
39
    /**
40
     * @var ObjectProphecy
41
     */
42
    protected $requestToPlugin;
43
44
    /**
45
     * @var callable
46
     */
47
    protected $dummyMiddleware;
48
49
    public function setUp()
50
    {
51
        $this->requestToPlugin = $this->prophesize(RequestToHttpAuthPluginInterface::class);
52
        $this->middleware = new AuthenticationMiddleware($this->requestToPlugin->reveal(), Translator::factory([]), [
53
            AuthenticateAction::class,
54
        ]);
55
    }
56
57
    /**
58
     * @test
59
     * @dataProvider provideWhitelistedRequests
60
     */
61
    public function someWhiteListedSituationsFallbackToNextMiddleware(ServerRequestInterface $request)
62
    {
63
        $handler = $this->prophesize(RequestHandlerInterface::class);
64
        $handle = $handler->handle($request)->willReturn(new Response());
65
        $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn(
66
            $this->prophesize(AuthenticationPluginInterface::class)->reveal()
67
        );
68
69
        $this->middleware->process($request, $handler->reveal());
70
71
        $handle->shouldHaveBeenCalledTimes(1);
72
        $fromRequest->shouldNotHaveBeenCalled();
73
    }
74
75
    public function provideWhitelistedRequests(): array
76
    {
77
        $dummyMiddleware = $this->getDummyMiddleware();
78
79
        return [
80
            'with no route result' => [ServerRequestFactory::fromGlobals()],
81
            'with failure route result' => [ServerRequestFactory::fromGlobals()->withAttribute(
82
                RouteResult::class,
83
                RouteResult::fromRouteFailure([RequestMethodInterface::METHOD_GET])
84
            )],
85
            'with whitelisted route' => [ServerRequestFactory::fromGlobals()->withAttribute(
86
                RouteResult::class,
87
                RouteResult::fromRoute(
88
                    new Route('foo', $dummyMiddleware, Route::HTTP_METHOD_ANY, AuthenticateAction::class)
89
                )
90
            )],
91
            'with OPTIONS method' => [ServerRequestFactory::fromGlobals()->withAttribute(
92
                RouteResult::class,
93
                RouteResult::fromRoute(new Route('bar', $dummyMiddleware), [])
94
            )->withMethod(RequestMethodInterface::METHOD_OPTIONS)],
95
        ];
96
    }
97
98
    /**
99
     * @test
100
     * @dataProvider provideExceptions
101
     */
102
    public function errorIsReturnedWhenNoValidAuthIsProvided($e)
103
    {
104
        $request = ServerRequestFactory::fromGlobals()->withAttribute(
105
            RouteResult::class,
106
            RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), [])
107
        );
108
        $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willThrow($e);
109
110
        /** @var Response\JsonResponse $response */
111
        $response = $this->middleware->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal());
112
        $payload = $response->getPayload();
113
114
        $this->assertEquals(RestUtils::INVALID_AUTHORIZATION_ERROR, $payload['error']);
115
        $this->assertEquals(sprintf(
116
            'Expected one of the following authentication headers, but none were provided, ["%s"]',
117
            implode('", "', RequestToHttpAuthPlugin::SUPPORTED_AUTH_HEADERS)
118
        ), $payload['message']);
119
        $fromRequest->shouldHaveBeenCalledTimes(1);
120
    }
121
122
    public function provideExceptions(): array
123
    {
124
        return [
125
            [new class extends Exception implements ContainerExceptionInterface {
126
            }],
127
            [NoAuthenticationException::fromExpectedTypes([])],
128
        ];
129
    }
130
131
    /**
132
     * @test
133
     */
134
    public function errorIsReturnedWhenVerificationFails()
135
    {
136
        $request = ServerRequestFactory::fromGlobals()->withAttribute(
137
            RouteResult::class,
138
            RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), [])
139
        );
140
        $plugin = $this->prophesize(AuthenticationPluginInterface::class);
141
142
        $verify = $plugin->verify($request)->willThrow(
143
            VerifyAuthenticationException::withError('the_error', 'the_message')
144
        );
145
        $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn($plugin->reveal());
146
147
        /** @var Response\JsonResponse $response */
148
        $response = $this->middleware->process($request, $this->prophesize(RequestHandlerInterface::class)->reveal());
149
        $payload = $response->getPayload();
150
151
        $this->assertEquals('the_error', $payload['error']);
152
        $this->assertEquals('the_message', $payload['message']);
153
        $verify->shouldHaveBeenCalledTimes(1);
154
        $fromRequest->shouldHaveBeenCalledTimes(1);
155
    }
156
157
    /**
158
     * @test
159
     */
160
    public function updatedResponseIsReturnedWhenVerificationPasses()
161
    {
162
        $newResponse = new Response();
163
        $request = ServerRequestFactory::fromGlobals()->withAttribute(
164
            RouteResult::class,
165
            RouteResult::fromRoute(new Route('bar', $this->getDummyMiddleware()), [])
166
        );
167
        $plugin = $this->prophesize(AuthenticationPluginInterface::class);
168
169
        $verify = $plugin->verify($request)->will(function () {
170
        });
171
        $update = $plugin->update($request, Argument::type(ResponseInterface::class))->willReturn($newResponse);
172
        $fromRequest = $this->requestToPlugin->fromRequest(Argument::any())->willReturn($plugin->reveal());
173
174
        $handler = $this->prophesize(RequestHandlerInterface::class);
175
        $handle = $handler->handle($request)->willReturn(new Response());
176
        $response = $this->middleware->process($request, $handler->reveal());
177
178
        $this->assertSame($response, $newResponse);
179
        $verify->shouldHaveBeenCalledTimes(1);
180
        $update->shouldHaveBeenCalledTimes(1);
181
        $handle->shouldHaveBeenCalledTimes(1);
182
        $fromRequest->shouldHaveBeenCalledTimes(1);
183
    }
184
185
    private function getDummyMiddleware(): MiddlewareInterface
186
    {
187
        return middleware(function () {
188
            return new Response\EmptyResponse();
189
        });
190
    }
191
}
192