Completed
Branch feature/pre-split (49b2ed)
by Anton
03:41
created

PermissionManager::findRule()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 2
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\Security;
9
10
use Spiral\Core\Component;
11
use Spiral\Core\Container\SingletonInterface;
12
use Spiral\Security\Exceptions\PermissionException;
13
use Spiral\Security\Exceptions\RoleException;
14
use Spiral\Support\Patternizer;
15
16
/**
17
 * Default implementation of associations repository and manager. Provides ability to set
18
 * permissions in bulk using * syntax.
19
 *
20
 * Attention, this class is serializable and can be cached in memory.
21
 *
22
 * Example:
23
 * $associations->associate('admin', '*');
24
 * $associations->associate('editor', 'posts.*', Allows::class);
25
 * $associations->associate('user', 'posts.*', Forbid::class);
26
 */
27
class PermissionManager extends Component implements PermissionsInterface, SingletonInterface
28
{
29
    /**
30
     * Roles associated with their permissions.
31
     *
32
     * @var array
33
     */
34
    private $permissions = [];
35
36
    /**
37
     * @var RulesInterface
38
     */
39
    private $rules = null;
40
41
    /**
42
     * @var Patternizer
43
     */
44
    private $patternizer = null;
45
46
    /**
47
     * @param RulesInterface   $rules
48
     * @param Patternizer|null $patternizer
49
     */
50
    public function __construct(RulesInterface $rules, Patternizer $patternizer)
51
    {
52
        $this->rules = $rules;
53
        $this->patternizer = $patternizer;
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function hasRole(string $role): bool
60
    {
61
        return array_key_exists($role, $this->permissions);
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     *
67
     * @return $this
68
     */
69 View Code Duplication
    public function addRole(string $role): PermissionManager
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
70
    {
71
        if ($this->hasRole($role)) {
72
            throw new RoleException("Role '{$role}' already exists");
73
        }
74
75
        $this->permissions[$role] = [
76
            //No associated permissions
77
        ];
78
79
        return $this;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     *
85
     * @return $this
86
     */
87 View Code Duplication
    public function removeRole(string $role): PermissionManager
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88
    {
89
        if (!$this->hasRole($role)) {
90
            throw new RoleException("Undefined role '{$role}'");
91
        }
92
93
        unset($this->permissions[$role]);
94
95
        return $this;
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function getRoles(): array
102
    {
103
        return array_keys($this->permissions);
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109
    public function getRule(string $role, string $permission): RuleInterface
110
    {
111
        if (!$this->hasRole($role)) {
112
            throw new RoleException("Undefined role '{$role}'");
113
        }
114
115
        //Behaviour points to rule
116
        return $this->rules->get($this->findRule($role, $permission));
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     *
122
     * @return $this
123
     */
124
    public function associate(
125
        string $role,
126
        string $permission,
127
        string $rule = 'Spiral\Security\Rules\Allow'
128
    ): PermissionManager {
129
        if (!$this->hasRole($role)) {
130
            throw new RoleException("Undefined role '{$role}'");
131
        }
132
133
        if (!$this->rules->has($rule)) {
134
            throw new PermissionException("Invalid permission rule '{$rule}'");
135
        }
136
137
        $this->permissions[$role][$permission] = $rule;
138
139
        return $this;
140
    }
141
142
    /**
143
     * @param string $role
144
     * @param string $permission
145
     *
146
     * @return string
147
     *
148
     * @throws PermissionException
149
     */
150
    private function findRule(string $role, string $permission): string
151
    {
152
        if (isset($this->permissions[$role][$permission])) {
153
            //O(1) check
1 ignored issue
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
154
            return $this->permissions[$role][$permission];
155
        }
156
157
        //Matching using star syntax
158
        foreach ($this->permissions[$role] as $pattern => $rule) {
159
            if ($this->patternizer->matches($permission, $pattern)) {
160
                return $rule;
161
            }
162
        }
163
164
        throw new PermissionException(
165
            "Unable to resolve role/permission association for '{$role}'/'{$permission}'"
166
        );
167
    }
168
}
169