Passed
Push — master ( 40955b...43eb1d )
by Anton
02:39
created

GuardInterceptor::process()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 10
c 2
b 0
f 1
dl 0
loc 17
rs 9.9332
cc 3
nc 2
nop 4
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Domain;
13
14
use Doctrine\Common\Annotations\AnnotationReader;
15
use Spiral\Core\CoreInterceptorInterface;
16
use Spiral\Core\CoreInterface;
17
use Spiral\Core\Exception\ControllerException;
18
use Spiral\Core\Exception\InterceptorException;
19
use Spiral\Domain\Annotation\Guarded;
20
use Spiral\Domain\Annotation\GuardNamespace;
21
use Spiral\Security\GuardInterface;
22
23
/**
24
 * Interceptor provides the ability to check the access to the controllers and controller methods using security
25
 * component and annotations "Guarded" and "GuardNamespace".
26
 */
27
final class GuardInterceptor implements CoreInterceptorInterface
28
{
29
    /** @var GuardInterface */
30
    private $guard;
31
32
    /** @var array */
33
    private $cache = [];
34
35
    /**
36
     * @param GuardInterface $guard
37
     */
38
    public function __construct(GuardInterface $guard)
39
    {
40
        $this->guard = $guard;
41
    }
42
43
    /**
44
     * @inheritDoc
45
     */
46
    public function process(string $controller, string $action, array $parameters, CoreInterface $core)
47
    {
48
        $permission = $this->getPermissions($controller, $action);
49
50
        if ($permission !== null && !$this->guard->allows($permission[0], $parameters)) {
51
            throw new ControllerException(
52
                sprintf(
53
                    'Unauthorized permission `%s` for action `%s`->`%s`',
54
                    $permission[0],
55
                    $controller,
56
                    $action
57
                ),
58
                $permission[1]
59
            );
60
        }
61
62
        return $core->callAction($controller, $action, $parameters);
63
    }
64
65
    /**
66
     * Get method RBAC permission if any. Automatically merges with controller namespace.
67
     *
68
     * @param string $controller
69
     * @param string $action
70
     * @return array|null
71
     *
72
     * @throws \Doctrine\Common\Annotations\AnnotationException
73
     */
74
    private function getPermissions(string $controller, string $action): ?array
75
    {
76
        $key = sprintf('%s:%s', $controller, $action);
77
        if (array_key_exists($key, $this->cache)) {
78
            return $this->cache[$key];
79
        }
80
81
        $this->cache[$key] = null;
82
        try {
83
            $method = new \ReflectionMethod($controller, $action);
84
        } catch (\ReflectionException $e) {
85
            return [];
86
        }
87
88
        $reader = new AnnotationReader();
89
90
        /** @var GuardNamespace $namespace */
91
        $namespace = $reader->getClassAnnotation(
92
            $method->getDeclaringClass(),
93
            GuardNamespace::class
94
        );
95
96
        /** @var Guarded $action */
97
        $action = $reader->getMethodAnnotation(
98
            $method,
99
            Guarded::class
100
        );
101
102
        if ($action === null) {
103
            return null;
104
        }
105
106
        if ($action->permission === null && $namespace === null) {
107
            throw new InterceptorException(
108
                'Unable to apply @Guarded annotation without specified permission name or @GuardNamespace'
109
            );
110
        }
111
112
        $this->cache[$key] = $this->makePermission($action, $method, $namespace);
113
114
        return $this->cache[$key];
115
    }
116
117
    /**
118
     * Generates permissions for the method or controller.
119
     *
120
     * @param Guarded             $guarded
121
     * @param \ReflectionMethod   $method
122
     * @param GuardNamespace|null $ns
123
     * @return array
124
     */
125
    private function makePermission(Guarded $guarded, \ReflectionMethod $method, ?GuardNamespace $ns): array
126
    {
127
        $permission = [
128
            $guarded->permission ?? $method->getName(),
129
            ControllerException::FORBIDDEN
130
        ];
131
132
        if ($guarded->permission === null && $ns === null) {
133
            throw new InterceptorException(sprintf(
134
                'Unable to apply @Guarded without name or @GuardNamespace on `%s`->`%s`',
135
                $method->getDeclaringClass()->getName(),
136
                $method->getName()
137
            ));
138
        }
139
140
        if ($ns !== null) {
141
            $permission[0] = sprintf('%s.%s', $ns->namespace, $permission[0]);
142
        }
143
144
        switch ($guarded->else) {
145
            case 'badAction':
146
                $permission[1] = ControllerException::BAD_ACTION;
147
                break;
148
            case 'notFound':
149
                $permission[1] = ControllerException::NOT_FOUND;
150
                break;
151
            case 'error':
152
                $permission[1] = ControllerException::ERROR;
153
                break;
154
        }
155
156
        return $permission;
157
    }
158
}
159