Completed
Pull Request — master (#148)
by Alejandro
04:08
created

CheckAuthenticationMiddleware::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 9
c 0
b 0
f 0
nc 2
nop 4
dl 0
loc 11
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
1
<?php
2
declare(strict_types=1);
3
4
namespace Shlinkio\Shlink\Rest\Middleware;
5
6
use Fig\Http\Message\StatusCodeInterface;
7
use Psr\Http\Message\ResponseInterface as Response;
8
use Psr\Http\Message\ServerRequestInterface as Request;
9
use Psr\Http\Server\MiddlewareInterface;
10
use Psr\Http\Server\RequestHandlerInterface;
11
use Psr\Log\LoggerInterface;
12
use Psr\Log\NullLogger;
13
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
14
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
15
use Shlinkio\Shlink\Rest\Util\RestUtils;
16
use Zend\Diactoros\Response\JsonResponse;
17
use Zend\Expressive\Router\RouteResult;
18
use Zend\I18n\Translator\TranslatorInterface;
19
use Zend\Stdlib\ErrorHandler;
20
21
class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface
22
{
23
    public const AUTHORIZATION_HEADER = 'Authorization';
24
25
    /**
26
     * @var TranslatorInterface
27
     */
28
    private $translator;
29
    /**
30
     * @var JWTServiceInterface
31
     */
32
    private $jwtService;
33
    /**
34
     * @var LoggerInterface
35
     */
36
    private $logger;
37
    /**
38
     * @var array
39
     */
40
    private $routesWhitelist;
41
42 6
    public function __construct(
43
        JWTServiceInterface $jwtService,
44
        TranslatorInterface $translator,
45
        array $routesWhitelist,
46
        LoggerInterface $logger = null
47
    ) {
48 6
        $this->translator = $translator;
49 6
        $this->jwtService = $jwtService;
50 6
        $this->routesWhitelist = $routesWhitelist;
51 6
        $this->logger = $logger ?: new NullLogger();
0 ignored issues
show
Documentation Bug introduced by
It seems like $logger ?: new \Psr\Log\NullLogger() can also be of type object<Psr\Log\NullLogger>. However, the property $logger is declared as type object<Psr\Log\LoggerInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
52 6
    }
53
54
    /**
55
     * Process an incoming server request and return a response, optionally delegating
56
     * to the next middleware component to create the response.
57
     *
58
     * @param Request $request
59
     * @param RequestHandlerInterface $handler
60
     *
61
     * @return Response
62
     * @throws \InvalidArgumentException
63
     * @throws \ErrorException
64
     */
65 6
    public function process(Request $request, RequestHandlerInterface $handler): Response
66
    {
67
        // If current route is the authenticate route or an OPTIONS request, continue to the next middleware
68
        /** @var RouteResult|null $routeResult */
69 6
        $routeResult = $request->getAttribute(RouteResult::class);
70 6
        if ($routeResult === null
71 6
            || $routeResult->isFailure()
72 6
            || $request->getMethod() === 'OPTIONS'
73 6
            || \in_array($routeResult->getMatchedRouteName(), $this->routesWhitelist, true)
74
        ) {
75 1
            return $handler->handle($request);
76
        }
77
78
        // Check that the auth header was provided, and that it belongs to a non-expired token
79 5
        if (! $request->hasHeader(self::AUTHORIZATION_HEADER)) {
80 1
            return $this->createTokenErrorResponse();
81
        }
82
83
        // Get token making sure the an authorization type is provided
84 4
        $authToken = $request->getHeaderLine(self::AUTHORIZATION_HEADER);
85 4
        $authTokenParts = \explode(' ', $authToken);
86 4
        if (\count($authTokenParts) === 1) {
87 1
            return new JsonResponse([
88 1
                'error' => RestUtils::INVALID_AUTHORIZATION_ERROR,
89 1
                'message' => \sprintf($this->translator->translate(
90 1
                    'You need to provide the Bearer type in the %s header.'
91 1
                ), self::AUTHORIZATION_HEADER),
92 1
            ], self::STATUS_UNAUTHORIZED);
93
        }
94
95
        // Make sure the authorization type is Bearer
96 3
        [$authType, $jwt] = $authTokenParts;
0 ignored issues
show
Bug introduced by
The variable $authType does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $jwt seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
97 3
        if (\strtolower($authType) !== 'bearer') {
98 1
            return new JsonResponse([
99 1
                'error' => RestUtils::INVALID_AUTHORIZATION_ERROR,
100 1
                'message' => \sprintf($this->translator->translate(
101 1
                    'Provided authorization type %s is not supported. Use Bearer instead.'
102 1
                ), $authType),
103 1
            ], self::STATUS_UNAUTHORIZED);
104
        }
105
106
        try {
107 2
            ErrorHandler::start();
108 2
            if (! $this->jwtService->verify($jwt)) {
0 ignored issues
show
Bug introduced by
The variable $jwt seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
109 1
                return $this->createTokenErrorResponse();
110
            }
111 1
            ErrorHandler::stop(true);
112
113
            // Update the token expiration and continue to next middleware
114 1
            $jwt = $this->jwtService->refresh($jwt);
0 ignored issues
show
Bug introduced by
The variable $jwt seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
115 1
            $response = $handler->handle($request);
116
117
            // Return the response with the updated token on it
118 1
            return $response->withHeader(self::AUTHORIZATION_HEADER, 'Bearer ' . $jwt);
119
        } catch (AuthenticationException $e) {
120
            $this->logger->warning('Tried to access API with an invalid JWT.' . PHP_EOL . $e);
121
            return $this->createTokenErrorResponse();
122
        } finally {
123 2
            ErrorHandler::clean();
124
        }
125
    }
126
127
    /**
128
     * @return JsonResponse
129
     * @throws \InvalidArgumentException
130
     */
131 2
    private function createTokenErrorResponse(): JsonResponse
132
    {
133 2
        return new JsonResponse([
134 2
            'error' => RestUtils::INVALID_AUTH_TOKEN_ERROR,
135 2
            'message' => \sprintf(
136 2
                $this->translator->translate(
137
                    'Missing or invalid auth token provided. Perform a new authentication request and send provided '
138 2
                    . 'token on every new request on the "%s" header'
139
                ),
140 2
                self::AUTHORIZATION_HEADER
141
            ),
142 2
        ], self::STATUS_UNAUTHORIZED);
143
    }
144
}
145