Passed
Push — master ( 4a12bd...ac5be9 )
by Mr
02:36
created

SecureActionHandler::process()   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 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 6
ccs 0
cts 6
cp 0
crap 6
rs 10
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\Action\ActionInterface;
12
use Daikon\Boot\Middleware\Action\ResponderInterface;
13
use Daikon\Boot\Middleware\Action\ValidatorInterface;
14
use Daikon\Boot\Middleware\ActionHandler;
15
use Daikon\Boot\Middleware\ResolvesDependency;
16
use Daikon\Boot\Middleware\RoutingHandler;
17
use Daikon\Interop\AssertionFailedException;
18
use Daikon\Interop\RuntimeException;
19
use Daikon\Security\Exception\AuthenticationException;
20
use Daikon\Security\Exception\AuthorizationException;
21
use Daikon\Security\Middleware\Action\SecureActionInterface;
22
use Exception;
23
use Fig\Http\Message\StatusCodeInterface;
24
use Middlewares\Utils\Factory;
25
use Psr\Container\ContainerInterface;
26
use Psr\Http\Message\ResponseInterface;
27
use Psr\Http\Message\ServerRequestInterface;
28
use Psr\Http\Server\MiddlewareInterface;
29
use Psr\Http\Server\RequestHandlerInterface;
30
use Psr\Log\LoggerInterface;
31
32
final class SecureActionHandler implements MiddlewareInterface, StatusCodeInterface
33
{
34
    use ResolvesDependency;
35
36
    private ContainerInterface $container;
37
38
    private LoggerInterface $logger;
39
40
    public function __construct(ContainerInterface $container, LoggerInterface $logger)
41
    {
42
        $this->container = $container;
43
        $this->logger = $logger;
44
    }
45
46
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
47
    {
48
        $requestHandler = $request->getAttribute(RoutingHandler::ATTR_REQUEST_HANDLER);
49
        return $requestHandler instanceof ActionInterface
50
            ? $this->executeAction($requestHandler, $request)
51
            : $handler->handle($request);
52
    }
53
54
    private function executeAction(ActionInterface $action, ServerRequestInterface $request): ResponseInterface
55
    {
56
        // Check action access first before running validation
57
        if ($action instanceof SecureActionInterface) {
58
            if (!$action->isAuthorized($request)) {
59
                return Factory::createResponse(self::STATUS_FORBIDDEN);
60
            }
61
        }
62
63
        $request = $action->registerValidator($request);
64
        if ($validator = $this->getValidator($request)) {
65
            $request = $validator($request);
66
        }
67
68
        if (!empty($request->getAttribute(ActionHandler::ATTR_ERRORS))) {
69
            $request = $action->handleError(
70
                $request->withAttribute(
71
                    ActionHandler::ATTR_STATUS_CODE,
72
                    $request->getAttribute(ActionHandler::ATTR_STATUS_CODE, self::STATUS_UNPROCESSABLE_ENTITY)
73
                )
74
            );
75
        } else {
76
            // Run secondary resource authorization after validation
77
            if ($action instanceof SecureActionInterface) {
78
                if (!$action->isAuthorized($request)) {
79
                    return Factory::createResponse(self::STATUS_FORBIDDEN);
80
                }
81
            }
82
83
            try {
84
                $request = $action($request);
85
            } catch (Exception $error) {
86
                $this->logger->error($error->getMessage(), ['exception' => $error]);
87
                switch (true) {
88
                    case $error instanceof AssertionFailedException:
89
                        $statusCode = self::STATUS_UNPROCESSABLE_ENTITY;
90
                        break;
91
                    case $error instanceof AuthenticationException:
92
                        $statusCode = self::STATUS_UNAUTHORIZED;
93
                        break;
94
                    case $error instanceof AuthorizationException:
95
                        $statusCode = self::STATUS_FORBIDDEN;
96
                        break;
97
                    default:
98
                        $statusCode = self::STATUS_INTERNAL_SERVER_ERROR;
99
                }
100
                $request = $action->handleError(
101
                    $request
102
                        ->withAttribute(ActionHandler::ATTR_STATUS_CODE, $statusCode)
103
                        ->withAttribute(ActionHandler::ATTR_ERRORS, $error)
104
                );
105
            }
106
        }
107
        if (!$responder = $this->getResponder($request)) {
108
            throw $error ?? new RuntimeException(
109
                sprintf("Unable to determine responder for '%s'.", get_class($action))
110
            );
111
        }
112
113
        return $responder($request);
114
    }
115
116
    private function getValidator(ServerRequestInterface $request): ?callable
117
    {
118
        return ($validator = $request->getAttribute(ActionHandler::ATTR_VALIDATOR))
119
            ? $this->resolve($this->container, $validator, ValidatorInterface::class)
120
            : null;
121
    }
122
123
    private function getResponder(ServerRequestInterface $request): ?callable
124
    {
125
        return ($responder = $request->getAttribute(ActionHandler::ATTR_RESPONDER))
126
            ? $this->resolve($this->container, $responder, ResponderInterface::class)
127
            : null;
128
    }
129
}
130