Completed
Pull Request — master (#105)
by Mikołaj
04:26 queued 01:11
created

CheckAuthenticationMiddleware   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 120
Duplicated Lines 13.33 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 94%

Importance

Changes 0
Metric Value
dl 16
loc 120
ccs 47
cts 50
cp 0.94
rs 10
c 0
b 0
f 0
wmc 13
lcom 1
cbo 10

3 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
C process() 16 61 10
A createTokenErrorResponse() 0 13 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
namespace Shlinkio\Shlink\Rest\Middleware;
3
4
use Acelaya\ZsmAnnotatedServices\Annotation\Inject;
5
use Fig\Http\Message\StatusCodeInterface;
6
use Interop\Http\ServerMiddleware\DelegateInterface;
7
use Interop\Http\ServerMiddleware\MiddlewareInterface;
8
use Psr\Http\Message\ResponseInterface as Response;
9
use Psr\Http\Message\ServerRequestInterface as Request;
10
use Psr\Log\LoggerInterface;
11
use Psr\Log\NullLogger;
12
use Shlinkio\Shlink\Rest\Action\AuthenticateAction;
13
use Shlinkio\Shlink\Rest\Authentication\JWTService;
14
use Shlinkio\Shlink\Rest\Authentication\JWTServiceInterface;
15
use Shlinkio\Shlink\Rest\Exception\AuthenticationException;
16
use Shlinkio\Shlink\Rest\Util\RestUtils;
17
use Zend\Diactoros\Response\JsonResponse;
18
use Zend\Expressive\Router\RouteResult;
19
use Zend\I18n\Translator\TranslatorInterface;
20
use Zend\Stdlib\ErrorHandler;
21
22
class CheckAuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterface
23
{
24
    const AUTHORIZATION_HEADER = 'Authorization';
25
26
    /**
27
     * @var TranslatorInterface
28
     */
29
    private $translator;
30
    /**
31
     * @var JWTServiceInterface
32
     */
33
    private $jwtService;
34
    /**
35
     * @var LoggerInterface
36
     */
37
    private $logger;
38
39
    /**
40
     * CheckAuthenticationMiddleware constructor.
41
     * @param JWTServiceInterface|JWTService $jwtService
42
     * @param TranslatorInterface $translator
43
     * @param LoggerInterface $logger
44
     *
45
     * @Inject({JWTService::class, "translator", "Logger_Shlink"})
46
     */
47 6
    public function __construct(
48
        JWTServiceInterface $jwtService,
49
        TranslatorInterface $translator,
50
        LoggerInterface $logger = null
51
    ) {
52 6
        $this->translator = $translator;
53 6
        $this->jwtService = $jwtService;
54 6
        $this->logger = $logger ?: new NullLogger();
55 6
    }
56
57
    /**
58
     * Process an incoming server request and return a response, optionally delegating
59
     * to the next middleware component to create the response.
60
     *
61
     * @param Request $request
62
     * @param DelegateInterface $delegate
63
     *
64
     * @return Response
65
     */
66 6
    public function process(Request $request, DelegateInterface $delegate)
67
    {
68
        // If current route is the authenticate route or an OPTIONS request, continue to the next middleware
69
        /** @var RouteResult $routeResult */
70 6
        $routeResult = $request->getAttribute(RouteResult::class);
71 6
        if (! isset($routeResult)
72 6
            || $routeResult->isFailure()
73 6
            || $routeResult->getMatchedRouteName() === AuthenticateAction::class
74 6
            || $request->getMethod() === 'OPTIONS'
75
        ) {
76 1
            return $delegate->process($request);
77
        }
78
79
        // Check that the auth header was provided, and that it belongs to a non-expired token
80 5
        if (! $request->hasHeader(self::AUTHORIZATION_HEADER)) {
81 1
            return $this->createTokenErrorResponse();
82
        }
83
84
        // Get token making sure the an authorization type is provided
85 4
        $authToken = $request->getHeaderLine(self::AUTHORIZATION_HEADER);
86 4
        $authTokenParts = explode(' ', $authToken);
87 4 View Code Duplication
        if (count($authTokenParts) === 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88 1
            return new JsonResponse([
89 1
                'error' => RestUtils::INVALID_AUTHORIZATION_ERROR,
90 1
                'message' => sprintf($this->translator->translate(
91 1
                    'You need to provide the Bearer type in the %s header.'
92 1
                ), self::AUTHORIZATION_HEADER),
93 1
            ], self::STATUS_UNAUTHORIZED);
94
        }
95
96
        // Make sure the authorization type is Bearer
97 3
        list($authType, $jwt) = $authTokenParts;
98 3 View Code Duplication
        if (strtolower($authType) !== 'bearer') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
99 1
            return new JsonResponse([
100 1
                'error' => RestUtils::INVALID_AUTHORIZATION_ERROR,
101 1
                'message' => sprintf($this->translator->translate(
102 1
                    'Provided authorization type %s is not supported. Use Bearer instead.'
103 1
                ), $authType),
104 1
            ], self::STATUS_UNAUTHORIZED);
105
        }
106
107
        try {
108 2
            ErrorHandler::start();
109 2
            if (! $this->jwtService->verify($jwt)) {
110 1
                return $this->createTokenErrorResponse();
111
            }
112 1
            ErrorHandler::stop(true);
113
114
            // Update the token expiration and continue to next middleware
115 1
            $jwt = $this->jwtService->refresh($jwt);
116 1
            $response = $delegate->process($request);
117
118
            // Return the response with the updated token on it
119 1
            return $response->withHeader(self::AUTHORIZATION_HEADER, 'Bearer ' . $jwt);
120
        } catch (AuthenticationException $e) {
121
            $this->logger->warning('Tried to access API with an invalid JWT.' . PHP_EOL . $e);
122
            return $this->createTokenErrorResponse();
123
        } finally {
124 2
            ErrorHandler::clean();
125
        }
126
    }
127
128 2
    protected function createTokenErrorResponse()
129
    {
130 2
        return new JsonResponse([
131 2
            'error' => RestUtils::INVALID_AUTH_TOKEN_ERROR,
132 2
            'message' => sprintf(
133 2
                $this->translator->translate(
134
                    'Missing or invalid auth token provided. Perform a new authentication request and send provided '
135 2
                    . 'token on every new request on the "%s" header'
136
                ),
137 2
                self::AUTHORIZATION_HEADER
138
            ),
139 2
        ], self::STATUS_UNAUTHORIZED);
140
    }
141
}
142