Passed
Push — master ( e7ebae...ecc5ac )
by Valentin
04:51 queued 11s
created

GuardPermissionsProvider::makePermission()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 7
nc 4
nop 3
dl 0
loc 14
rs 9.6111
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Domain;
6
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use Spiral\Core\Container\SingletonInterface;
9
use Spiral\Core\Exception\ControllerException;
10
use Spiral\Core\Exception\InterceptorException;
11
use Spiral\Domain\Annotation\Guarded;
12
use Spiral\Domain\Annotation\GuardNamespace;
13
14
final class GuardPermissionsProvider implements PermissionsProviderInterface, SingletonInterface
15
{
16
    private const FAILURE_MAP = [
17
        'unauthorized' => ControllerException::UNAUTHORIZED,
18
        'badAction'    => ControllerException::BAD_ACTION,
19
        'notFound'     => ControllerException::NOT_FOUND,
20
        'error'        => ControllerException::ERROR,
21
    ];
22
23
    /** @var array */
24
    private $cache = [];
25
26
    /** @var string|null */
27
    private $namespace;
28
29
    /** @var AnnotationReader */
30
    private $reader;
31
32
    public function __construct(AnnotationReader $reader, string $namespace = null)
33
    {
34
        $this->reader = $reader;
35
        $this->namespace = $namespace;
36
    }
37
38
    /**
39
     * Get method RBAC permission if any. Automatically merges with controller namespace.
40
     *
41
     * @param string $controller
42
     * @param string $action
43
     * @return array|null
44
     *
45
     */
46
    public function getPermission(string $controller, string $action): ?array
47
    {
48
        $key = sprintf('%s:%s', $controller, $action);
49
        if (!array_key_exists($key, $this->cache)) {
50
            $this->cache[$key] = $this->generatePermission($controller, $action);
51
        }
52
53
        return $this->cache[$key];
54
    }
55
56
    private function generatePermission(string $controller, string $action): ?array
57
    {
58
        try {
59
            $method = new \ReflectionMethod($controller, $action);
60
        } catch (\ReflectionException $e) {
61
            return null;
62
        }
63
64
        $guarded = $this->reader->getMethodAnnotation($method, Guarded::class);
65
        if (!$guarded instanceof Guarded) {
66
            return null;
67
        }
68
69
        $namespace = $this->reader->getClassAnnotation($method->getDeclaringClass(), GuardNamespace::class);
70
71
        if ($guarded->permission || ($namespace instanceof GuardNamespace && $namespace->namespace)) {
72
            return [
73
                $this->makePermission($guarded, $method, $namespace),
74
                $this->mapFailureException($guarded),
75
                $guarded->errorMessage ?: sprintf(
76
                    'Unauthorized access `%s`',
77
                    $guarded->permission ?: $method->getName()
78
                ),
79
            ];
80
        }
81
82
        throw new InterceptorException(
83
            sprintf(
84
                'Unable to apply @Guarded without name or @GuardNamespace on `%s`->`%s`',
85
                $method->getDeclaringClass()->getName(),
86
                $method->getName()
87
            )
88
        );
89
    }
90
91
    private function makePermission(Guarded $guarded, \ReflectionMethod $method, ?GuardNamespace $ns): string
92
    {
93
        $permission = [];
94
        if ($this->namespace) {
95
            $permission[] = $this->namespace;
96
        }
97
98
        if ($ns !== null && $ns->namespace) {
99
            $permission[] = $ns->namespace;
100
        }
101
102
        $permission[] = $guarded->permission ?: $method->getName();
103
104
        return implode('.', $permission);
105
    }
106
107
    private function mapFailureException(Guarded $guarded): int
108
    {
109
        return self::FAILURE_MAP[$guarded->else] ?? ControllerException::FORBIDDEN;
110
    }
111
}
112