Completed
Push — master ( 66ec7e...c9ec0a )
by Matze
07:32
created

Token   A

Complexity

Total Complexity 11

Size/Duplication

Total Lines 94
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Test Coverage

Coverage 0%

Importance

Changes 2
Bugs 0 Features 2
Metric Value
wmc 11
c 2
b 0
f 2
lcom 1
cbo 0
dl 0
loc 94
ccs 0
cts 34
cp 0
rs 10

5 Methods

Rating   Name   Duplication   Size   Complexity  
A addToken() 0 13 1
A getToken() 0 4 1
A getTokensForUser() 0 13 3
A hasUserForRole() 0 13 4
A revoke() 0 16 2
1
<?php
2
3
namespace BrainExe\Core\Authentication;
4
5
use BrainExe\Annotations\Annotations\Service;
6
use BrainExe\Core\Traits\IdGeneratorTrait;
7
use BrainExe\Core\Traits\RedisTrait;
8
use Generator;
9
10
/**
11
 * @Service("Core.Authentication.Token", public=false)
12
 */
13
class Token
14
{
15
16
    const USER_KEY  = 'tokens:user:%s';
17
    const TOKEN_KEY = 'tokens';
18
19
    use RedisTrait;
20
    use IdGeneratorTrait;
21
22
    /**
23
     * @param int $userId
24
     * @param string[] $roles
25
     * @return string
26
     */
27
    public function addToken(int $userId, array $roles = []) : string
28
    {
29
        $token = $this->generateRandomId(32);
30
31
        $redis = $this->getRedis()->pipeline();
32
33
        $redis->sadd(sprintf(self::USER_KEY, $userId), $token);
34
        $redis->hset(self::TOKEN_KEY, $token, json_encode(['userId' => $userId, 'roles' => $roles]));
35
36
        $redis->execute();
37
38
        return $token;
39
    }
40
41
    /**
42
     * @param string $token
43
     * @return array|
44
     */
45
    public function getToken(string $token)
46
    {
47
        return json_decode($this->getRedis()->hget(self::TOKEN_KEY, $token), true);
48
    }
49
50
    /**
51
     * @param int $userId
52
     * @return array[]|Generator
53
     */
54
    public function getTokensForUser(int $userId)
55
    {
56
        $redis     = $this->getRedis();
57
        $tokensIds = $redis->smembers(sprintf(self::USER_KEY, $userId));
58
59
        if (!empty($tokensIds)) {
60
            $tokens = $redis->hmget(self::TOKEN_KEY, $tokensIds);
61
62
            foreach ($tokens as $idx => $token) {
63
                yield $tokensIds[$idx] => json_decode($token, true)['roles'];
64
            }
65
        }
66
    }
67
68
    /**
69
     * @param string $token
70
     * @param string|null $role
71
     * @return int|null
72
     */
73
    public function hasUserForRole(string $token, string $role = null)
74
    {
75
        $tokenData = $this->getToken($token);
76
        if (empty($tokenData)) {
77
            return null;
78
        }
79
80
        if ($role && !in_array($role, $tokenData['roles'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $role of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
81
            return null;
82
        }
83
84
        return $tokenData['userId'];
85
    }
86
87
    /**
88
     * @param string $token
89
     */
90
    public function revoke(string $token)
91
    {
92
        $tokenData = $this->getToken($token);
93
        if (!$tokenData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tokenData of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
94
            return;
95
        }
96
97
        $userId = $tokenData['userId'];
98
99
        $redis = $this->getRedis()->pipeline();
100
101
        $redis->srem(sprintf(self::USER_KEY, $userId), $token);
102
        $redis->hdel(self::TOKEN_KEY, $token);
103
104
        $redis->execute();
105
    }
106
}
107