Completed
Push — devel ( 3d6262...5f7ea5 )
by Philippe
01:49
created

Manager   F

Complexity

Total Complexity 156

Size/Duplication

Total Lines 1078
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Test Coverage

Coverage 94.42%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
wmc 156
c 5
b 1
f 0
lcom 1
cbo 14
dl 0
loc 1078
ccs 525
cts 556
cp 0.9442
rs 1.8513

55 Methods

Rating   Name   Duplication   Size   Complexity  
A getUserAssignmentsKey() 0 4 1
A getRoleAssignmentsKey() 0 4 1
A getRuleKey() 0 4 1
A getTypeItemsKey() 0 4 1
A getItemKey() 0 4 1
A getRuleItemsKey() 0 4 1
A getItemChildrenKey() 0 4 1
A getItemParentsKey() 0 4 1
A getItemMappingKey() 0 4 1
A getRuleMappingKey() 0 4 1
A getItemMappingGuidKey() 0 4 1
A getRuleMappingGuidKey() 0 4 1
A init() 0 5 1
A getItem() 0 10 2
B getItemByGuid() 0 24 6
A getItems() 0 10 2
B addItem() 0 35 6
D buildRedisItemInsert() 0 33 9
F updateItem() 0 80 17
B removeItem() 0 31 3
B addRule() 0 31 5
A updateRule() 0 23 2
A removeRule() 0 22 2
A getRules() 0 10 2
A getRule() 0 13 4
A getRuleGuid() 0 6 1
B addChild() 0 20 5
A removeChild() 0 10 1
A removeChildren() 0 15 2
A getChildren() 0 6 1
A getChildrenByGuid() 0 11 3
A hasChild() 0 8 1
A revokeAll() 0 17 4
A revoke() 0 13 2
A removeAllRoles() 0 4 1
A removeAllRules() 0 7 2
A removeAllPermissions() 0 4 1
A removeAll() 0 14 3
B removeAllAssignments() 0 24 4
A removeAllItems() 0 7 2
B getRolesByUser() 0 19 6
A getUserIdsByRole() 0 13 3
A assign() 0 15 1
B getPermissionsByUser() 0 23 6
A getPermissionsByRole() 0 11 2
A getChildrenRecursiveGuid() 0 11 2
A detectLoop() 0 12 4
A getParents() 0 5 1
A getParentsGuid() 0 10 2
B getAssignments() 0 26 4
A getAssignment() 0 16 3
A checkAccess() 0 5 1
A canAddChild() 0 4 1
B checkAccessRecursive() 0 20 8
B populateItem() 0 17 6

How to fix   Complexity   

Complex Class

Complex classes like Manager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Manager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Manager.php
4
 *
5
 * PHP version 5.6+
6
 *
7
 * @author Philippe Gaultier <[email protected]>
8
 * @copyright 2010-2016 Philippe Gaultier
9
 * @license http://www.sweelix.net/license license
10
 * @version XXX
11
 * @link http://www.sweelix.net
12
 * @package sweelix\rbac\redis
13
 */
14
15
namespace sweelix\rbac\redis;
16
17
use sweelix\guid\Guid;
18
use Yii;
19
use yii\base\InvalidCallException;
20
use yii\base\InvalidParamException;
21
use yii\di\Instance;
22
use yii\rbac\Assignment;
23
use yii\rbac\BaseManager;
24
use yii\rbac\Permission;
25
use yii\rbac\Role;
26
use yii\rbac\Item;
27
use yii\rbac\Rule;
28
use yii\redis\Connection;
29
30
/**
31
 * REDIS Manager represents an authorization manager that stores
32
 * authorization information in REDIS database
33
 *
34
 * @author Philippe Gaultier <[email protected]>
35
 * @copyright 2010-2016 Philippe Gaultier
36
 * @license http://www.sweelix.net/license license
37
 * @version XXX
38
 * @link http://www.sweelix.net
39
 * @package application\controllers
40
 * @since XXX
41
 */
