Completed
Push — devel ( 5aa576...293fc2 )
by Philippe
07:12 queued 04:28
created

RefreshTokenService::delete()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 4.0015

Importance

Changes 0
Metric Value
dl 0
loc 28
c 0
b 0
f 0
ccs 21
cts 22
cp 0.9545
rs 8.5806
cc 4
eloc 20
nc 5
nop 1
crap 4.0015
1
<?php
2
/**
3
 * RefreshTokenService.php
4
 *
5
 * PHP version 5.6+
6
 *
7
 * @author Philippe Gaultier <[email protected]>
8
 * @copyright 2010-2017 Philippe Gaultier
9
 * @license http://www.sweelix.net/license license
10
 * @version 1.1.0
11
 * @link http://www.sweelix.net
12
 * @package sweelix\oauth2\server\services\redis
13
 */
14
15
namespace sweelix\oauth2\server\services\redis;
16
17
use sweelix\oauth2\server\exceptions\DuplicateIndexException;
18
use sweelix\oauth2\server\exceptions\DuplicateKeyException;
19
use sweelix\oauth2\server\interfaces\RefreshTokenModelInterface;
20
use sweelix\oauth2\server\interfaces\RefreshTokenServiceInterface;
21
use yii\db\Exception as DatabaseException;
22
use Yii;
23
24
/**
25
 * This is the refresh token service for redis
26
 *  database structure
27
 *    * oauth2:refreshTokens:<rid> : hash (RefreshToken)
28
 *
29
 * @author Philippe Gaultier <[email protected]>
30
 * @copyright 2010-2017 Philippe Gaultier
31
 * @license http://www.sweelix.net/license license
32
 * @version 1.1.0
33
 * @link http://www.sweelix.net
34
 * @package sweelix\oauth2\server\services\redis
35
 * @since 1.0.0
36
 */
