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

CheckAuthenticationMiddleware::process()   C

Complexity

Conditions 10
Paths 34

Size

Total Lines 61
Code Lines 39

Duplication

Lines 16
Ratio 26.23 %

Code Coverage

Tests 34
CRAP Score 10.0533

Importance

Changes 0
Metric Value
cc 10
eloc 39
nc 34
nop 2
dl 16
loc 61
ccs 34
cts 37
cp 0.9189
crap 10.0533
rs 6.4757
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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