42
class Manager extends BaseManager
43
{
44
    /**
45
     * @var Connection|array|string the Redis DB connection object or the application component ID of the DB connection.
46
     */
47
    public $db = 'redis';
48
49
    /**
50
     * @var string
51
     */
52
    public $globalMatchKey = 'auth:*';
53
54
    /**
55
     * @var string
56
     */
57
    public $userAssignmentsKey = 'auth:users:{id}:assignments';
58
59
    /**
60
     * @var string
61
     */
62
    public $roleAssignmentsKey = 'auth:roles:{id}:assignments';
63
64
    /**
65
     * @var string
66
     */
67
    public $ruleKey = 'auth:rules:{id}';
68
69
    /**
70
     * @var string
71
     */
72
    public $typeItemsKey = 'auth:types:{id}:items';
73
74
    /**
75
     * @var string
76
     */
77
    public $itemKey = 'auth:items:{id}';
78
79
    /**
80
     * @var string
81
     */
82
    public $ruleItemsKey = 'auth:rules:{id}:items';
83
84
    /**
85
     * @var string
86
     */
87
    public $itemChildrenKey = 'auth:items:{id}:children';
88
89
    /**
90
     * @var string
91
     */
92
    public $itemParentsKey = 'auth:items:{id}:parents';
93
94
    /**
95
     * @var string
96
     */
97
    public $itemMappings = 'auth:mappings:items';
98
99
    /**
100
     * @var string
101
     */
102
    public $itemMappingsGuid = 'auth:mappings:itemsguid';
103
104
    /**
105
     * @var string
106
     */
107
    public $ruleMappings = 'auth:mappings:rules';
108
109
    /**
110
     * @var string
111
     */
112
    public $ruleMappingsGuid = 'auth:mappings:rulesguid';
113
114
115
    /**
116
     * @param string|integer $userId user id
117
     * @return string the user assignments key
118
     * @since XXX
119
     */
120 19
    public function getUserAssignmentsKey($userId)
121
    {
122 19
        return str_replace('{id}', $userId, $this->userAssignmentsKey);
123
    }
124
125
    /**
126
     * @param string $roleGuid role guid
127
     * @return string the rule assignments key
128
     * @since XXX
129
     */
130 19
    public function getRoleAssignmentsKey($roleGuid)
131
    {
132 19
        return str_replace('{id}', $roleGuid, $this->roleAssignmentsKey);
133
    }
134
135
    /**
136
     * @param string $ruleGuid rule guid
137
     * @return string the rule key
138
     * @since XXX
139
     */
140 21
    public function getRuleKey($ruleGuid)
141
    {
142 21
        return str_replace('{id}', $ruleGuid, $this->ruleKey);
143
    }
144
145
    /**
146
     * @param integer $typeId type id
147
     * @return string the type id key
148
     * @since XXX
149
     */
150 28
    public function getTypeItemsKey($typeId)
151
    {
152 28
        return str_replace('{id}', $typeId, $this->typeItemsKey);
153
    }
154
155
    /**
156
     * @param string $itemGuid item guid
157
     * @return string
158
     * @since XXX
159
     */
160 28
    public function getItemKey($itemGuid)
161
    {
162 28
        return str_replace('{id}', $itemGuid, $this->itemKey);
163
    }
164
165
    /**
166
     * @param string $ruleGuid rule guid
167
     * @return string the rule items key
168
     * @since XXX
169
     */
170 4
    public function getRuleItemsKey($ruleGuid)
171
    {
172 4
        return str_replace('{id}', $ruleGuid, $this->ruleItemsKey);
173
    }
174
175
    /**
176
     * @param string $itemGuid item guid
177
     * @return string the item children key
178
     * @since XXX
179
     */
180 23
    public function getItemChildrenKey($itemGuid)
181
    {
182 23
        return str_replace('{id}', $itemGuid, $this->itemChildrenKey);
183
    }
184
185
    /**
186
     * @param string $itemGuid item guid
187
     * @return string the item parents key
188
     * @since XXX
189
     */
190 23
    public function getItemParentsKey($itemGuid)
191
    {
192 23
        return str_replace('{id}', $itemGuid, $this->itemParentsKey);
193
    }
194
195
    /**
196
     * @return string the item mapping key
197
     * @since XXX
198
     */
199 28
    public function getItemMappingKey()
200
    {
201 28
        return $this->itemMappings;
202
    }
203
204
    /**
205
     * @return string the rule mapping key
206
     * @since XXX
207
     */
208 21
    public function getRuleMappingKey()
209
    {
210 21
        return $this->ruleMappings;
211
    }
212
213
    /**
214
     * @return string the item mapping key
215
     * @since XXX
216
     */
217 28
    public function getItemMappingGuidKey()
218
    {
219 28
        return $this->itemMappingsGuid;
220
    }
221
222
    /**
223
     * @return string the rule mapping key
224
     * @since XXX
225
     */
226 21
    public function getRuleMappingGuidKey()
227
    {
228 21
        return $this->ruleMappingsGuid;
229
    }
230
231
232
    /**
233
     * @inheritdoc
234
     */
235 30
    public function init()
236
    {
237 30
        parent::init();
238 30
        $this->db = Instance::ensure($this->db, Connection::className());
239 30
    }
240
241
    /**
242
     * @inheritdoc
243
     */
244 10
    protected function getItem($name)
245
    {
246 10
        $item = null;
247 10
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $name]);
248 10
        if ($guid !== null)
249 10
        {
250 10
            $item = $this->getItemByGuid($guid, $name);
251 10
        }
252 10
        return $item;
253
    }
254
255
    /**
256
     * @param string $guid item guid
257
     * @param string $name name if known to avoid additional call
258
     * @return mixed
259
     * @since XXX
260
     */
261 24
    protected function getItemByGuid($guid, $name = null)
262
    {
263 24
        if ($name === null) {
264 23
            $name = $this->db->executeCommand('HGET', [$this->getItemMappingGuidKey(), $guid]);
265 23
        }
266 24
        $data = $this->db->executeCommand('HGETALL', [$this->getItemKey($guid)]);
267 24
        $dataRow = ['name' => $name];
268 24
        $nbProps = count($data);
269 24
        for ($i = 0; $i < $nbProps; $i = $i + 2) {
270 24
            $dataRow[$data[$i]] = $data[($i + 1)];
271 24
        }
272 24
        if (isset($dataRow['ruleGuid']) === true) {
273 19
            $ruleName = $this->db->executeCommand('HGET', [$this->getRuleMappingGuidKey(), $dataRow['ruleGuid']]);
274 19
            if ($ruleName !== null) {
275 19
                $dataRow['ruleName'] = $ruleName;
276 19
            }
277 19
            unset($dataRow['ruleGuid']);
278 24
        } elseif(isset($dataRow['ruleClass']) === true) {
279 1
            $dataRow['ruleName'] = $dataRow['ruleClass'];
280 1
            unset($dataRow['ruleClass']);
281 1
        }
282 24
        $item = $this->populateItem($dataRow);
283 24
        return $item;
284
    }