37
class RefreshTokenService extends BaseService implements RefreshTokenServiceInterface
38
{
39
40
    /**
41
     * @var string user namespace (collection for refreshtokens)
42
     */
43
    public $userNamespace = '';
44
45
    /**
46
     * @var string client namespace (collection for refreshtokens)
47
     */
48
    public $clientNamespace = '';
49
50
    /**
51
     * @param string $rid refresh token ID
52
     * @return string refresh token Key
53
     * @since 1.0.0
54
     */
55 7
    protected function getRefreshTokenKey($rid)
56
    {
57 7
        return $this->namespace . ':' . $rid;
58
    }
59
60
    /**
61
     * @param string $uid user ID
62
     * @return string user refresh tokens collection Key
63
     * @since XXX
64
     */
65 8
    protected function getUserRefreshTokensKey($uid)
66
    {
67 8
        return $this->userNamespace . ':' . $uid . ':refreshTokens';
68
    }
69
70
    /**
71
     * @param string $cid client ID
72
     * @return string client refresh tokens collection Key
73
     * @since XXX
74
     */
75 8
    protected function getClientRefreshTokensKey($cid)
76
    {
77 8
        return $this->clientNamespace . ':' . $cid . ':refreshTokens';
78
    }
79
80
    /**
81
     * @inheritdoc
82
     */
83 7
    public function save(RefreshTokenModelInterface $refreshToken, $attributes)
84
    {
85 7
        if ($refreshToken->getIsNewRecord()) {
86 7
            $result = $this->insert($refreshToken, $attributes);
87 7
        } else {
88 1
            $result = $this->update($refreshToken, $attributes);
89
        }
90 7
        return $result;
91
    }
92
93
    /**
94
     * Save Refresh Token
95
     * @param RefreshTokenModelInterface $refreshToken
96
     * @param null|array $attributes attributes to save
97
     * @return bool
98
     * @throws DatabaseException
99
     * @throws DuplicateIndexException
100
     * @throws DuplicateKeyException
101
     * @since 1.0.0
102
     */
103 7
    protected function insert(RefreshTokenModelInterface $refreshToken, $attributes)
104
    {
105 7
        $result = false;
106 7
        if (!$refreshToken->beforeSave(true)) {
107
            return $result;
108
        }
109 7
        $refreshTokenId = $refreshToken->getKey();
110 7
        $refreshTokenKey = $this->getRefreshTokenKey($refreshTokenId);
111 7
        if (empty($refreshToken->userId) === false) {
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
112 7
            $userRefreshTokensKey = $this->getUserRefreshTokensKey($refreshToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
113 7
        } else {
114
            $userRefreshTokensKey = null;
115
        }
116 7
        $clientRefreshTokensKey = $this->getClientRefreshTokensKey($refreshToken->clientId);
0 ignored issues
show
Bug introduced by
Accessing clientId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
117
118
        //check if record exists
119 7
        $entityStatus = (int)$this->db->executeCommand('EXISTS', [$refreshTokenKey]);
120 7
        if ($entityStatus === 1) {
121 1
            throw new DuplicateKeyException('Duplicate key "'.$refreshTokenKey.'"');
122
        }
123
124 7
        $values = $refreshToken->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 103 can also be of type array; however, sweelix\oauth2\server\in...e::getDirtyAttributes() does only seem to accept array<integer,string>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
125 7
        $redisParameters = [$refreshTokenKey];
126 7
        $this->setAttributesDefinitions($refreshToken->attributesDefinition());
127 7
        foreach ($values as $key => $value)
128
        {
129 7
            if ($value !== null) {
130 7
                $redisParameters[] = $key;
131 7
                $redisParameters[] = $this->convertToDatabase($key, $value);
132 7
            }
133 7
        }
134
        //TODO: use EXEC/MULTI to avoid errors
135 7
        $transaction = $this->db->executeCommand('MULTI');
136 7
        if ($transaction === true) {
137
            try {
138 7
                $this->db->executeCommand('HMSET', $redisParameters);
139 7
                if ($userRefreshTokensKey !== null) {
140 7
                    $this->db->executeCommand('SADD', [$userRefreshTokensKey, $refreshTokenId]);
141 7
                }
142 7
                $this->db->executeCommand('SADD', [$clientRefreshTokensKey, $refreshTokenId]);
143 7
                $this->db->executeCommand('EXEC');
144 7
            } catch (DatabaseException $e) {
145
                // @codeCoverageIgnoreStart
146
                // we have a REDIS exception, we should not discard
147
                Yii::trace('Error while inserting entity', __METHOD__);
148
                throw $e;
149
                // @codeCoverageIgnoreEnd
150
            }
151 7
        }
152 7
        $changedAttributes = array_fill_keys(array_keys($values), null);
153 7
        $refreshToken->setOldAttributes($values);
154 7
        $refreshToken->afterSave(true, $changedAttributes);
155 7
        $result = true;
156 7
        return $result;
157
    }
158
159
160
    /**
161
     * Update Refresh Token
162
     * @param RefreshTokenModelInterface $refreshToken
163
     * @param null|array $attributes attributes to save
164
     * @return bool
165
     * @throws DatabaseException
166
     * @throws DuplicateIndexException
167
     * @throws DuplicateKeyException
168
     */
169 1
    protected function update(RefreshTokenModelInterface $refreshToken, $attributes)
170
    {
171 1
        if (!$refreshToken->beforeSave(false)) {
172
            return false;
173
        }
174
175 1
        $values = $refreshToken->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 169 can also be of type array; however, sweelix\oauth2\server\in...e::getDirtyAttributes() does only seem to accept array<integer,string>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
176 1
        $modelKey = $refreshToken->key();
177 1
        $refreshTokenId = isset($values[$modelKey]) ? $values[$modelKey] : $refreshToken->getKey();
178 1
        $refreshTokenKey = $this->getRefreshTokenKey($refreshTokenId);
179
180 1
        if (empty($refreshToken->userId) === false) {
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
181 1
            $userRefreshTokensKey = $this->getUserRefreshTokensKey($refreshToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
182 1
        } else {
183
            $userRefreshTokensKey = null;
184
        }
185 1
        $clientRefreshTokensKey = $this->getClientRefreshTokensKey($refreshToken->clientId);
0 ignored issues
show
Bug introduced by
Accessing clientId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
186
187 1
        if (isset($values[$modelKey]) === true) {
188 1
            $newRefreshTokenKey = $this->getRefreshTokenKey($values[$modelKey]);
189 1
            $entityStatus = (int)$this->db->executeCommand('EXISTS', [$newRefreshTokenKey]);
190 1
            if ($entityStatus === 1) {
191 1
                throw new DuplicateKeyException('Duplicate key "'.$newRefreshTokenKey.'"');
192
            }
193 1
        }
194
195 1
        $this->db->executeCommand('MULTI');
196
        try {
197 1
            if (array_key_exists($modelKey, $values) === true) {
198 1
                $oldId = $refreshToken->getOldKey();
199 1
                $oldRefreshTokenKey = $this->getRefreshTokenKey($oldId);
200
201 1
                $this->db->executeCommand('RENAMENX', [$oldRefreshTokenKey, $refreshTokenKey]);
202 1
                if ($userRefreshTokensKey !== null) {
203 1
                    $this->db->executeCommand('SREM', [$userRefreshTokensKey, $oldRefreshTokenKey]);
204 1
                    $this->db->executeCommand('SADD', [$userRefreshTokensKey, $refreshTokenKey]);
205 1
                }
206 1
                $this->db->executeCommand('SREM', [$clientRefreshTokensKey, $oldRefreshTokenKey]);
207 1
                $this->db->executeCommand('SADD', [$clientRefreshTokensKey, $refreshTokenKey]);
208 1
            }
209
210 1
            $redisUpdateParameters = [$refreshTokenKey];
211 1
            $redisDeleteParameters = [$refreshTokenKey];
212 1
            $this->setAttributesDefinitions($refreshToken->attributesDefinition());
213 1
            foreach ($values as $key => $value)
214
            {
215 1
                if ($value === null) {
216 1
                    $redisDeleteParameters[] = $key;
217 1
                } else {
218 1
                    $redisUpdateParameters[] = $key;
219 1
                    $redisUpdateParameters[] = $this->convertToDatabase($key, $value);
220
                }
221 1
            }
222 1
            if (count($redisDeleteParameters) > 1) {
223 1
                $this->db->executeCommand('HDEL', $redisDeleteParameters);
224 1
            }
225 1
            if (count($redisUpdateParameters) > 1) {
226 1
                $this->db->executeCommand('HMSET', $redisUpdateParameters);
227 1
            }
228
229 1
            $this->db->executeCommand('EXEC');
230 1
        } catch (DatabaseException $e) {
231
            // @codeCoverageIgnoreStart
232
            // we have a REDIS exception, we should not discard
233
            Yii::trace('Error while updating entity', __METHOD__);
234
            throw $e;
235
            // @codeCoverageIgnoreEnd
236
        }
237
238 1
        $changedAttributes = [];
239 1
        foreach ($values as $name => $value) {
240 1
            $oldAttributes = $refreshToken->getOldAttributes();
241 1
            $changedAttributes[$name] = isset($oldAttributes[$name]) ? $oldAttributes[$name] : null;
242 1
            $refreshToken->setOldAttribute($name, $value);
243 1
        }
244 1
        $refreshToken->afterSave(false, $changedAttributes);
245 1
        return true;
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 7
    public function findOne($key)
252
    {
253 7
        $record = null;
254 7
        $refreshTokenKey = $this->getRefreshTokenKey($key);
255 7
        $refreshTokenExists = (bool)$this->db->executeCommand('EXISTS', [$refreshTokenKey]);
256 7
        if ($refreshTokenExists === true) {
257 7
            $refreshTokenData = $this->db->executeCommand('HGETALL', [$refreshTokenKey]);
258 7
            $record = Yii::createObject('sweelix\oauth2\server\interfaces\RefreshTokenModelInterface');
259
            /** @var RefreshTokenModelInterface $record */
260 7
            $properties = $record->attributesDefinition();
261 7
            $this->setAttributesDefinitions($properties);
262 7
            $attributes = [];
263 7
            for ($i = 0; $i < count($refreshTokenData); $i += 2) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
264 7
                if (isset($properties[$refreshTokenData[$i]]) === true) {
265 7
                    $refreshTokenData[$i + 1] = $this->convertToModel($refreshTokenData[$i], $refreshTokenData[($i + 1)]);
266 7
                    $record->setAttribute($refreshTokenData[$i], $refreshTokenData[$i + 1]);
267 7
                    $attributes[$refreshTokenData[$i]] = $refreshTokenData[$i + 1];
268
                // @codeCoverageIgnoreStart
269
                } elseif ($record->canSetProperty($refreshTokenData[$i])) {
270
                    // TODO: find a way to test attribute population
271
                    $record->{$refreshTokenData[$i]} = $refreshTokenData[$i + 1];
272
                }
273
                // @codeCoverageIgnoreEnd
274 7
            }
275 7
            if (empty($attributes) === false) {
276 7
                $record->setOldAttributes($attributes);
277 7
            }
278 7
            $record->afterFind();
279 7
        }
280 7
        return $record;
281
    }
282
283
    /**
284
     * @inheritdoc
285
     */
286 2
    public function findAllByUserId($userId)
287
    {
288 2
        $userRefreshTokensKey = $this->getUserRefreshTokensKey($userId);
289 2
        $userRefreshTokens = $this->db->executeCommand('SMEMBERS', [$userRefreshTokensKey]);
290 2
        $refreshTokens = [];
291 2
        if ((is_array($userRefreshTokens) === true) && (count($userRefreshTokens) > 0)) {
292 1
            foreach($userRefreshTokens as $userRefreshTokenId) {
293 1
                $refreshTokens[] = $this->findOne($userRefreshTokenId);
294 1
            }
295 1
        }
296 2
        return $refreshTokens;
297
    }
298
299
    /**
300
     * @inheritdoc
301
     */
302 2
    public function findAllByClientId($clientId)
303
    {
304 2
        $clientRefreshTokensKey = $this->getClientRefreshTokensKey($clientId);
305 2
        $clientRefreshTokens = $this->db->executeCommand('SMEMBERS', [$clientRefreshTokensKey]);
306 2
        $refreshTokens = [];
307 2
        if ((is_array($clientRefreshTokens) === true) && (count($clientRefreshTokens) > 0)) {
308 1
            foreach($clientRefreshTokens as $clientRefreshTokenId) {
309 1
                $refreshTokens[] = $this->findOne($clientRefreshTokenId);
310 1
            }
311 1
        }
312 2
        return $refreshTokens;
313
    }
314
315
    /**
316
     * @inheritdoc
317
     */
318 2
    public function delete(RefreshTokenModelInterface $refreshToken)
319
    {
320 2
        $result = false;
321 2
        if ($refreshToken->beforeDelete()) {
322 2
            if (empty($refreshToken->userId) === false) {
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
323 2
                $userRefreshTokensKey = $this->getUserRefreshTokensKey($refreshToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
324 2
            } else {
325
                $userRefreshTokensKey = null;
326
            }
327 2
            $clientRefreshTokensKey = $this->getClientRefreshTokensKey($refreshToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...reshTokenModelInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
328
329 2
            $this->db->executeCommand('MULTI');
330 2
            $id = $refreshToken->getOldKey();
331 2
            $refreshTokenKey = $this->getRefreshTokenKey($id);
332
333 2
            $this->db->executeCommand('DEL', [$refreshTokenKey]);
334 2
            if ($userRefreshTokensKey !== null) {
335 2
                $this->db->executeCommand('SREM', [$userRefreshTokensKey, $id]);
336 2
            }
337 2
            $this->db->executeCommand('SREM', [$clientRefreshTokensKey, $id]);
338
            //TODO: check results to return correct information
339 2
            $queryResult = $this->db->executeCommand('EXEC');
0 ignored issues
show
Unused Code introduced by
$queryResult is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
340 2
            $refreshToken->setIsNewRecord(true);
341 2
            $refreshToken->afterDelete();
342 2
            $result = true;
343 2
        }
344 2
        return $result;
345
    }
346
347
}
348