Completed
Push — master ( 50a40a...cdb457 )
by Arnold
03:12
created

Levels::getUserLevelFromRole()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 15
ccs 9
cts 9
cp 1
rs 10
cc 4
nc 3
nop 0
crap 4
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\User\PartiallyLoggedIn;
13
use Jasny\Auth\UserInterface as User;
14
15
/**
16
 * Authorize by access level.
17
 * @immutable
18
 *
19
 * <code>
20
 *   $authz = new Authz\Levels([
21
 *     'user' => 1,
22
 *     'moderator' => 100,
23
 *     'admin' => 1000
24
 *   ]);
25
 *
26
 *   $auth = new Auth($authz);
27
 * </code>
28
 *
29
 * Levels should be positive integers (greater than 0).
30
 */
31
class Levels implements AuthzInterface
32
{
33
    use StateTrait;
34
35
    /** @var array<string,int> */
36
    protected array $levels;
37
38
    /**
39
     * Cached user level. Service has an immutable state.
40
     */
41
    protected int $userLevel = 0;
42
43
    /**
44
     * AuthzByLevel constructor.
45
     *
46
     * @param array<string,int> $levels
47
     */
48 19
    public function __construct(array $levels)
49
    {
50 19
        $this->levels = $levels;
51 19
    }
52
53
54
    /**
55
     * Get a copy of the service with a modified property and recalculated
56
     * Returns $this if authz hasn't changed.
57
     *
58
     * @param string $property
59
     * @param string $value
60
     * @return static
61
     */
62 15
    protected function withProperty(string $property, $value): self
63
    {
64 15
        $clone = clone $this;
65 15
        $clone->{$property} = $value;
66
67 15
        $clone->calcUserLevel();
68
69 13
        $isSame = $clone->{$property} === $this->{$property} && $clone->userLevel === $this->userLevel;
70
71 13
        return $isSame ? $this : $clone;
72
    }
73
74
    /**
75
     * Get all available authorization roles (for the current context)
76
     *
77
     * @return string[]
78
     */
79 1
    public function getAvailableRoles(): array
80
    {
81 1
        return array_keys($this->levels);
82
    }
83
84
85
    /**
86
     * Check if the current user is logged in and has specified role.
87
     */
88 10
    public function is(string $role): bool
89
    {
90 10
        if (!isset($this->levels[$role])) {
91 1
            trigger_error("Unknown authz role '$role'", E_USER_WARNING); // Catch typos
92 1
            return false;
93
        }
94
95 9
        return $this->userLevel >= $this->levels[$role];
96
    }
97
98
99
    /**
100
     * Get a copy, recalculating the authz level of the user.
101
     * Returns $this if authz hasn't changed.
102
     *
103
     * @return static
104
     */
105 3
    public function recalc(): self
106
    {
107 3
        $clone = clone $this;
108 3
        $clone->calcUserLevel();
109
110 3
        return $clone->userLevel === $this->userLevel ? $this : $clone;
111
    }
112
113
    /**
114
     * Get access level of the current user.
115
     *
116
     * @throws \DomainException for unknown level names
117
     */
118 15
    private function calcUserLevel(): void
119
    {
120 15
        if ($this->user === null || $this->user instanceof PartiallyLoggedIn) {
121 3
            $this->userLevel = 0;
122 3
            return;
123
        }
124
125 12
        $uid = $this->user->getAuthId();
126
127 12
        $role = i\type_check(
128 12
            $this->user->getAuthRole($this->context),
129 12
            ['int', 'string'],
130 12
            new \UnexpectedValueException("For authz levels the role should be string|int, %s returned (uid:$uid)")
131
        );
132
133 11
        if (is_string($role) && !isset($this->levels[$role])) {
134 1
            throw new \DomainException("Authorization level '$role' isn't defined (uid:$uid)");
135
        }
136
137 10
        $this->userLevel = is_string($role) ? $this->levels[$role] : (int)$role;
138 10
    }
139
}
140