285
286
    /**
287
     * @inheritdoc
288
     */
289 3
    protected function getItems($type)
290
    {
291 3
        $itemGuids = $this->db->executeCommand('SMEMBERS', [$this->getTypeItemsKey($type)]);
292 3
        $items = [];
293 3
        foreach($itemGuids as $itemGuid) {
294 3
            $item = $this->getItemByGuid($itemGuid);
295 3
            $items[$item->name] = $item;
296 3
        }
297 3
        return $items;
298
    }
299
300
    /**
301
     * @inheritdoc
302
     */
303 28
    protected function addItem($item)
304
    {
305 28
        if (empty($item->name) === true) {
306 1
            throw new InvalidParamException("Item name must be defined");
307
        }
308 28
        $itemExists = (int)$this->db->executeCommand('HEXISTS', [$this->getItemMappingKey(), $item->name]);
309 28
        if ($itemExists === 1)
310 28
        {
311 1
            throw new DuplicateKeyException("Rule '{$item->name}' already defined");
312
        }
313 28
        $guid = Guid::v4();
314 28
        $time = time();
315 28
        if ($item->createdAt === null) {
316 28
            $item->createdAt = $time;
317 28
        }
318 28
        if ($item->updatedAt === null) {
319 28
            $item->updatedAt = $time;
320 28
        }
321
322 28
        $insertInRedis = $this->buildRedisItemInsert($item, $guid);
323 28
        $this->db->executeCommand('MULTI');
324
        // update mapping
325 28
        $this->db->executeCommand('HSET', [$this->getItemMappingKey(), $item->name, $guid]);
326 28
        $this->db->executeCommand('HSET', [$this->getItemMappingGuidKey(), $guid, $item->name]);
327
        // insert item
328 28
        $this->db->executeCommand('HMSET', $insertInRedis);
329 28
        $this->db->executeCommand('SADD', [$this->getTypeItemsKey($item->type), $guid]);
330
        // affect rule
331 28
        if (isset($insertInRedis['ruleGuid']) === true) {
332
            $this->db->executeCommand('SADD', [$this->getRuleItemsKey($insertInRedis['ruleGuid']), $guid]);
333
        }
334 28
        $this->db->executeCommand('EXEC');
335 28
        return true;
336
337
    }
338
339
    /**
340
     * @param Item $item item to insert in DB
341
     * @param string $guid guid generated
342
     * @return array Redis command
343
     * @since XXX
344
     */
345 28
    private function buildRedisItemInsert($item, $guid)
346
    {
347 28
        $insertItem = [$this->getItemKey($guid),
348 28
            'data', serialize($item->data),
349 28
            'type', $item->type,
350 28
            'createdAt', $item->createdAt,
351 28
            'updatedAt', $item->updatedAt
352 28
        ];
353 28
        $ruleGuid = null;
354 28
        $ruleClass = null;
355 28
        if (empty($item->ruleName) === false) {
356 19
            $ruleGuid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $item->ruleName]);
357 19
            if (($ruleGuid === null) && class_exists($item->ruleName)) {
358 1
                $ruleClass = $item->ruleName;
359 19
            } elseif(($ruleGuid === null) && (Yii::$container->has($item->ruleName))) {
360 1
                $ruleClass = $item->ruleName;
361 1
            }
362 19
        }
363
364 28
        if ($item->description !== null) {
365 22
            $insertItem[] = 'description';
366 22
            $insertItem[] = $item->description;
367 22
        }
368 28
        if ($ruleGuid !== null) {
369 19
            $insertItem[] = 'ruleGuid';
370 19
            $insertItem[] = $ruleGuid;
371 19
        }
372 28
        if ($ruleClass !== null) {
373 1
            $insertItem[] = 'ruleClass';
374 1
            $insertItem[] = $ruleClass;
375 1
        }
376 28
        return $insertItem;
377
    }
378
379
    /**
380
     * @inheritdoc
381
     */
382 2
    protected function updateItem($name, $item)
383
    {
384 2
        $item->updatedAt = time();
385
386 2
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $name]);
387 2
        $ruleGuid = null;
388 2
        $ruleClass = null;
389 2
        if (empty($item->ruleName) === false) {
390 1
            $ruleGuid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $item->ruleName]);
391 1
            if (($ruleGuid === null) && class_exists($item->ruleName)) {
392
                $ruleClass = $item->ruleName;
393 1
            } elseif(($ruleGuid === null) && (Yii::$container->has($item->ruleName))) {
394 1
                $ruleClass = $item->ruleName;
395 1
            }
396 1
        }
397 2
        $newRule = ($ruleGuid === null) ? $ruleClass : $ruleGuid;
398
399 2
        list($currentRuleGuid, $currentRuleClass, $currentType) = $this->db->executeCommand('HMGET', [$this->getItemKey($guid), 'ruleGuid', 'ruleClass', 'type']);
400
401 2
        $oldRule = ($currentRuleGuid === null) ? $currentRuleClass : $currentRuleGuid;
402
403 2
        $this->db->executeCommand('MULTI');
