ClientService::hasUser()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 1
cts 1
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * ClientService.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.2.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\ClientModelInterface;
20
use sweelix\oauth2\server\interfaces\ClientServiceInterface;
21
use yii\db\Exception as DatabaseException;
22
use Yii;
23
24
/**
25
 * This is the client service for redis
26
 *  database structure
27
 *    * oauth2:clients:<cid> : hash (Client)
28
 *    * oauth2:clients:<cid>:users : set
29
 *    * oauth2:users:<uid>:clients : set
30
 *
31
 * @author Philippe Gaultier <[email protected]>
32
 * @copyright 2010-2017 Philippe Gaultier
33
 * @license http://www.sweelix.net/license license
34
 * @version 1.2.0
35
 * @link http://www.sweelix.net
36
 * @package sweelix\oauth2\server\services\redis
37
 * @since 1.0.0
38
 */
39
class ClientService extends BaseService implements ClientServiceInterface
40
{
41
    /**
42
     * @var string user namespace (collection for clients)
43
     */
44
    public $userNamespace = '';
45
46
    /**
47
     * @param string $cid client ID
48
     * @return string client Key
49
     * @since 1.0.0
50
     */
51
    public function getClientKey($cid)
52 25
    {
53
        return $this->namespace . ':' . $cid;
54 25
    }
55
56
    /**
57
     * @param string $cid client ID
58
     * @return string clientUsers Key
59
     * @since 1.0.0
60
     */
61
    public function getClientUsersListKey($cid)
62 6
    {
63
        return $this->namespace . ':' . $cid . ':users';
64 6
    }
65
66
    /**
67
     * @param string $uid user ID
68
     * @return string user clients collection Key
69
     * @since XXX
70
     */
71
    public function getUserClientsListKey($uid)
72 5
    {
73
        return $this->userNamespace . ':' . $uid . ':clients';
74 5
    }
75
76
    /**
77
     * @return string key of all clients list
78
     */
79
    public function getClientListKey()
80 21
    {
81
        return $this->namespace . ':keys';
82 21
    }
83 21
84 21
    /**
85 2
     * @inheritdoc
86
     */
87 21
    public function save(ClientModelInterface $client, $attributes)
88
    {
89
        if ($client->getIsNewRecord()) {
90
            $result = $this->insert($client, $attributes);
91
        } else {
92
            $result = $this->update($client, $attributes);
93
        }
94
        return $result;
95
    }
96
97
    /**
98
     * Save Client
99
     * @param ClientModelInterface $client
100 21
     * @param null|array $attributes attributes to save
101
     * @return bool
102 21
     * @throws DatabaseException
103 21
     * @throws DuplicateIndexException
104
     * @throws DuplicateKeyException
105
     * @since 1.0.0
106 21
     */
107
    protected function insert(ClientModelInterface $client, $attributes)
108 21
    {
109 21
        $result = false;
110 1
        if (!$client->beforeSave(true)) {
111
            return $result;
112
        }
113 21
        $clientKey = $this->getClientKey($client->getKey());
114 21
        $clientListKey = $this->getClientListKey();
115 21
        //check if record exists
116 21
        $entityStatus = (int)$this->db->executeCommand('EXISTS', [$clientKey]);
117
        if ($entityStatus === 1) {
118 21
            throw new DuplicateKeyException('Duplicate key "' . $clientKey . '"');
119 21
        }
120 21
121 21
        $values = $client->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 107 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...
122 21
        $redisParameters = [$clientKey];
123
        $this->setAttributesDefinitions($client->attributesDefinition());
124 21
        foreach ($values as $key => $value) {
125 21
            if ($value !== null) {
126
                $redisParameters[] = $key;
127 21
                $redisParameters[] = $this->convertToDatabase($key, $value);
128 21
            }
129 21
        }
130
        //TODO: use EXEC/MULTI to avoid errors
131
        $transaction = $this->db->executeCommand('MULTI');
132
        if ($transaction === true) {
133
            try {
134
                $this->db->executeCommand('HMSET', $redisParameters);
135
                $this->db->executeCommand('SADD', [$clientListKey, $client->getKey()]);
136 21
                $this->db->executeCommand('EXEC');
137 21
            } catch (DatabaseException $e) {
138 21
                // @codeCoverageIgnoreStart
139 21
                // we have a REDIS exception, we should not discard
140 21
                Yii::debug('Error while inserting entity', __METHOD__);
141 21
                throw $e;
142
                // @codeCoverageIgnoreEnd
143
            }
144
        }
145
        $changedAttributes = array_fill_keys(array_keys($values), null);
146
        $client->setOldAttributes($values);
147
        $client->afterSave(true, $changedAttributes);
148
        $result = true;
149
        return $result;
150
    }
151
152
153
    /**
154 2
     * Update Client
155
     * @param ClientModelInterface $client
156 2
     * @param null|array $attributes attributes to save
157
     * @return bool
158
     * @throws DatabaseException
159
     * @throws DuplicateIndexException
160 2
     * @throws DuplicateKeyException
161 2
     */
162 2
    protected function update(ClientModelInterface $client, $attributes)
163 2
    {
164
        if (!$client->beforeSave(false)) {
165
            return false;
166 2
        }
167 1
168 1
        $values = $client->getDirtyAttributes($attributes);
0 ignored issues
show
Bug introduced by
It seems like $attributes defined by parameter $attributes on line 162 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...
169 1
        $modelKey = $client->key();
170 1
        $clientId = isset($values[$modelKey]) ? $values[$modelKey] : $client->getKey();
171
        $clientKey = $this->getClientKey($clientId);
172 1
        $clientListKey = $this->getClientListKey();
173
174 2
        if (isset($values[$modelKey]) === true) {
175
            $newClientKey = $this->getClientKey($values[$modelKey]);
176 2
            $entityStatus = (int)$this->db->executeCommand('EXISTS', [$newClientKey]);
177 1
            if ($entityStatus === 1) {
178 1
                throw new DuplicateKeyException('Duplicate key "' . $newClientKey . '"');
179
            }
180 1
        }
181 1
182
        $this->db->executeCommand('MULTI');
183 2
        try {
184 2
            $reAddKeyInList = false;
185 2
            if (array_key_exists($modelKey, $values) === true) {
186 2
                $oldId = $client->getOldKey();
187
                $oldClientKey = $this->getClientKey($oldId);
188 2
189 1
                $this->db->executeCommand('RENAMENX', [$oldClientKey, $clientKey]);
190 1
                $this->db->executeCommand('SREM', [$clientListKey, $oldClientKey]);
191 2
                $reAddKeyInList = true;
192 2
            }
193
194 2
            $redisUpdateParameters = [$clientKey];
195 2
            $redisDeleteParameters = [$clientKey];
196 1
            $this->setAttributesDefinitions($client->attributesDefinition());
197 1
            foreach ($values as $key => $value) {
198 2
                if ($value === null) {
199 2
                    $redisDeleteParameters[] = $key;
200 2
                } else {
201
                    $redisUpdateParameters[] = $key;
202 2
                    $redisUpdateParameters[] = $this->convertToDatabase($key, $value);
203 2
                }
204
            }
205
            if (count($redisDeleteParameters) > 1) {
206
                $this->db->executeCommand('HDEL', $redisDeleteParameters);
207
            }
208
            if (count($redisUpdateParameters) > 1) {
209
                $this->db->executeCommand('HMSET', $redisUpdateParameters);
210
            }
211 2
212 2
            if ($reAddKeyInList === true) {
213 2
                $this->db->executeCommand('SADD', [$clientListKey, $clientId]);
214 2
            }
215 2
216 2
            $this->db->executeCommand('EXEC');
217 2
        } catch (DatabaseException $e) {
218 2
            // @codeCoverageIgnoreStart
219
            // we have a REDIS exception, we should not discard
220
            Yii::debug('Error while updating entity', __METHOD__);
221
            throw $e;
222
            // @codeCoverageIgnoreEnd
223
        }
224 23
225
        $changedAttributes = [];
226 23
        foreach ($values as $name => $value) {
227 23
            $oldAttributes = $client->getOldAttributes();
228 23
            $changedAttributes[$name] = isset($oldAttributes[$name]) ? $oldAttributes[$name] : null;
229 23
            $client->setOldAttribute($name, $value);
230 18
        }
231 18
        $client->afterSave(false, $changedAttributes);
232
        return true;
233 18
    }
234 18
235 18
    /**
236 18
     * @inheritdoc
237 18
     */
238 18
    public function findOne($key)
239 18
    {
240 18
        $record = null;
241
        $clientKey = $this->getClientKey($key);
242
        $clientExists = (bool)$this->db->executeCommand('EXISTS', [$clientKey]);
243
        if ($clientExists === true) {
244
            $clientData = $this->db->executeCommand('HGETALL', [$clientKey]);
245
            $record = Yii::createObject('sweelix\oauth2\server\interfaces\ClientModelInterface');
246
            /** @var ClientModelInterface $record */
247 18
            $properties = $record->attributesDefinition();
248 18
            $this->setAttributesDefinitions($properties);
249 18
            $attributes = [];
250 18
            for ($i = 0; $i < count($clientData); $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...
251 18
                if (isset($properties[$clientData[$i]]) === true) {
252 18
                    $clientData[$i + 1] = $this->convertToModel($clientData[$i], $clientData[($i + 1)]);
253 23
                    $record->setAttribute($clientData[$i], $clientData[$i + 1]);
254
                    $attributes[$clientData[$i]] = $clientData[$i + 1];
255
                    // @codeCoverageIgnoreStart
256
                } elseif ($record->canSetProperty($clientData[$i])) {
257
                    // TODO: find a way to test attribute population
258
                    $record->{$clientData[$i]} = $clientData[$i + 1];
259 1
                }
260
                // @codeCoverageIgnoreEnd
261 1
            }
262 1
            if (empty($attributes) === false) {
263 1
                $record->setOldAttributes($attributes);
264 1
            }
265 1
            $record->afterFind();
266
        }
267
        return $record;
268
    }
269 1
270 1
    /**
271 1
     * @inheritdoc
272
     */
273 1
    public function delete(ClientModelInterface $client)
274 1
    {
275 1
        $result = false;
276
        if ($client->beforeDelete()) {
277 1
            $id = $client->getOldKey();
278
            $clientKey = $this->getClientKey($id);
279 1
            $clientListKey = $this->getClientListKey();
280
            $clientUsersListKey = $this->getClientUsersListKey($id);
281 1
282
            // before cleaning the client, drop all access tokens, refresh tokens, auth codes and jtis
283
            $token = Yii::createObject('sweelix\oauth2\server\interfaces\AccessTokenModelInterface');
284 1
            $tokenClass = get_class($token);
285 1
            $tokenClass::deleteAllByClientId($id);
286 1
287
            $token = Yii::createObject('sweelix\oauth2\server\interfaces\RefreshTokenModelInterface');
288 1
            $tokenClass = get_class($token);
289 1
            $tokenClass::deleteAllByClientId($id);
290 1
291 1
            $token = Yii::createObject('sweelix\oauth2\server\interfaces\JtiModelInterface');
292 1
            $tokenClass = get_class($token);
293 1
            $tokenClass::deleteAllByClientId($id);
294
295
            $usersList = $this->db->executeCommand('SMEMBERS', [$clientUsersListKey]);
296
297
            $this->db->executeCommand('MULTI');
298
            // remove client from all userClient sets
299 5
            foreach ($usersList as $user) {
300
                $userClientKey = $this->getUserClientsListKey($user);
301 5
                $this->db->executeCommand('SREM', [$userClientKey, $id]);
302 5
            }
303 5
            $this->db->executeCommand('DEL', [$clientKey]);
304
            $this->db->executeCommand('DEL', [$clientUsersListKey]);
305
            $this->db->executeCommand('SREM', [$clientListKey, $id]);
306
            //TODO: check results to return correct information
307
            $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...
308
            $client->setIsNewRecord(true);
309 3
            $client->afterDelete();
310
            $result = true;
311 3
        }
312 3
        return $result;
313 3
    }
314 3
315 3
    /**
316 3
     * @inheritdoc
317 3
     */
318
    public function hasUser(ClientModelInterface $client, $userId)
319 3
    {
320
        $key = $client->getKey();
321
        $clientUsersListKey = $this->getClientUsersListKey($key);
322
        return (bool)$this->db->executeCommand('SISMEMBER', [$clientUsersListKey, $userId]);
323
    }
324
325 4
    /**
326
     * @inheritdoc
327 4
     */
328 4
    public function addUser(ClientModelInterface $client, $userId)
329 4
    {
330 4
        $key = $client->getKey();
331 4
        $clientUsersListKey = $this->getClientUsersListKey($key);
332 4
        $userClientsListKey = $this->getUserClientsListKey($userId);
333 4
        $this->db->executeCommand('MULTI');
334
        $this->db->executeCommand('SADD', [$clientUsersListKey, $userId]);
335 4
        $this->db->executeCommand('SADD', [$userClientsListKey, $key]);
336
        $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...
337
        //TODO: check if we should send back false or not
338
        return true;
339
    }
340
341 1
    /**
342
     * @inheritdoc
343 1
     */
344 1
    public function removeUser(ClientModelInterface $client, $userId)
345 1
    {
346 1
        $key = $client->getKey();
347 1
        $clientUsersListKey = $this->getClientUsersListKey($key);
348 1
        $userClientsListKey = $this->getUserClientsListKey($userId);
349 1
        $this->db->executeCommand('MULTI');
350 1
        $this->db->executeCommand('SREM', [$clientUsersListKey, $userId]);
351 1
        $this->db->executeCommand('SREM', [$userClientsListKey, $key]);
352 1
        $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...
353
        //TODO: check if we should send back false or not
354
        return true;
355
    }
356
357
    /**
358
     * @inheritdoc
359
     */
360
    public function findAllByUserId($userId)
361
    {
362
        $userClientsListKey = $this->getUserClientsListKey($userId);
363
        $clientsList = $this->db->executeCommand('SMEMBERS', [$userClientsListKey]);
364
        $clients = [];
365
        foreach ($clientsList as $clientId) {
366
            $result = $this->findOne($clientId);
367
            if ($result instanceof ClientModelInterface) {
368
                $clients[] = $result;
369
            }
370
        }
371
        return $clients;
372
    }
373
374
    /**
375
     * @inheritdoc
376
     */
377
    public function findAll()
378
    {
379
        $clientListKey = $this->getClientListKey();
380
        $clientList = $this->db->executeCommand('SMEMBERS', [$clientListKey]);
381
        $clients = [];
382
        foreach ($clientList as $clientId) {
383
            $result = $this->findOne($clientId);
384
            if ($result instanceof ClientModelInterface) {
385
                $clients[] = $result;
386
            }
387
        }
388
        return $clients;
389
    }
390
}
391