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

AccessTokenService::getAccessTokenKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * AccessTokenService.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\AccessTokenModelInterface;
20
use sweelix\oauth2\server\interfaces\AccessTokenServiceInterface;
21
use yii\db\Exception as DatabaseException;
22
use Yii;
23
24
/**
25
 * This is the access token service for redis
26
 *  database structure
27
 *    * oauth2:accessTokens:<aid> : hash (AccessToken)
28
 *    * oauth2:users:<uid>:accessTokens : set (AccessToken for user)
29
 *
30
 * @author Philippe Gaultier <[email protected]>
31
 * @copyright 2010-2017 Philippe Gaultier
32
 * @license http://www.sweelix.net/license license
33
 * @version 1.1.0
34
 * @link http://www.sweelix.net
35
 * @package sweelix\oauth2\server\services\redis
36
 * @since 1.0.0
37
 */
38
class AccessTokenService extends BaseService implements AccessTokenServiceInterface
39
{
40
41
    /**
42
     * @var string user namespace (collection for accesstokens)
43
     */
44
    public $userNamespace = '';
45
46
    /**
47
     * @var string client namespace (collection for accesstokens)
48
     */
49
    public $clientNamespace = '';
50
51
    /**
52
     * @param string $aid access token ID
53
     * @return string access token Key
54
     * @since 1.0.0
55
     */
56 9
    protected function getAccessTokenKey($aid)
57
    {
58 9
        return $this->namespace . ':' . $aid;
59
    }
60
61
    /**
62
     * @param string $uid user ID
63
     * @return string user access tokens collection Key
64
     * @since XXX
65
     */
66 10
    protected function getUserAccessTokensKey($uid)
67
    {
68 10
        return $this->userNamespace . ':' . $uid . ':accessTokens';
69
    }
70
71
    /**
72
     * @param string $cid client ID
73
     * @return string client access tokens collection Key
74
     * @since XXX
75
     */
76 10
    protected function getClientAccessTokensKey($cid)
77
    {
78 10
        return $this->clientNamespace . ':' . $cid . ':accessTokens';
79
    }
80
81
    /**
82
     * @inheritdoc
83
     */
84 9
    public function save(AccessTokenModelInterface $accessToken, $attributes)
85
    {
86 9
        if ($accessToken->getIsNewRecord()) {
87 9
            $result = $this->insert($accessToken, $attributes);
88 9
        } else {
89 1
            $result = $this->update($accessToken, $attributes);
90
        }
91 9
        return $result;
92
    }
93
94
    /**
95
     * Save Access Token
96
     * @param AccessTokenModelInterface $accessToken
97
     * @param null|array $attributes attributes to save
98
     * @return bool
99
     * @throws DatabaseException
100
     * @throws DuplicateIndexException
101
     * @throws DuplicateKeyException
102
     * @since 1.0.0
103
     */
104 9
    protected function insert(AccessTokenModelInterface $accessToken, $attributes)
105
    {
106 9
        $result = false;
107 9
        if (!$accessToken->beforeSave(true)) {
108
            return $result;
109
        }
110 9
        $accessTokenId = $accessToken->getKey();
111 9
        $accessTokenKey = $this->getAccessTokenKey($accessTokenId);
112 9
        if (empty($accessToken->userId) === false) {
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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 9
            $userAccessTokensKey = $this->getUserAccessTokensKey($accessToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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...
114 9
        } else {
115
            $userAccessTokensKey = null;
116
        }
117 9
        $clientAccessTokensKey = $this->getClientAccessTokensKey($accessToken->clientId);
0 ignored issues
show
Bug introduced by
Accessing clientId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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...
118
119
        //check if record exists
120 9
        $entityStatus = (int)$this->db->executeCommand('EXISTS', [$accessTokenKey]);
121 9
        if ($entityStatus === 1) {
122 1
            throw new DuplicateKeyException('Duplicate key "'.$accessTokenKey.'"');
123
        }
124
125 9
        $values = $accessToken->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 104 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...
126 9
        $redisParameters = [$accessTokenKey];
127 9
        $this->setAttributesDefinitions($accessToken->attributesDefinition());
128 9
        foreach ($values as $key => $value)
129
        {
130 9
            if ($value !== null) {
131 9
                $redisParameters[] = $key;
132 9
                $redisParameters[] = $this->convertToDatabase($key, $value);
133 9
            }
134 9
        }
135
        //TODO: use EXEC/MULTI to avoid errors
136 9
        $transaction = $this->db->executeCommand('MULTI');
137 9
        if ($transaction === true) {
138
            try {
139 9
                $this->db->executeCommand('HMSET', $redisParameters);
140 9
                if ($userAccessTokensKey !== null) {
141 9
                    $this->db->executeCommand('SADD', [$userAccessTokensKey, $accessTokenId]);
142 9
                }
143 9
                $this->db->executeCommand('SADD', [$clientAccessTokensKey, $accessTokenId]);
144 9
                $this->db->executeCommand('EXEC');
145 9
            } catch (DatabaseException $e) {
146
                // @codeCoverageIgnoreStart
147
                // we have a REDIS exception, we should not discard
148
                Yii::trace('Error while inserting entity', __METHOD__);
149
                throw $e;
150
                // @codeCoverageIgnoreEnd
151
            }
152 9
        }
153 9
        $changedAttributes = array_fill_keys(array_keys($values), null);
154 9
        $accessToken->setOldAttributes($values);
155 9
        $accessToken->afterSave(true, $changedAttributes);
156 9
        $result = true;
157 9
        return $result;
158
    }
159
160
161
    /**
162
     * Update Access Token
163
     * @param AccessTokenModelInterface $accessToken
164
     * @param null|array $attributes attributes to save
165
     * @return bool
166
     * @throws DatabaseException
167
     * @throws DuplicateIndexException
168
     * @throws DuplicateKeyException
169
     */
170 1
    protected function update(AccessTokenModelInterface $accessToken, $attributes)
171
    {
172 1
        if (!$accessToken->beforeSave(false)) {
173
            return false;
174
        }
175
176 1
        $values = $accessToken->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 170 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...
177 1
        $modelKey = $accessToken->key();
178 1
        $accessTokenId = isset($values[$modelKey]) ? $values[$modelKey] : $accessToken->getKey();
179 1
        $accessTokenKey = $this->getAccessTokenKey($accessTokenId);
180
181 1
        if (empty($accessToken->userId) === false) {
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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
            $userAccessTokensKey = $this->getUserAccessTokensKey($accessToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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...
183 1
        } else {
184
            $userAccessTokensKey = null;
185
        }
186 1
        $clientAccessTokensKey = $this->getClientAccessTokensKey($accessToken->clientId);
0 ignored issues
show
Bug introduced by
Accessing clientId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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...
187
188 1
        if (isset($values[$modelKey]) === true) {
189 1
            $newAccessTokenKey = $this->getAccessTokenKey($values[$modelKey]);
190 1
            $entityStatus = (int)$this->db->executeCommand('EXISTS', [$newAccessTokenKey]);
191 1
            if ($entityStatus === 1) {
192 1
                throw new DuplicateKeyException('Duplicate key "'.$newAccessTokenKey.'"');
193
            }
194 1
        }
195
196 1
        $this->db->executeCommand('MULTI');
197
        try {
198 1
            if (array_key_exists($modelKey, $values) === true) {
199 1
                $oldId = $accessToken->getOldKey();
200 1
                $oldAccessTokenKey = $this->getAccessTokenKey($oldId);
201 1
                $this->db->executeCommand('RENAMENX', [$oldAccessTokenKey, $accessTokenKey]);
202 1
                if ($userAccessTokensKey !== null) {
203 1
                    $this->db->executeCommand('SREM', [$userAccessTokensKey, $oldAccessTokenKey]);
204 1
                    $this->db->executeCommand('SADD', [$userAccessTokensKey, $accessTokenKey]);
205 1
                }
206 1
                $this->db->executeCommand('SREM', [$clientAccessTokensKey, $oldAccessTokenKey]);
207 1
                $this->db->executeCommand('SADD', [$clientAccessTokensKey, $accessTokenKey]);
208 1
            }
209
210 1
            $redisUpdateParameters = [$accessTokenKey];
211 1
            $redisDeleteParameters = [$accessTokenKey];
212 1
            $this->setAttributesDefinitions($accessToken->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 = $accessToken->getOldAttributes();
241 1
            $changedAttributes[$name] = isset($oldAttributes[$name]) ? $oldAttributes[$name] : null;
242 1
            $accessToken->setOldAttribute($name, $value);
243 1
        }
244 1
        $accessToken->afterSave(false, $changedAttributes);
245 1
        return true;
246
    }
247
248
    /**
249
     * @inheritdoc
250
     */
251 9
    public function findOne($key)
252
    {
253 9
        $record = null;
254 9
        $accessTokenKey = $this->getAccessTokenKey($key);
255 9
        $accessTokenExists = (bool)$this->db->executeCommand('EXISTS', [$accessTokenKey]);
256 9
        if ($accessTokenExists === true) {
257 9
            $accessTokenData = $this->db->executeCommand('HGETALL', [$accessTokenKey]);
258 9
            $record = Yii::createObject('sweelix\oauth2\server\interfaces\AccessTokenModelInterface');
259
            /** @var AccessTokenModelInterface $record */
260 9
            $properties = $record->attributesDefinition();
261 9
            $this->setAttributesDefinitions($properties);
262 9
            $attributes = [];
263 9
            for ($i = 0; $i < count($accessTokenData); $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 9
                if (isset($properties[$accessTokenData[$i]]) === true) {
265 9
                    $accessTokenData[$i + 1] = $this->convertToModel($accessTokenData[$i], $accessTokenData[($i + 1)]);
266 9
                    $record->setAttribute($accessTokenData[$i], $accessTokenData[$i + 1]);
267 9
                    $attributes[$accessTokenData[$i]] = $accessTokenData[$i + 1];
268
                // @codeCoverageIgnoreStart
269
                } elseif ($record->canSetProperty($accessTokenData[$i])) {
270
                    // TODO: find a way to test attribute population
271
                    $record->{$accessTokenData[$i]} = $accessTokenData[$i + 1];
272
                }
273
                // @codeCoverageIgnoreEnd
274 9
            }
275 9
            if (empty($attributes) === false) {
276 9
                $record->setOldAttributes($attributes);
277 9
            }
278 9
            $record->afterFind();
279 9
        }
280 9
        return $record;
281
    }
282
283
    /**
284
     * @inheritdoc
285
     */
286 2
    public function findAllByUserId($userId)
287
    {
288 2
        $userAccessTokensKey = $this->getUserAccessTokensKey($userId);
289 2
        $userAccessTokens = $this->db->executeCommand('SMEMBERS', [$userAccessTokensKey]);
290 2
        $accessTokens = [];
291 2
        if ((is_array($userAccessTokens) === true) && (count($userAccessTokens) > 0)) {
292 1
            foreach($userAccessTokens as $userAccessTokenId) {
293 1
                $accessTokens[] = $this->findOne($userAccessTokenId);
294 1
            }
295 1
        }
296 2
        return $accessTokens;
297
    }
298
299
    /**
300
     * @inheritdoc
301
     */
302 2
    public function findAllByClientId($clientId)
303
    {
304 2
        $clientAccessTokensKey = $this->getClientAccessTokensKey($clientId);
305 2
        $clientAccessTokens = $this->db->executeCommand('SMEMBERS', [$clientAccessTokensKey]);
306 2
        $accessTokens = [];
307 2
        if ((is_array($clientAccessTokens) === true) && (count($clientAccessTokens) > 0)) {
308 1
            foreach($clientAccessTokens as $clientAccessTokenId) {
309 1
                $accessTokens[] = $this->findOne($clientAccessTokenId);
310 1
            }
311 1
        }
312 2
        return $accessTokens;
313
    }
314
315
    /**
316
     * @inheritdoc
317
     */
318 2
    public function delete(AccessTokenModelInterface $accessToken)
319
    {
320 2
        $result = false;
321 2
        if ($accessToken->beforeDelete()) {
322 2
            if (empty($accessToken->userId) === false) {
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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
                $userAccessTokensKey = $this->getUserAccessTokensKey($accessToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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
                $userAccessTokensKey = null;
326
            }
327 2
            $clientAccessTokensKey = $this->getClientAccessTokensKey($accessToken->userId);
0 ignored issues
show
Bug introduced by
Accessing userId on the interface sweelix\oauth2\server\in...cessTokenModelInterface 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 = $accessToken->getOldKey();
331 2
            $accessTokenKey = $this->getAccessTokenKey($id);
332
333 2
            $this->db->executeCommand('DEL', [$accessTokenKey]);
334 2
            if ($userAccessTokensKey !== null) {
335 2
                $this->db->executeCommand('SREM', [$userAccessTokensKey, $id]);
336 2
            }
337 2
            $this->db->executeCommand('SREM', [$clientAccessTokensKey, $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
            $accessToken->setIsNewRecord(true);
341 2
            $accessToken->afterDelete();
342 2
            $result = true;
343 2
        }
344 2
        return $result;
345
    }
346
347
}
348