404 2
        if ($name !== $item->name) {
405
            // delete old mapping
406 2
            $this->db->executeCommand('HDEL', [$this->getItemMappingKey(), $name]);
407
            // add new mapping
408 2
            $this->db->executeCommand('HSET', [$this->getItemMappingKey(), $item->name, $guid]);
409 2
            $this->db->executeCommand('HSET', [$this->getItemMappingGuidKey(), $guid, $item->name]);
410 2
        }
411
412 2
        $updateEmptyItem = [$this->getItemKey($guid)];
413 2
        $updateItem = [$this->getItemKey($guid),
414 2
            'data', serialize($item->data),
415 2
            'type', $item->type,
416 2
            'createdAt', $item->createdAt,
417 2
            'updatedAt', $item->updatedAt
418 2
        ];
419 2
        if ($item->description !== null) {
420 1
            $updateItem[] = 'description';
421 1
            $updateItem[] = $item->description;
422 1
        } else {
423 1
            $updateEmptyItem[] = 'description';
424
        }
425 2
        if ($newRule !== $oldRule) {
426 1
            if ($currentRuleGuid !== null) {
427
                $this->db->executeCommand('SREM', [$this->getRuleItemsKey($currentRuleGuid), $guid]);
428
            }
429 1
            if ($ruleGuid !== null) {
430
                $this->db->executeCommand('SADD', [$this->getRuleItemsKey($ruleGuid), $guid]);
431
            }
432 1
            if ($ruleGuid !== null) {
433
                $updateItem[] = 'ruleGuid';
434
                $updateItem[] = $ruleGuid;
435
            } else {
436 1
                $updateEmptyItem[] = 'ruleGuid';
437
            }
438 1
            if ($ruleClass !== null) {
439 1
                $updateItem[] = 'ruleClass';
440 1
                $updateItem[] = $ruleClass;
441 1
            } else {
442
                $updateEmptyItem[] = 'ruleClass';
443
            }
444 1
        }
445 2
        if ($item->type !== $currentType) {
446
            $this->db->executeCommand('SREM', [$this->getTypeItemsKey($currentType), $guid]);
447
            $this->db->executeCommand('SADD', [$this->getTypeItemsKey($item->type), $guid]);
448
        }
449
450
        // update item
451 2
        $this->db->executeCommand('HMSET', $updateItem);
452
        // remove useless props
453 2
        if (count($updateEmptyItem) > 1) {
454 1
            $this->db->executeCommand('HDEL', $updateEmptyItem);
455 1
        }
456
457
458 2
        $this->db->executeCommand('EXEC');
459 2
        return true;
460
461
    }
462
463
    /**
464
     * @inheritdoc
465
     */
466 3
    protected function removeItem($item)
467
    {
468 3
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $item->name]);
469 3
        $ruleGuid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $item->ruleName]);
470
471 3
        $parentGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemParentsKey($guid)]);
472 3
        $childrenGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($guid)]);
473
474 3
        $this->db->executeCommand('MULTI');
475
        // delete mapping
476 3
        $this->db->executeCommand('HDEL', [$this->getItemMappingKey(), $item->name]);
477 3
        $this->db->executeCommand('HDEL', [$this->getItemMappingGuidKey(), $guid]);
478
        // delete rule <-> item link
479 3
        $this->db->executeCommand('SREM', [$this->getRuleItemsKey($ruleGuid), $guid]);
480 3
        $this->db->executeCommand('SREM', [$this->getTypeItemsKey($item->type), $guid]);
481
        // detach from hierarchy
482 3
        foreach($parentGuids as $parentGuid) {
483 2
            $this->db->executeCommand('SREM', [$this->getItemChildrenKey($parentGuid), $guid]);
484 3
        }
485
        // detach children
486 3
        foreach($childrenGuids as $childGuid) {
487 1
            $this->db->executeCommand('SREM', [$this->getItemParentsKey($childGuid), $guid]);
488 3
        }
489 3
        $this->db->executeCommand('DEL', [$this->getItemParentsKey($guid)]);
490 3
        $this->db->executeCommand('DEL', [$this->getItemChildrenKey($guid)]);
491
        // delete rule
492 3
        $this->db->executeCommand('DEL', [$this->getItemKey($guid)]);
493 3
        $this->db->executeCommand('EXEC');
494 3
        return true;
495
496
    }
497
498
    /**
499
     * @inheritdoc
500
     */
501 21
    protected function addRule($rule)
502
    {
503 21
        if(empty($rule->name) === true) {
504 1
            throw new InvalidParamException("Rule name must be defined");
505
        }
506 21
        $ruleExists = (int)$this->db->executeCommand('HEXISTS', [$this->getRuleMappingKey(), $rule->name]);
507 21
        if ($ruleExists === 1)
508 21
        {
509 1
            throw new DuplicateKeyException("Rule '{$rule->name}' already defined");
510
        }
511 21
        $guid = Guid::v4();
512 21
        $time = time();
513 21
        if ($rule->createdAt === null) {
514 21
            $rule->createdAt = $time;
515 21
        }
516 21
        if ($rule->updatedAt === null) {
517 21
            $rule->updatedAt = $time;
518 21
        }
519 21
        $this->db->executeCommand('MULTI');
520 21
        $this->db->executeCommand('HSET', [$this->getRuleMappingKey(), $rule->name, $guid]);
521 21
        $this->db->executeCommand('HSET', [$this->getRuleMappingGuidKey(),$guid, $rule->name]);
522 21
        $this->db->executeCommand('HMSET', [$this->getRuleKey($guid),
523 21
            'data', serialize($rule),
524 21
            'createdAt', $rule->createdAt,
525 21
            'updatedAt', $rule->updatedAt
526 21
        ]);
527
528 21
        $this->db->executeCommand('EXEC');
529 21
        return true;
530
531
    }
