Completed
Pull Request — master (#11)
by Arnold
03:10
created

Levels::recalc()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Jasny\Auth\Authz;
6
7
use Improved as i;
8
use Improved\IteratorPipeline\Pipeline;
9
use Jasny\Auth\AuthzInterface;
10
use Jasny\Auth\AuthzInterface as Authz;
11
use Jasny\Auth\ContextInterface as Context;
12
use Jasny\Auth\UserInterface as User;
13
14
/**
15
 * Authorize by access level.
16
 * @immutable
17
 *
18
 * <code>
19
 *   $authz = new Authz\Levels([
20
 *     'user' => 1,
21
 *     'moderator' => 100,
22
 *     'admin' => 1000
23
 *   ]);
24
 *
25
 *   $auth = new Auth($authz);
26
 * </code>
27
 *
28
 * Levels should be positive integers.
29
 */
30
class Levels implements AuthzInterface
31
{
32
    use StateTrait;
33
34
    /** @var array<string,int> */
35
    protected array $levels;
36
37
    /**
38
     * Cached user level. Service has an immutable state.
39
     */
40
    protected int $userLevel = 0;
41
42
    /**
43
     * AuthzByLevel constructor.
44
     *
45
     * @param array<string,int> $levels
46
     */
47 17
    public function __construct(array $levels)
48
    {
49 17
        $this->levels = $levels;
50 17
    }
51
52
53
    /**
54
     * Get a copy of the service with a modified property and recalculated
55
     * Returns $this if authz hasn't changed.
56
     *
57
     * @param string $property
58
     * @param string $value
59
     * @return static
60
     */
61 14
    private function withProperty(string $property, $value): self
0 ignored issues
show
Unused Code introduced by
The method withProperty() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
62
    {
63 14
        $clone = clone $this;
64 14
        $clone->{$property} = $value;
65
66 14
        $clone->calcUserLevel();
67
68 12
        $isSame = $clone->{$property} === $this->{$property} && $clone->userLevel === $this->userLevel;
69
70 12
        return $isSame ? $this : $clone;
71
    }
72
73
    /**
74
     * Get all available authorization roles (for the current context)
75
     *
76
     * @return string[]
77
     */
78 1
    public function getAvailableRoles(): array
79
    {
80 1
        return array_keys($this->levels);
81
    }
82
83
    /**
84
     * Check if the current user is logged in and has specified role.
85
     */
86 10
    public function is(string $role): bool
87
    {
88 10
        if (!isset($this->levels[$role])) {
89 1
            trigger_error("Unknown authz role '$role'", E_USER_WARNING); // Catch typos
90 1
            return false;
91
        }
92
93 9
        return $this->userLevel >= $this->levels[$role];
94
    }
95
96
97
    /**
98
     * Get a copy, recalculating the authz level of the user.
99
     * Returns $this if authz hasn't changed.
100
     *
101
     * @return static
102
     */
103 3
    public function recalc(): self
104
    {
105 3
        $clone = clone $this;
106 3
        $clone->calcUserLevel();
107
108 3
        return $clone->userLevel === $this->userLevel ? $this : $clone;
109
    }
110
111
    /**
112
     * Get access level of the current user.
113
     *
114
     * @throws \DomainException for unknown level names
115
     */
116 14
    private function calcUserLevel(): void
117
    {
118 14
        if ($this->user === null) {
119 2
            $this->userLevel = 0;
120 2
            return;
121
        }
122
123 12
        $uid = $this->user->getAuthId();
124
125 12
        $role = i\type_check(
1 ignored issue
show
Bug introduced by
The function type_check was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

125
        $role = /** @scrutinizer ignore-call */ i\type_check(
Loading history...
126 12
            $this->user->getAuthRole($this->context),
127 12
            ['int', 'string'],
128 12
            new \UnexpectedValueException("For authz levels the role should be string|int, %s returned (uid:$uid)")
129
        );
130
131 11
        if (is_string($role) && !isset($this->levels[$role])) {
132 1
            throw new \DomainException("Authorization level '$role' isn't defined (uid:$uid)");
133
        }
134
135 10
        $this->userLevel = is_string($role) ? $this->levels[$role] : (int)$role;
136 10
    }
137
}
138