Failed Conditions
Pull Request — master (#13)
by Adrien
05:33 queued 02:11
created

DebugAcl   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 56
c 1
b 0
f 0
dl 0
loc 135
ccs 45
cts 45
cp 1
rs 10
wmc 14

7 Methods

Rating   Name   Duplication   Size   Complexity  
A show() 0 11 1
A deny() 0 6 1
A allow() 0 6 1
A storePrivileges() 0 7 2
A isAllowed() 0 20 4
A wrapAssertion() 0 17 4
A getPrivileges() 0 23 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Acl;
6
7
use Ecodev\Felix\Acl\Assertion\NamedAssertion;
8
use Laminas\Permissions\Acl\Assertion\AssertionInterface;
9
use Laminas\Permissions\Acl\Assertion\CallbackAssertion;
10
use Laminas\Permissions\Acl\Resource\ResourceInterface;
11
use Laminas\Permissions\Acl\Role\RoleInterface;
12
13
/**
14
 * Debug ACL is used to mirror normal ACL but with assertions configured but disabled. This allows
15
 * to query the entire ACL configuration, including configured assertions, while still leveraging
16
 * the complex underlying logic of Laminas ACL.
17
 *
18
 * @internal
19
 */
20
final class DebugAcl extends \Laminas\Permissions\Acl\Acl
21
{
22
    /**
23
     * @var string[]
24
     */
25
    private array $usedAllowAssertions = [];
26
27
    /**
28
     * @var string[]
29
     */
30
    private array $usedDenyAssertions = [];
31
32
    /**
33
     * @var array<null|string> All possible privileges
34
     */
35
    private array $privileges = [];
36
37 6
    private function wrapAssertion(?AssertionInterface $assert, bool $isAllow): ?AssertionInterface
38
    {
39 6
        if (!$assert) {
40 3
            return null;
41
        }
42
43 4
        return new CallbackAssertion(function () use ($assert, $isAllow) {
44 2
            $name = $assert instanceof NamedAssertion ? $assert->getName() : 'unnamed assertion ' . $assert::class;
45
46 2
            if ($isAllow) {
47 1
                $this->usedAllowAssertions[] = $name;
48
            } else {
49 1
                $this->usedDenyAssertions[] = $name;
50
            }
51
52
            // hardcode response so other assertions from other roles are executed too
53 2
            return !$isAllow;
54
        });
55
    }
56
57 6
    public function allow($roles = null, $resources = null, $privileges = null, ?AssertionInterface $assert = null)
58
    {
59 6
        $this->storePrivileges($privileges);
60 6
        $assert = $this->wrapAssertion($assert, true);
61
62 6
        return parent::allow($roles, $resources, $privileges, $assert);
63
    }
64
65 2
    public function deny($roles = null, $resources = null, $privileges = null, ?AssertionInterface $assert = null)
66
    {
67 2
        $this->storePrivileges($privileges);
68 2
        $assert = $this->wrapAssertion($assert, false);
69
70 2
        return parent::deny($roles, $resources, $privileges, $assert);
71
    }
72
73 6
    private function storePrivileges(null|string|array $privileges): void
74
    {
75 6
        if (!is_array($privileges)) {
0 ignored issues
show
introduced by
The condition is_array($privileges) is always true.
Loading history...
76 3
            $privileges = [$privileges];
77
        }
78
79 6
        $this->privileges = array_merge($this->privileges, $privileges);
80
    }
81
82
    /**
83
     * @return array<null|string>
84
     */
85 1
    public function getPrivileges(): array
86
    {
87
        // Keep most common privileges at the beginning, for convenience
88 1
        $mostCommon = [
89
            null,
90
            'create',
91
            'read',
92
            'update',
93
            'index',
94
            'view',
95
            'edit',
96
            'add',
97
            'delete',
98
            'deleteAll',
99
        ];
100
101 1
        $mostCommonThatExists = array_unique(array_intersect($mostCommon, $this->privileges));
102 1
        $result = array_unique(array_diff($this->privileges, $mostCommonThatExists));
103 1
        sort($result);
104
105 1
        $result = array_merge($mostCommonThatExists, $result);
106
107 1
        return $result;
108
    }
109
110
    /**
111
     * Override parent to provide compatibility with MultipleRoles.
112
     *
113
     * @param RoleInterface|string $role
114
     * @param ResourceInterface|string $resource
115
     * @param string $privilege
116
     */
117 3
    public function isAllowed($role = null, $resource = null, $privilege = null): bool
118
    {
119 3
        $this->usedAllowAssertions = [];
120 3
        $this->usedDenyAssertions = [];
121
122
        // Normalize roles
123 3
        if ($role instanceof MultipleRoles) {
124 2
            $roles = $role->getRoles();
125
        } else {
126 3
            $roles = [$role];
127
        }
128
129
        // If at least one role is allowed, then return early
130 3
        foreach ($roles as $role) {
131 3
            if (parent::isAllowed($role, $resource, $privilege)) {
132 1
                return true;
133
            }
134
        }
135
136 3
        return false;
137
    }
138
139
    /**
140
     * Returns whether the privilege is allowed and the assertions that were used to determine that.s.
141
     *
142
     * @return array{privilege: null|string, allowed: bool, allowIf: string[], denyIf: string[]}
143
     */
144 3
    public function show(null|RoleInterface|string $role, null|ResourceInterface|string $resource, null|string $privilege): array
145
    {
146 3
        $allowed = $this->isAllowed($role, $resource, $privilege);
147 3
        sort($this->usedAllowAssertions);
148 3
        sort($this->usedDenyAssertions);
149
150
        return [
151 3
            'privilege' => $privilege,
152
            'allowed' => $allowed,
153 3
            'allowIf' => array_unique($this->usedAllowAssertions),
154 3
            'denyIf' => array_unique($this->usedDenyAssertions),
155
        ];
156
    }
157
}
158