532
533
    /**
534
     * @inheritdoc
535
     */
536 1
    protected function updateRule($name, $rule)
537
    {
538 1
        $rule->updatedAt = time();
539
540 1
        $guid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $name]);
541
542 1
        $this->db->executeCommand('MULTI');
543 1
        if ($name !== $rule->name) {
544
            // delete old mapping
545 1
            $this->db->executeCommand('HDEL', [$this->getRuleMappingKey(), $name]);
546
            // add new mapping
547 1
            $this->db->executeCommand('HSET', [$this->getRuleMappingKey(), $rule->name, $guid]);
548 1
            $this->db->executeCommand('HSET', [$this->getRuleMappingGuidKey(),$guid, $rule->name]);
549 1
        }
550 1
        $this->db->executeCommand('HMSET', [$this->getRuleKey($guid),
551 1
            'data', serialize($rule),
552 1
            'createdAt', $rule->createdAt,
553 1
            'updatedAt', $rule->updatedAt
554 1
        ]);
555
556 1
        $this->db->executeCommand('EXEC');
557 1
        return true;
558
    }
559
560
    /**
561
     * @inheritdoc
562
     */
563 2
    protected function removeRule($rule)
564
    {
565 2
        $guid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $rule->name]);
566
567 2
        $ruleMembers = $this->db->executeCommand('SMEMBERS', [$this->getRuleItemsKey($guid)]);
568
569 2
        $this->db->executeCommand('MULTI');
570
        // delete mapping
571 2
        $this->db->executeCommand('HDEL', [$this->getRuleMappingKey(), $rule->name]);
572 2
        $this->db->executeCommand('HDEL', [$this->getRuleMappingGuidKey(), $guid]);
573
        // detach items
574 2
        foreach($ruleMembers as $itemGuid)
575
        {
576
            $this->db->executeCommand('HDEL', [$this->getItemKey($itemGuid), 'ruleGuid']);
577 2
        }
578
        // delete rule <-> item link
579 2
        $this->db->executeCommand('DEL', [$this->getRuleItemsKey($guid)]);
580
        // delete rule
581 2
        $this->db->executeCommand('DEL', [$this->getRuleKey($guid)]);
582 2
        $this->db->executeCommand('EXEC');
583 2
        return true;
584
    }
585
586
    /**
587
     * @inheritdoc
588
     */
589 5
    public function getRules()
590
    {
591 5
        $ruleNames = $this->db->executeCommand('HKEYS', [$this->getRuleMappingKey()]);
592 5
        $rules = [];
593 5
        foreach ($ruleNames as $ruleName)
594
        {
595 4
            $rules[$ruleName] = $this->getRule($ruleName);
596 5
        }
597 5
        return $rules;
598
    }
599
600
    /**
601
     * @inheritdoc
602
     */
603 10
    public function getRule($name)
604
    {
605 10
        $rule = null;
606 10
        $guid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $name]);
607 10
        if ($guid !== null) {
608 10
            $rule = $this->getRuleGuid($guid);
609 10
        } elseif(class_exists($name) === true) {
610 1
            $rule = new $name;
611 3
        } elseif(Yii::$container->has($name) === true) {
612 1
            $rule = Yii::$container->get($name);
613 1
        }
614 10
        return $rule;
615
    }
616
617
    /**
618
     * @param string $guid rule unique ID
619
     * @return Rule
620
     * @since XXX
621
     */
622 10
    protected function getRuleGuid($guid)
623
    {
624 10
        $data = $this->db->executeCommand('HGET', [$this->getRuleKey($guid), 'data']);
625 10
        $rule = unserialize($data);
626 10
        return $rule;
627
    }
628
629
    /**
630
     * @inheritdoc
631
     */
632 23
    public function addChild($parent, $child)
633
    {
634 23
        if ($parent->name === $child->name) {
635 1
            throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
636
        }
637 23
        if ($parent instanceof Permission && $child instanceof Role) {
638 1
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
639
        }
640 23
        if ($this->detectLoop($parent, $child)) {
641 1
            throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
642
        }
643
644 23
        list($parentGuid, $childGuid) = $this->db->executeCommand('HMGET', [$this->getItemMappingKey(), $parent->name, $child->name]);
645
646 23
        $this->db->executeCommand('MULTI');
647 23
        $this->db->executeCommand('SADD', [$this->getItemParentsKey($childGuid), $parentGuid]);
648 23
        $this->db->executeCommand('SADD', [$this->getItemChildrenKey($parentGuid), $childGuid]);
649 23
        $this->db->executeCommand('EXEC');
650 23
        return true;
651
    }
652
653
    /**
654
     * @inheritdoc
655
     */
656 1
    public function removeChild($parent, $child)
657
    {
658 1
        list($parentGuid, $childGuid) = $this->db->executeCommand('HMGET', [$this->getItemMappingKey(), $parent->name, $child->name]);
659 1
        $this->db->executeCommand('MULTI');
660 1
        $this->db->executeCommand('SREM', [$this->getItemParentsKey($childGuid), $parentGuid]);
661 1
        $this->db->executeCommand('SREM', [$this->getItemChildrenKey($parentGuid), $childGuid]);
662 1
        $this->db->executeCommand('EXEC');
663 1
        return true;
664
665
    }
