Passed
Push — master ( cfe4db...8b5b59 )
by Mr
02:05
created

JwtAuthenticator::isSecure()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 6
ccs 0
cts 6
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 * This file is part of the daikon-cqrs/security-interop project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Daikon\Security\Middleware;
10
11
use Daikon\Boot\Middleware\RoutingHandler;
12
use Daikon\Config\ConfigProviderInterface;
13
use Daikon\Interop\Assertion;
14
use Daikon\Security\Authentication\AuthenticatorInterface;
15
use Daikon\Security\Authentication\JwtAuthenticationServiceInterface;
16
use Daikon\Security\Exception\AuthenticationException;
17
use Daikon\Security\Middleware\Action\SecureActionInterface;
18
use Fig\Http\Message\StatusCodeInterface;
19
use Middlewares\Utils\Factory;
20
use Psr\Http\Message\ResponseInterface;
21
use Psr\Http\Message\ServerRequestInterface;
22
use Psr\Http\Server\MiddlewareInterface;
23
use Psr\Http\Server\RequestHandlerInterface;
24
25
final class JwtAuthenticator implements MiddlewareInterface, StatusCodeInterface
26
{
27
    public const ATTR_AUTHENTICATOR = '_authenticator';
28
29
    private ConfigProviderInterface $config;
30
31
    private JwtAuthenticationServiceInterface $authenticationService;
32
33
    public function __construct(
34
        ConfigProviderInterface $config,
35
        JwtAuthenticationServiceInterface $authenticationService
36
    ) {
37
        $this->config = $config;
38
        $this->authenticationService = $authenticationService;
39
    }
40
41
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
42
    {
43
        $authConfig = $this->config->get('project.authentication', []);
44
        Assertion::true(
45
            is_a($authConfig['default_role'], AuthenticatorInterface::class, true),
46
            sprintf("Authentication default role must implement '%s'.", AuthenticatorInterface::class)
47
        );
48
        $jwtAttribute = $authConfig['jwt']['attribute'] ?? JwtDecoder::DEFAULT_ATTR_JWT;
49
        $xsrfAttribute = $authConfig['xsrf']['attribute'] ?? JwtDecoder::DEFAULT_ATTR_XSRF;
50
51
        $jwt = $request->getAttribute($jwtAttribute);
52
        $xsrfToken = $request->getAttribute($xsrfAttribute);
53
54
        try {
55
            if ($this->isSecure($request)) {
56
                if (!$jwt) {
57
                    throw new AuthenticationException('Missing JWT.');
58
                }
59
                if (!$xsrfToken) {
60
                    throw new AuthenticationException('Missing XSRF token.');
61
                }
62
            }
63
64
            if ($jwt) {
65
                if (!$jwt->uid || !$jwt->jti) {
66
                    throw new AuthenticationException('Invalid JWT.');
67
                }
68
                if ($jwt->xsrf !== $xsrfToken) {
69
                    throw new AuthenticationException('XSRF token does not match JWT.');
70
                }
71
                /** @var AuthenticatorInterface $authenticator */
72
                $authenticator = $this->authenticationService->authenticateJWT($jwt->uid, $jwt->jti, $jwt->xsrf);
73
            }
74
        } catch (AuthenticationException $error) {
75
            return Factory::createResponse(self::STATUS_UNAUTHORIZED);
76
        }
77
78
        return $handler->handle(
79
            $request->withAttribute(self::ATTR_AUTHENTICATOR, $authenticator ?? new $authConfig['default_role'])
80
        );
81
    }
82
83
    private function isSecure(ServerRequestInterface $request): bool
84
    {
85
        $requestHandler = $request->getAttribute(RoutingHandler::ATTR_REQUEST_HANDLER);
86
        return $requestHandler instanceof SecureActionInterface
87
            ? $requestHandler->isSecure()
88
            : false;
89
    }
90
}
91