666
667
    /**
668
     * @inheritdoc
669
     */
670 1
    public function removeChildren($parent)
671
    {
672 1
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $parent->name]);
673 1
        $childrenGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($guid)]);
674
675 1
        $this->db->executeCommand('MULTI');
676 1
        foreach($childrenGuids as $childGuid)
677
        {
678 1
            $this->db->executeCommand('SREM', [$this->getItemParentsKey($childGuid), $guid]);
679 1
        }
680 1
        $this->db->executeCommand('DEL', [$this->getItemChildrenKey($guid)]);
681 1
        $this->db->executeCommand('EXEC');
682 1
        return true;
683
684
    }
685
686
    /**
687
     * @inheritdoc
688
     */
689 23
    public function getChildren($name)
690
    {
691 23
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $name]);
692 23
        return $this->getChildrenByGuid($guid);
693
694
    }
695
696
    /**
697
     * @param string $guid item guid
698
     * @return Item[]
699
     * @since XXX
700
     */
701 23
    protected function getChildrenByGuid($guid)
702
    {
703 23
        $childrenGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($guid)]);
704 23
        $children = [];
705 23
        if (count($childrenGuids) > 0) {
706 23
            foreach($childrenGuids as $childGuid) {
707 23
                $children[] = $this->getItemByGuid($childGuid);
708 23
            }
709 23
        }
710 23
        return $children;
711
    }
712
713
    /**
714
     * @inheritdoc
715
     */
716 5
    public function hasChild($parent, $child)
717
    {
718 5
        list($parentGuid, $childGuid) = $this->db->executeCommand('HMGET', [$this->getItemMappingKey(), $parent->name, $child->name]);
719 5
        $result = (int)$this->db->executeCommand('SISMEMBER', [$this->getItemChildrenKey($parentGuid), $childGuid]);
720
721 5
        return $result === 1;
722
723
    }
724
725
    /**
726
     * @inheritdoc
727
     */
728 1
    public function revokeAll($userId)
729
    {
730 1
        if (empty($userId) === true) {
731 1
            return false;
732
        }
733 1
        $roleGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf']);
734 1
        $this->db->executeCommand('MULTI');
735 1
        if (count($roleGuids) > 0) {
736 1
            foreach ($roleGuids as $roleGuid) {
737 1
                $this->db->executeCommand('ZREM', [$this->getRoleAssignmentsKey($roleGuid), $userId]);
738 1
            }
739 1
        }
740 1
        $this->db->executeCommand('DEL', [$this->getUserAssignmentsKey($userId)]);
741 1
        $this->db->executeCommand('EXEC');
742 1
        return true;
743
744
    }
745
746
    /**
747
     * @inheritdoc
748
     */
749 1
    public function revoke($role, $userId)
750
    {
751 1
        if (empty($userId) === true) {
752 1
            return false;
753
        }
754 1
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $role->name]);
755
756 1
        $this->db->executeCommand('MULTI');
757 1
        $this->db->executeCommand('ZREM', [$this->getUserAssignmentsKey($userId), $roleGuid]);
758 1
        $this->db->executeCommand('ZREM', [$this->getRoleAssignmentsKey($roleGuid), $userId]);
759 1
        $this->db->executeCommand('EXEC');
760 1
        return true;
761
    }
762
763
    /**
764
     * @inheritdoc
765
     */
766 1
    public function removeAllRoles()
767
    {
768 1
        $this->removeAllItems(Item::TYPE_ROLE);
769 1
    }
770
771
    /**
772
     * @inheritdoc
773
     */
774 1
    public function removeAllRules()
775
    {
776 1
        $rules = $this->getRules();
777 1
        foreach($rules as $rule) {
778 1
            $this->removeRule($rule);
779 1
        }
780 1
    }
781
782
    /**
783
     * @inheritdoc
784
     */
785 1
    public function removeAllPermissions()
786
    {
787 1
        $this->removeAllItems(Item::TYPE_PERMISSION);
788 1
    }
789
790
    /**
791
     * @inheritdoc
792
     */
793 30
    public function removeAll()
794
    {
795 30
        $authKeys = [];
796 30
        $nextCursor = 0;
797
        do {
798 30
            list($nextCursor, $keys) = $this->db->executeCommand('SCAN', [$nextCursor, 'MATCH', $this->globalMatchKey]);
799 30
            $authKeys = array_merge($authKeys, $keys);
800
801 30
        } while($nextCursor != 0);
802
803 30
        if (count($authKeys) > 0) {
804 28
            $this->db->executeCommand('DEL', $authKeys);
805 28
        }
806 30
    }
807
808
    /**
809
     * @inheritdoc
810
     */
811 1
    public function removeAllAssignments()
812
    {
813 1
        $roleAssignKey = $this->getRoleAssignmentsKey('*');
814 1
        $userAssignKey = $this->getUserAssignmentsKey('*');
815 1
        $assignmentKeys = [];
816
817 1
        $nextCursor = 0;
818
        do {
819 1
            list($nextCursor, $keys) = $this->db->executeCommand('SCAN', [$nextCursor, 'MATCH', $roleAssignKey]);
820 1
            $assignmentKeys = array_merge($assignmentKeys, $keys);
821
822 1
        } while($nextCursor != 0);
823
824 1
        $nextCursor = 0;
825
        do {
826 1
            list($nextCursor, $keys) = $this->db->executeCommand('SCAN', [$nextCursor, 'MATCH', $userAssignKey]);
827 1
            $assignmentKeys = array_merge($assignmentKeys, $keys);
828
829 1
        } while($nextCursor != 0);
830
831 1
        if (count($assignmentKeys) > 0) {
832 1
            $this->db->executeCommand('DEL', $assignmentKeys);
833 1
        }
834 1
    }
835
836
    /**
837
     * @param integer $type
838
     * @since XXX
839
     */
840 2
    public function removeAllItems($type)
841
    {
842 2
        $items = $this->getItems($type);
843 2
        foreach ($items as $item) {
844 2
            $this->removeItem($item);
845 2
        }
846 2
    }
847
848
    /**
849
     * @inheritdoc
850
     */
851 4
    public function getRolesByUser($userId)
852
    {
853 4
        if (!isset($userId) || $userId === '') {
854 1
            return [];
855
        }
856 4
        $roleGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf']);
857 4
        $roles = [];
858 4
        if (count($roleGuids) > 0) {
859 4
            foreach ($roleGuids as $roleGuid) {
860 4
                $isRole = (int)$this->db->executeCommand('SISMEMBER', [$this->getTypeItemsKey(Item::TYPE_ROLE), $roleGuid]);
861 4
                if ($isRole === 1) {
862 4
                    $item = $this->getItemByGuid($roleGuid);
863 4
                    $roles[$item->name] = $item;
864 4
                }
865 4
            }
866 4
        }
867
868 4
        return $roles;
869
    }
870
871
    /**
872
     * @inheritdoc
873
     */
874 2
    public function getUserIdsByRole($roleName)
875
    {
876 2
        if (empty($roleName)) {
877 1
            return [];
878
        }
879 1
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $roleName]);
880 1
        $userIds = [];
881 1
        if ($roleGuid !== null) {
882 1
            $userIds = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getRoleAssignmentsKey($roleGuid), '-inf', '+inf']);
883 1
        }
884 1
        return $userIds;
885
886
    }
887
888
    /**
889
     * @inheritdoc
890
     */
891 19
    public function assign($role, $userId)
892
    {
893 19
        $assignment = new Assignment([
894 19
            'userId' => $userId,
895 19
            'roleName' => $role->name,
896 19
            'createdAt' => time(),
897 19
        ]);
898 19
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $role->name]);
899
900 19
        $this->db->executeCommand('MULTI');
901 19
        $this->db->executeCommand('ZADD', [$this->getUserAssignmentsKey($userId), $assignment->createdAt, $roleGuid]);
902 19
        $this->db->executeCommand('ZADD', [$this->getRoleAssignmentsKey($roleGuid), $assignment->createdAt, $userId]);
903 19
        $this->db->executeCommand('EXEC');
904 19
        return $assignment;
905
    }
906
907
    /**
908
     * @inheritdoc
909
     */
910 1
    public function getPermissionsByUser($userId)
911
    {
912 1
        $rolesGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf']);
913 1
        $permissions = [];
914 1
        if (count($rolesGuids) > 0) {
915 1
            $permissionsGuid = [];
916 1
            foreach($rolesGuids as $roleGuid) {
917 1
                $isPerm = (int)$this->db->executeCommand('SISMEMBER', [$this->getTypeItemsKey(Item::TYPE_PERMISSION), $roleGuid]);
918 1
                if ($isPerm === 1) {
919 1
                    $permissionsGuid[] = $roleGuid;
920 1
                }
921 1
            }
922 1
            foreach ($rolesGuids as $roleGuid) {
923 1
                list(, $permGuids) = $this->getChildrenRecursiveGuid($roleGuid, Item::TYPE_PERMISSION);
924 1
                $permissionsGuid = array_merge($permissionsGuid, $permGuids);
925 1
            }
926 1
            foreach($permissionsGuid as $permissionGuid) {
927 1
                $item = $this->getItemByGuid($permissionGuid);
928 1
                $permissions[$item->name] = $item;
929 1
            }
930 1
        }
931 1
        return $permissions;
932
    }
933
934
    /**
935
     * @inheritdoc
936
     */
937 1
    public function getPermissionsByRole($roleName)
938
    {
939 1
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $roleName]);
940 1
        $permissions = [];
941 1
        list(, $permissionsGuid) = $this->getChildrenRecursiveGuid($roleGuid, Item::TYPE_PERMISSION);
942 1
        foreach($permissionsGuid as $permissionGuid) {
943 1
            $item = $this->getItemByGuid($permissionGuid);
944 1
            $permissions[$item->name] = $item;
945 1
        }
946 1
        return $permissions;
947
    }
948
949
    /**
950
     * @param string $itemGuid item unique ID
951
     * @param integer $type Item type
952
     * @return array
953
     * @since XXX
954
     */
955 2
    protected function getChildrenRecursiveGuid($itemGuid, $type)
956
    {
957 2
        $childrenGuid = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($itemGuid)]);
958 2
        $typedChildrenGuid = $this->db->executeCommand('SINTER', [$this->getItemChildrenKey($itemGuid), $this->getTypeItemsKey($type)]);
959 2
        foreach($childrenGuid as $childGuid) {
960 2
            list($subChildrenGuid, $subTypedChildrenGuid) = $this->getChildrenRecursiveGuid($childGuid, $type);
961 2
            $childrenGuid = array_merge($childrenGuid, $subChildrenGuid);
962 2
            $typedChildrenGuid = array_merge($typedChildrenGuid, $subTypedChildrenGuid);
963 2
        }
964 2
        return [$childrenGuid, $typedChildrenGuid];
965
    }
966
967
    /**
968
     * @param Item $parent parent item
969
     * @param Item $child child item
970
     * @return bool
971
     * @since XXX
972
     */
973 23
    protected function detectLoop(Item $parent, Item $child)
974
    {
975 23
        if ($child->name === $parent->name) {
976 2
            return true;
977
        }
978 23
        foreach ($this->getChildren($child->name) as $grandchild) {
979 19
            if ($this->detectLoop($parent, $grandchild)) {
980 2
                return true;
981
            }
982 23
        }
983 23
        return false;
984
    }
985
986
    /**
987
     * @param string $itemName item name
988
     * @return array
989
     * @since XXX
990
     */
991 1
    public function getParents($itemName)
992
    {
993 1
        $itemGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $itemName]);
994 1
        return $this->getParentsGuid($itemGuid);
995
    }
996
997 1
    protected function getParentsGuid($itemGuid)
998
    {
999 1
        $parentsGuid = $this->db->executeCommand('SMEMBERS', [$this->getItemParentsKey($itemGuid)]);
1000 1
        $parents = [];
1001 1
        if (count($parentsGuid) > 0) {
1002 1
            array_unshift($parentsGuid, $this->getItemMappingGuidKey());
1003 1
            $parents = $this->db->executeCommand('HMGET', $parentsGuid);
1004 1
        }
1005 1
        return $parents;
1006
    }
1007
1008
    /**
1009
     * @inheritdoc
1010
     */
1011 3
    public function getAssignments($userId)
1012
    {
1013 3
        $roleGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf', 'WITHSCORES']);
1014 3
        $assignments = [];
1015 3
        if (count($roleGuids) > 0) {
1016 3
            $guids = [];
1017 3
            $dates = [];
1018 3
            $nbRolesGuids = count($roleGuids);
1019 3
            for($i=0; $i < $nbRolesGuids; $i = $i+2) {
1020 3
                $guids[] = $roleGuids[$i];
1021 3
                $dates[] = $roleGuids[($i + 1)];
1022 3
            }
1023 3
            array_unshift($guids, $this->getItemMappingGuidKey());
1024 3
            $names = $this->db->executeCommand('HMGET', $guids);
1025 3
            foreach ($names as $i => $name) {
1026 3
                $assignments[$name] = new Assignment([
1027 3
                    'userId' => $userId,
1028 3
                    'roleName' => $name,
1029 3
                    'createdAt' => $dates[$i],
1030 3
                ]);
1031 3
            }
1032 3
        }
1033
1034 3
        return $assignments;
1035
1036
    }
1037
1038
    /**
1039
     * @inheritdoc
1040
     */
1041
    public function getAssignment($roleName, $userId)
1042
    {
1043
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $roleName]);
1044
        $assignment = null;
1045
        if ($roleGuid !== null) {
1046
            $assignmentScore = $this->db->executeCommand('ZSCORE', [$this->getUserAssignmentsKey($userId), $roleGuid]);
1047
            if ($assignmentScore !== null) {
1048
                $assignment = new Assignment([
1049
                    'userId' => $userId,
1050
                    'roleName' => $roleName,
1051
                    'createdAt' => $assignmentScore,
1052
                ]);
1053
            }
1054
        }
1055
        return $assignment;
1056
    }
1057
1058 2
    public function checkAccess($userId, $permissionName, $params = [])
1059
    {
1060 2
        $assignments = $this->getAssignments($userId);
1061 2
        return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
1062
    }
1063
1064
1065
    /**
1066
     * @param Item $parent parent item
1067
     * @param Item $child child item
1068
     * @return bool
1069
     * @since XXX
1070
     */
1071 1
    public function canAddChild(Item $parent, Item $child)
1072
    {
1073 1
        return !$this->detectLoop($parent, $child);
1074
    }
1075
1076 2
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
1077
    {
1078 2
        if (($item = $this->getItem($itemName)) === null) {
1079 1
            return false;
1080
        }
1081 2
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
1082 2
        if (!$this->executeRule($user, $item, $params)) {
1083 2
            return false;
1084
        }
1085 2
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
1086 2
            return true;
1087
        }
1088 1
        $parents = $this->getParents($itemName);
1089 1
        foreach ($parents as $parent) {
1090 1
            if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
1091 1
                return true;
1092
            }
1093 1
        }
1094 1
        return false;
1095
    }
1096
1097
    /**
1098
     * @param array $dataRow
1099
     * @return Permission|Role
1100
     * @since XXX
1101
     */
1102 24
    protected function populateItem($dataRow)
1103
    {
1104 24
        $class = $dataRow['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
1105 24
        if (!isset($dataRow['data']) || ($data = @unserialize($dataRow['data'])) === false) {
1106
            $data = null;
1107
        }
1108
1109 24
        return new $class([
1110 24
            'name' => $dataRow['name'],
1111 24
            'type' => $dataRow['type'],
1112 24
            'description' => isset($dataRow['description']) ? $dataRow['description'] : null,
1113 24
            'ruleName' => isset($dataRow['ruleName']) ? $dataRow['ruleName'] : null,
1114 24
            'data' => $data,
1115 24
            'createdAt' => $dataRow['createdAt'],
1116 24
            'updatedAt' => $dataRow['updatedAt'],
1117 24
        ]);
1118
    }
1119
}
1120