Completed
Push — devel ( af81b7...e363c0 )
by Philippe
02:40 queued 35s
created

Manager::getAssignments()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 26
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 4

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 26
ccs 22
cts 22
cp 1
rs 8.5806
cc 4
eloc 18
nc 2
nop 1
crap 4
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 28
    public function getRuleMappingKey()
209
    {
210 28
        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
            $item = $this->getItemByGuid($guid, $name);
250 10
        }
251 10
        return $item;
252
    }
253
254
    /**
255
     * @param string $guid item guid
256
     * @param string $name name if known to avoid additional call
257
     * @return mixed
258
     * @since XXX
259
     */
260 24
    protected function getItemByGuid($guid, $name = null)
261
    {
262 24
        if ($name === null) {
263 23
            $name = $this->db->executeCommand('HGET', [$this->getItemMappingGuidKey(), $guid]);
264 23
        }
265 24
        $data = $this->db->executeCommand('HGETALL', [$this->getItemKey($guid)]);
266 24
        $dataRow = ['name' => $name];
267 24
        $nbProps = count($data);
268 24
        for ($i = 0; $i < $nbProps; $i = $i + 2) {
269 24
            $dataRow[$data[$i]] = $data[($i + 1)];
270 24
        }
271 24
        if (isset($dataRow['ruleGuid']) === true) {
272 19
            $ruleName = $this->db->executeCommand('HGET', [$this->getRuleMappingGuidKey(), $dataRow['ruleGuid']]);
273 19
            if ($ruleName !== null) {
274 19
                $dataRow['ruleName'] = $ruleName;
275 19
            }
276 19
            unset($dataRow['ruleGuid']);
277 24
        } elseif(isset($dataRow['ruleClass']) === true) {
278 1
            $dataRow['ruleName'] = $dataRow['ruleClass'];
279 1
            unset($dataRow['ruleClass']);
280 1
        }
281 24
        $item = $this->populateItem($dataRow);
282 24
        return $item;
283
    }
284
285
    /**
286
     * @inheritdoc
287
     */
288 3
    protected function getItems($type)
289
    {
290 3
        $itemGuids = $this->db->executeCommand('SMEMBERS', [$this->getTypeItemsKey($type)]);
291 3
        $items = [];
292 3
        foreach($itemGuids as $itemGuid) {
293 3
            $item = $this->getItemByGuid($itemGuid);
294 3
            $items[$item->name] = $item;
295 3
        }
296 3
        return $items;
297
    }
298
299
    /**
300
     * @inheritdoc
301
     */
302 28
    protected function addItem($item)
303
    {
304 28
        if (empty($item->name) === true) {
305 1
            throw new InvalidParamException("Item name must be defined");
306
        }
307 28
        $itemExists = (int)$this->db->executeCommand('HEXISTS', [$this->getItemMappingKey(), $item->name]);
308 28
        if ($itemExists === 1)
309 28
        {
310 1
            throw new DuplicateKeyException("Rule '{$item->name}' already defined");
311
        }
312 28
        $guid = Guid::v4();
313 28
        $time = time();
314 28
        if ($item->createdAt === null) {
315 28
            $item->createdAt = $time;
316 28
        }
317 28
        if ($item->updatedAt === null) {
318 28
            $item->updatedAt = $time;
319 28
        }
320
321 28
        list($insertInRedis, ) = $this->prepareRedisItem($item, $guid);
322 28
        $this->db->executeCommand('MULTI');
323
        // update mapping
324 28
        $this->db->executeCommand('HSET', [$this->getItemMappingKey(), $item->name, $guid]);
325 28
        $this->db->executeCommand('HSET', [$this->getItemMappingGuidKey(), $guid, $item->name]);
326
        // insert item
327 28
        $this->db->executeCommand('HMSET', $insertInRedis);
328 28
        $this->db->executeCommand('SADD', [$this->getTypeItemsKey($item->type), $guid]);
329
        // affect rule
330 28
        if (isset($insertInRedis['ruleGuid']) === true) {
331
            $this->db->executeCommand('SADD', [$this->getRuleItemsKey($insertInRedis['ruleGuid']), $guid]);
332
        }
333 28
        $this->db->executeCommand('EXEC');
334 28
        return true;
335
336
    }
337
338
    /**
339
     * @param Item $item item to insert in DB
340
     * @param string $guid guid generated
341
     * @return array Redis command
342
     * @since XXX
343
     */
344 28
    private function prepareRedisItem($item, $guid)
345
    {
346 28
        $redisCleanItem = [$this->getItemKey($guid)];
347 28
        $redisItem = [$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
354 28
        $ruleGuid = null;
0 ignored issues
show
Unused Code introduced by
$ruleGuid 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...
355 28
        $ruleGuid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $item->ruleName]);
356
357 28
        if ($ruleGuid !== null) {
358 19
            $redisItem[] = 'ruleGuid';
359 19
            $redisItem[] = $ruleGuid;
360 19
            $redisCleanItem[] = 'ruleClass';
361 28
        } elseif (class_exists($item->ruleName) || Yii::$container->has($item->ruleName)) {
362 1
            $redisItem[] = 'ruleClass';
363 1
            $redisItem[] = $item->ruleName;
364 1
            $redisCleanItem[] = 'ruleGuid';
365 1
        }
366
367 28
        if ($item->description !== null) {
368 22
            $redisItem[] = 'description';
369 22
            $redisItem[] = $item->description;
370 22
        } else {
371 24
            $redisCleanItem[] = 'description';
372
        }
373 28
        return [$redisItem, $redisCleanItem];
374
    }
375
376
377
    /**
378
     * @inheritdoc
379
     */
380 2
    protected function updateItem($name, $item)
381
    {
382 2
        $item->updatedAt = time();
383
384 2
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $name]);
385
386 2
        $ruleGuid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $item->ruleName]);
387 2
        $newRule = $ruleGuid;
388 2
        if (($ruleGuid === null) && (empty($item->ruleName) === false)) {
389 1
            $newRule = $item->ruleName;
390 1
        }
391
392 2
        list($currentRuleGuid, $currentRuleClass, $currentType) = $this->db->executeCommand('HMGET', [$this->getItemKey($guid), 'ruleGuid', 'ruleClass', 'type']);
393
394 2
        $oldRule = ($currentRuleGuid === null) ? $currentRuleClass : $currentRuleGuid;
395 2
        $isUpdated = $newRule !== $oldRule;
396
397 2
        $this->db->executeCommand('MULTI');
398 2
        if ($name !== $item->name) {
399 2
            $this->remap($name, $item->name, $guid, false);
400 2
        }
401
402 2
        list($updateItem, $updateEmptyItem) = $this->prepareRedisItem($item, $guid);
403
404 2
        if ($isUpdated && $currentRuleGuid !== null) {
405
            $this->db->executeCommand('SREM', [$this->getRuleItemsKey($currentRuleGuid), $guid]);
406
        }
407 2
        if ($isUpdated && $ruleGuid !== null) {
408
            $this->db->executeCommand('SADD', [$this->getRuleItemsKey($ruleGuid), $guid]);
409
        }
410 2
        if ($item->type !== $currentType) {
411
            $this->db->executeCommand('SREM', [$this->getTypeItemsKey($currentType), $guid]);
412
            $this->db->executeCommand('SADD', [$this->getTypeItemsKey($item->type), $guid]);
413
        }
414
415
        // update item
416 2
        $this->db->executeCommand('HMSET', $updateItem);
417
        // remove useless props
418 2
        if (count($updateEmptyItem) > 1) {
419 2
            $this->db->executeCommand('HDEL', $updateEmptyItem);
420 2
        }
421
422
423 2
        $this->db->executeCommand('EXEC');
424 2
        return true;
425
426
    }
427
428
    /**
429
     * @param string $oldName old mapping name
430
     * @param string $newName new mapping name
431
     * @param string $guid unique ID
432
     * @since XXX
433
     */
434 2
    private function remap($oldName, $newName, $guid, $isRule = false)
435
    {
436 2
        if ($isRule) {
437 1
            $mappingKey = $this->getRuleMappingKey();
438 1
            $mappingGuidKey = $this->getRuleMappingGuidKey();
439 1
        } else {
440 2
            $mappingKey = $this->getItemMappingKey();
441 2
            $mappingGuidKey = $this->getItemMappingGuidKey();
442
        }
443 2
        $this->db->executeCommand('HDEL', [$mappingKey, $oldName]);
444
        // add new mapping
445 2
        $this->db->executeCommand('HSET', [$mappingKey, $newName, $guid]);
446 2
        $this->db->executeCommand('HSET', [$mappingGuidKey, $guid, $newName]);
447 2
    }
448
449
    /**
450
     * @inheritdoc
451
     */
452 3
    protected function removeItem($item)
453
    {
454 3
        $mappingKey = $this->getItemMappingKey();
455
456 3
        $guid = $this->db->executeCommand('HGET', [$mappingKey, $item->name]);
457 3
        $ruleGuid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $item->ruleName]);
458
459 3
        $parentGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemParentsKey($guid)]);
460 3
        $childrenGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($guid)]);
461
462 3
        $this->db->executeCommand('MULTI');
463
        // delete mapping
464 3
        $this->db->executeCommand('HDEL', [$mappingKey, $item->name]);
465 3
        $this->db->executeCommand('HDEL', [$this->getItemMappingGuidKey(), $guid]);
466
        // delete rule <-> item link
467 3
        $this->db->executeCommand('SREM', [$this->getRuleItemsKey($ruleGuid), $guid]);
468 3
        $this->db->executeCommand('SREM', [$this->getTypeItemsKey($item->type), $guid]);
469
        // detach from hierarchy
470 3
        foreach($parentGuids as $parentGuid) {
471 3
            $this->db->executeCommand('SREM', [$this->getItemChildrenKey($parentGuid), $guid]);
472 3
        }
473
        // detach children
474 3
        foreach($childrenGuids as $childGuid) {
475 1
            $this->db->executeCommand('SREM', [$this->getItemParentsKey($childGuid), $guid]);
476 3
        }
477 3
        $this->db->executeCommand('DEL', [$this->getItemParentsKey($guid)]);
478 3
        $this->db->executeCommand('DEL', [$this->getItemChildrenKey($guid)]);
479
        // delete rule
480 3
        $this->db->executeCommand('DEL', [$this->getItemKey($guid)]);
481 3
        $this->db->executeCommand('EXEC');
482 3
        return true;
483
484
    }
485
486
    /**
487
     * @inheritdoc
488
     */
489 21
    protected function addRule($rule)
490
    {
491 21
        if(empty($rule->name) === true) {
492 1
            throw new InvalidParamException("Rule name must be defined");
493
        }
494 21
        $ruleExists = (int)$this->db->executeCommand('HEXISTS', [$this->getRuleMappingKey(), $rule->name]);
495 21
        if ($ruleExists === 1)
496 21
        {
497 1
            throw new DuplicateKeyException("Rule '{$rule->name}' already defined");
498
        }
499 21
        $guid = Guid::v4();
500 21
        $time = time();
501 21
        if ($rule->createdAt === null) {
502 21
            $rule->createdAt = $time;
503 21
        }
504 21
        if ($rule->updatedAt === null) {
505 21
            $rule->updatedAt = $time;
506 21
        }
507 21
        $this->db->executeCommand('MULTI');
508 21
        $this->db->executeCommand('HSET', [$this->getRuleMappingKey(), $rule->name, $guid]);
509 21
        $this->db->executeCommand('HSET', [$this->getRuleMappingGuidKey(),$guid, $rule->name]);
510 21
        $this->db->executeCommand('HMSET', [$this->getRuleKey($guid),
511 21
            'data', serialize($rule),
512 21
            'createdAt', $rule->createdAt,
513 21
            'updatedAt', $rule->updatedAt
514 21
        ]);
515
516 21
        $this->db->executeCommand('EXEC');
517 21
        return true;
518
519
    }
520
521
    /**
522
     * @inheritdoc
523
     */
524 1
    protected function updateRule($name, $rule)
525
    {
526 1
        $rule->updatedAt = time();
527
528 1
        $guid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $name]);
529
530 1
        $this->db->executeCommand('MULTI');
531 1
        if ($name !== $rule->name) {
532 1
            $this->remap($name, $rule->name, $guid, true);
533 1
        }
534 1
        $this->db->executeCommand('HMSET', [$this->getRuleKey($guid),
535 1
            'data', serialize($rule),
536 1
            'createdAt', $rule->createdAt,
537 1
            'updatedAt', $rule->updatedAt
538 1
        ]);
539
540 1
        $this->db->executeCommand('EXEC');
541 1
        return true;
542
    }
543
544
    /**
545
     * @inheritdoc
546
     */
547 2
    protected function removeRule($rule)
548
    {
549 2
        $guid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $rule->name]);
550
551 2
        $ruleMembers = $this->db->executeCommand('SMEMBERS', [$this->getRuleItemsKey($guid)]);
552
553 2
        $this->db->executeCommand('MULTI');
554
        // delete mapping
555 2
        $this->db->executeCommand('HDEL', [$this->getRuleMappingKey(), $rule->name]);
556 2
        $this->db->executeCommand('HDEL', [$this->getRuleMappingGuidKey(), $guid]);
557
        // detach items
558 2
        foreach($ruleMembers as $itemGuid)
559
        {
560
            $this->db->executeCommand('HDEL', [$this->getItemKey($itemGuid), 'ruleGuid']);
561 2
        }
562
        // delete rule <-> item link
563 2
        $this->db->executeCommand('DEL', [$this->getRuleItemsKey($guid)]);
564
        // delete rule
565 2
        $this->db->executeCommand('DEL', [$this->getRuleKey($guid)]);
566 2
        $this->db->executeCommand('EXEC');
567 2
        return true;
568
    }
569
570
    /**
571
     * @inheritdoc
572
     */
573 5
    public function getRules()
574
    {
575 5
        $ruleNames = $this->db->executeCommand('HKEYS', [$this->getRuleMappingKey()]);
576 5
        $rules = [];
577 5
        foreach ($ruleNames as $ruleName)
578
        {
579 4
            $rules[$ruleName] = $this->getRule($ruleName);
580 5
        }
581 5
        return $rules;
582
    }
583
584
    /**
585
     * @inheritdoc
586
     */
587 10
    public function getRule($name)
588
    {
589 10
        $rule = null;
590 10
        $guid = $this->db->executeCommand('HGET', [$this->getRuleMappingKey(), $name]);
591 10
        if ($guid !== null) {
592 10
            $rule = $this->getRuleGuid($guid);
593 10
        } elseif(class_exists($name) === true) {
594 1
            $rule = new $name;
595 3
        } elseif(Yii::$container->has($name) === true) {
596 1
            $rule = Yii::$container->get($name);
597 1
        }
598 10
        return $rule;
599
    }
600
601
    /**
602
     * @param string $guid rule unique ID
603
     * @return Rule
604
     * @since XXX
605
     */
606 10
    protected function getRuleGuid($guid)
607
    {
608 10
        $data = $this->db->executeCommand('HGET', [$this->getRuleKey($guid), 'data']);
609 10
        $rule = unserialize($data);
610 10
        return $rule;
611
    }
612
613
    /**
614
     * @inheritdoc
615
     */
616 23
    public function addChild($parent, $child)
617
    {
618 23
        if ($parent->name === $child->name) {
619 1
            throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself.");
620
        }
621 23
        if ($parent instanceof Permission && $child instanceof Role) {
622 1
            throw new InvalidParamException('Cannot add a role as a child of a permission.');
623
        }
624 23
        if ($this->detectLoop($parent, $child)) {
625 1
            throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected.");
626
        }
627
628 23
        list($parentGuid, $childGuid) = $this->db->executeCommand('HMGET', [$this->getItemMappingKey(), $parent->name, $child->name]);
629
630 23
        $this->db->executeCommand('MULTI');
631 23
        $this->db->executeCommand('SADD', [$this->getItemParentsKey($childGuid), $parentGuid]);
632 23
        $this->db->executeCommand('SADD', [$this->getItemChildrenKey($parentGuid), $childGuid]);
633 23
        $this->db->executeCommand('EXEC');
634 23
        return true;
635
    }
636
637
    /**
638
     * @inheritdoc
639
     */
640 1
    public function removeChild($parent, $child)
641
    {
642 1
        list($parentGuid, $childGuid) = $this->db->executeCommand('HMGET', [$this->getItemMappingKey(), $parent->name, $child->name]);
643 1
        $this->db->executeCommand('MULTI');
644 1
        $this->db->executeCommand('SREM', [$this->getItemParentsKey($childGuid), $parentGuid]);
645 1
        $this->db->executeCommand('SREM', [$this->getItemChildrenKey($parentGuid), $childGuid]);
646 1
        $this->db->executeCommand('EXEC');
647 1
        return true;
648
649
    }
650
651
    /**
652
     * @inheritdoc
653
     */
654 1
    public function removeChildren($parent)
655
    {
656 1
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $parent->name]);
657 1
        $childrenGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($guid)]);
658
659 1
        $this->db->executeCommand('MULTI');
660 1
        foreach($childrenGuids as $childGuid)
661
        {
662 1
            $this->db->executeCommand('SREM', [$this->getItemParentsKey($childGuid), $guid]);
663 1
        }
664 1
        $this->db->executeCommand('DEL', [$this->getItemChildrenKey($guid)]);
665 1
        $this->db->executeCommand('EXEC');
666 1
        return true;
667
668
    }
669
670
    /**
671
     * @inheritdoc
672
     */
673 23
    public function getChildren($name)
674
    {
675 23
        $guid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $name]);
676 23
        return $this->getChildrenByGuid($guid);
677
678
    }
679
680
    /**
681
     * @param string $guid item guid
682
     * @return Item[]
683
     * @since XXX
684
     */
685 23
    protected function getChildrenByGuid($guid)
686
    {
687 23
        $childrenGuids = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($guid)]);
688 23
        $children = [];
689 23
        if (count($childrenGuids) > 0) {
690 23
            foreach($childrenGuids as $childGuid) {
691 23
                $children[] = $this->getItemByGuid($childGuid);
692 23
            }
693 23
        }
694 23
        return $children;
695
    }
696
697
    /**
698
     * @inheritdoc
699
     */
700 5
    public function hasChild($parent, $child)
701
    {
702 5
        list($parentGuid, $childGuid) = $this->db->executeCommand('HMGET', [$this->getItemMappingKey(), $parent->name, $child->name]);
703 5
        $result = (int)$this->db->executeCommand('SISMEMBER', [$this->getItemChildrenKey($parentGuid), $childGuid]);
704
705 5
        return $result === 1;
706
707
    }
708
709
    /**
710
     * @inheritdoc
711
     */
712 1
    public function revokeAll($userId)
713
    {
714 1
        if (empty($userId) === true) {
715 1
            return false;
716
        }
717 1
        $roleGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf']);
718 1
        $this->db->executeCommand('MULTI');
719 1
        if (count($roleGuids) > 0) {
720 1
            foreach ($roleGuids as $roleGuid) {
721 1
                $this->db->executeCommand('ZREM', [$this->getRoleAssignmentsKey($roleGuid), $userId]);
722 1
            }
723 1
        }
724 1
        $this->db->executeCommand('DEL', [$this->getUserAssignmentsKey($userId)]);
725 1
        $this->db->executeCommand('EXEC');
726 1
        return true;
727
728
    }
729
730
    /**
731
     * @inheritdoc
732
     */
733 1
    public function revoke($role, $userId)
734
    {
735 1
        if (empty($userId) === true) {
736 1
            return false;
737
        }
738 1
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $role->name]);
739
740 1
        $this->db->executeCommand('MULTI');
741 1
        $this->db->executeCommand('ZREM', [$this->getUserAssignmentsKey($userId), $roleGuid]);
742 1
        $this->db->executeCommand('ZREM', [$this->getRoleAssignmentsKey($roleGuid), $userId]);
743 1
        $this->db->executeCommand('EXEC');
744 1
        return true;
745
    }
746
747
    /**
748
     * @inheritdoc
749
     */
750 1
    public function removeAllRoles()
751
    {
752 1
        $this->removeAllItems(Item::TYPE_ROLE);
753 1
    }
754
755
    /**
756
     * @inheritdoc
757
     */
758 1
    public function removeAllRules()
759
    {
760 1
        $rules = $this->getRules();
761 1
        foreach($rules as $rule) {
762 1
            $this->removeRule($rule);
763 1
        }
764 1
    }
765
766
    /**
767
     * @inheritdoc
768
     */
769 1
    public function removeAllPermissions()
770
    {
771 1
        $this->removeAllItems(Item::TYPE_PERMISSION);
772 1
    }
773
774
    /**
775
     * @inheritdoc
776
     */
777 30
    public function removeAll()
778
    {
779 30
        $authKeys = [];
780 30
        $nextCursor = 0;
781
        do {
782 30
            list($nextCursor, $keys) = $this->db->executeCommand('SCAN', [$nextCursor, 'MATCH', $this->globalMatchKey]);
783 30
            $authKeys = array_merge($authKeys, $keys);
784
785 30
        } while($nextCursor != 0);
786
787 30
        if (count($authKeys) > 0) {
788 28
            $this->db->executeCommand('DEL', $authKeys);
789 28
        }
790 30
    }
791
792
    /**
793
     * @inheritdoc
794
     */
795 1
    public function removeAllAssignments()
796
    {
797 1
        $roleAssignKey = $this->getRoleAssignmentsKey('*');
798 1
        $userAssignKey = $this->getUserAssignmentsKey('*');
799 1
        $assignmentKeys = [];
800
801 1
        $nextCursor = 0;
802
        do {
803 1
            list($nextCursor, $keys) = $this->db->executeCommand('SCAN', [$nextCursor, 'MATCH', $roleAssignKey]);
804 1
            $assignmentKeys = array_merge($assignmentKeys, $keys);
805
806 1
        } while($nextCursor != 0);
807
808 1
        $nextCursor = 0;
809
        do {
810 1
            list($nextCursor, $keys) = $this->db->executeCommand('SCAN', [$nextCursor, 'MATCH', $userAssignKey]);
811 1
            $assignmentKeys = array_merge($assignmentKeys, $keys);
812
813 1
        } while($nextCursor != 0);
814
815 1
        if (count($assignmentKeys) > 0) {
816 1
            $this->db->executeCommand('DEL', $assignmentKeys);
817 1
        }
818 1
    }
819
820
    /**
821
     * @param integer $type
822
     * @since XXX
823
     */
824 2
    public function removeAllItems($type)
825
    {
826 2
        $items = $this->getItems($type);
827 2
        foreach ($items as $item) {
828 2
            $this->removeItem($item);
829 2
        }
830 2
    }
831
832
    /**
833
     * @inheritdoc
834
     */
835 4
    public function getRolesByUser($userId)
836
    {
837 4
        if (!isset($userId) || $userId === '') {
838 1
            return [];
839
        }
840 4
        $roleGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf']);
841 4
        $roles = [];
842 4
        if (count($roleGuids) > 0) {
843 4
            foreach ($roleGuids as $roleGuid) {
844 4
                $isRole = (int)$this->db->executeCommand('SISMEMBER', [$this->getTypeItemsKey(Item::TYPE_ROLE), $roleGuid]);
845 4
                if ($isRole === 1) {
846 4
                    $item = $this->getItemByGuid($roleGuid);
847 4
                    $roles[$item->name] = $item;
848 4
                }
849 4
            }
850 4
        }
851
852 4
        return $roles;
853
    }
854
855
    /**
856
     * @inheritdoc
857
     */
858 2
    public function getUserIdsByRole($roleName)
859
    {
860 2
        if (empty($roleName)) {
861 1
            return [];
862
        }
863 1
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $roleName]);
864 1
        $userIds = [];
865 1
        if ($roleGuid !== null) {
866 1
            $userIds = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getRoleAssignmentsKey($roleGuid), '-inf', '+inf']);
867 1
        }
868 1
        return $userIds;
869
870
    }
871
872
    /**
873
     * @inheritdoc
874
     */
875 19
    public function assign($role, $userId)
876
    {
877 19
        $assignment = new Assignment([
878 19
            'userId' => $userId,
879 19
            'roleName' => $role->name,
880 19
            'createdAt' => time(),
881 19
        ]);
882 19
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $role->name]);
883
884 19
        $this->db->executeCommand('MULTI');
885 19
        $this->db->executeCommand('ZADD', [$this->getUserAssignmentsKey($userId), $assignment->createdAt, $roleGuid]);
886 19
        $this->db->executeCommand('ZADD', [$this->getRoleAssignmentsKey($roleGuid), $assignment->createdAt, $userId]);
887 19
        $this->db->executeCommand('EXEC');
888 19
        return $assignment;
889
    }
890
891
    /**
892
     * @inheritdoc
893
     */
894 1
    public function getPermissionsByUser($userId)
895
    {
896 1
        $rolesGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf']);
897 1
        $permissions = [];
898 1
        if (count($rolesGuids) > 0) {
899 1
            $permissionsGuid = [];
900 1
            foreach($rolesGuids as $roleGuid) {
901 1
                $isPerm = (int)$this->db->executeCommand('SISMEMBER', [$this->getTypeItemsKey(Item::TYPE_PERMISSION), $roleGuid]);
902 1
                if ($isPerm === 1) {
903 1
                    $permissionsGuid[] = $roleGuid;
904 1
                }
905 1
            }
906 1
            foreach ($rolesGuids as $roleGuid) {
907 1
                list(, $permGuids) = $this->getChildrenRecursiveGuid($roleGuid, Item::TYPE_PERMISSION);
908 1
                $permissionsGuid = array_merge($permissionsGuid, $permGuids);
909 1
            }
910 1
            foreach($permissionsGuid as $permissionGuid) {
911 1
                $item = $this->getItemByGuid($permissionGuid);
912 1
                $permissions[$item->name] = $item;
913 1
            }
914 1
        }
915 1
        return $permissions;
916
    }
917
918
    /**
919
     * @inheritdoc
920
     */
921 1
    public function getPermissionsByRole($roleName)
922
    {
923 1
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $roleName]);
924 1
        $permissions = [];
925 1
        list(, $permissionsGuid) = $this->getChildrenRecursiveGuid($roleGuid, Item::TYPE_PERMISSION);
926 1
        foreach($permissionsGuid as $permissionGuid) {
927 1
            $item = $this->getItemByGuid($permissionGuid);
928 1
            $permissions[$item->name] = $item;
929 1
        }
930 1
        return $permissions;
931
    }
932
933
    /**
934
     * @param string $itemGuid item unique ID
935
     * @param integer $type Item type
936
     * @return array
937
     * @since XXX
938
     */
939 2
    protected function getChildrenRecursiveGuid($itemGuid, $type)
940
    {
941 2
        $childrenGuid = $this->db->executeCommand('SMEMBERS', [$this->getItemChildrenKey($itemGuid)]);
942 2
        $typedChildrenGuid = $this->db->executeCommand('SINTER', [$this->getItemChildrenKey($itemGuid), $this->getTypeItemsKey($type)]);
943 2
        foreach($childrenGuid as $childGuid) {
944 2
            list($subChildrenGuid, $subTypedChildrenGuid) = $this->getChildrenRecursiveGuid($childGuid, $type);
945 2
            $childrenGuid = array_merge($childrenGuid, $subChildrenGuid);
946 2
            $typedChildrenGuid = array_merge($typedChildrenGuid, $subTypedChildrenGuid);
947 2
        }
948 2
        return [$childrenGuid, $typedChildrenGuid];
949
    }
950
951
    /**
952
     * @param Item $parent parent item
953
     * @param Item $child child item
954
     * @return bool
955
     * @since XXX
956
     */
957 23
    protected function detectLoop(Item $parent, Item $child)
958
    {
959 23
        if ($child->name === $parent->name) {
960 2
            return true;
961
        }
962 23
        foreach ($this->getChildren($child->name) as $grandchild) {
963 19
            if ($this->detectLoop($parent, $grandchild)) {
964 2
                return true;
965
            }
966 23
        }
967 23
        return false;
968
    }
969
970
    /**
971
     * @param string $itemName item name
972
     * @return array
973
     * @since XXX
974
     */
975 1
    public function getParents($itemName)
976
    {
977 1
        $itemGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $itemName]);
978 1
        return $this->getParentsGuid($itemGuid);
979
    }
980
981 1
    protected function getParentsGuid($itemGuid)
982
    {
983 1
        $parentsGuid = $this->db->executeCommand('SMEMBERS', [$this->getItemParentsKey($itemGuid)]);
984 1
        $parents = [];
985 1
        if (count($parentsGuid) > 0) {
986 1
            array_unshift($parentsGuid, $this->getItemMappingGuidKey());
987 1
            $parents = $this->db->executeCommand('HMGET', $parentsGuid);
988 1
        }
989 1
        return $parents;
990
    }
991
992
    /**
993
     * @inheritdoc
994
     */
995 3
    public function getAssignments($userId)
996
    {
997 3
        $roleGuids = $this->db->executeCommand('ZRANGEBYSCORE', [$this->getUserAssignmentsKey($userId), '-inf', '+inf', 'WITHSCORES']);
998 3
        $assignments = [];
999 3
        if (count($roleGuids) > 0) {
1000 3
            $guids = [];
1001 3
            $dates = [];
1002 3
            $nbRolesGuids = count($roleGuids);
1003 3
            for($i=0; $i < $nbRolesGuids; $i = $i + 2) {
1004 3
                $guids[] = $roleGuids[$i];
1005 3
                $dates[] = $roleGuids[($i + 1)];
1006 3
            }
1007 3
            array_unshift($guids, $this->getItemMappingGuidKey());
1008 3
            $names = $this->db->executeCommand('HMGET', $guids);
1009 3
            foreach ($names as $i => $name) {
1010 3
                $assignments[$name] = new Assignment([
1011 3
                    'userId' => $userId,
1012 3
                    'roleName' => $name,
1013 3
                    'createdAt' => $dates[$i],
1014 3
                ]);
1015 3
            }
1016 3
        }
1017
1018 3
        return $assignments;
1019
1020
    }
1021
1022
    /**
1023
     * @inheritdoc
1024
     */
1025
    public function getAssignment($roleName, $userId)
1026
    {
1027
        $roleGuid = $this->db->executeCommand('HGET', [$this->getItemMappingKey(), $roleName]);
1028
        $assignment = null;
1029
        if ($roleGuid !== null) {
1030
            $assignmentScore = $this->db->executeCommand('ZSCORE', [$this->getUserAssignmentsKey($userId), $roleGuid]);
1031
            if ($assignmentScore !== null) {
1032
                $assignment = new Assignment([
1033
                    'userId' => $userId,
1034
                    'roleName' => $roleName,
1035
                    'createdAt' => $assignmentScore,
1036
                ]);
1037
            }
1038
        }
1039
        return $assignment;
1040
    }
1041
1042
    /**
1043
     * @inheritdoc
1044
     */
1045 2
    public function checkAccess($userId, $permissionName, $params = [])
1046
    {
1047 2
        $assignments = $this->getAssignments($userId);
1048 2
        return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
1049
    }
1050
1051
1052
    /**
1053
     * @param Item $parent parent item
1054
     * @param Item $child child item
1055
     * @return bool
1056
     * @since XXX
1057
     */
1058 1
    public function canAddChild(Item $parent, Item $child)
1059
    {
1060 1
        return !$this->detectLoop($parent, $child);
1061
    }
1062
1063
    /**
1064
     * @param string|integer $user user ID
1065
     * @param string $itemName current item name
1066
     * @param array $params parameters applied in rule
1067
     * @param Assignment[] $assignments
1068
     * @return bool
1069
     * @since XXX
1070
     * @throws \yii\base\InvalidConfigException
1071
     */
1072 2
    protected function checkAccessRecursive($user, $itemName, $params, $assignments)
1073
    {
1074 2
        if (($item = $this->getItem($itemName)) === null) {
1075 1
            return false;
1076
        }
1077 2
        Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__);
1078 2
        if (!$this->executeRule($user, $item, $params)) {
1079 2
            return false;
1080
        }
1081 2
        if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) {
1082 2
            return true;
1083
        }
1084 1
        $parents = $this->getParents($itemName);
1085 1
        foreach ($parents as $parent) {
1086 1
            if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) {
1087 1
                return true;
1088
            }
1089 1
        }
1090 1
        return false;
1091
    }
1092
1093
    /**
1094
     * @param array $dataRow
1095
     * @return Permission|Role
1096
     * @since XXX
1097
     */
1098 24
    protected function populateItem($dataRow)
1099
    {
1100 24
        $class = $dataRow['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className();
1101 24
        if (!isset($dataRow['data']) || ($data = @unserialize($dataRow['data'])) === false) {
1102
            $data = null;
1103
        }
1104
1105 24
        return new $class([
1106 24
            'name' => $dataRow['name'],
1107 24
            'type' => $dataRow['type'],
1108 24
            'description' => isset($dataRow['description']) ? $dataRow['description'] : null,
1109 24
            'ruleName' => isset($dataRow['ruleName']) ? $dataRow['ruleName'] : null,
1110 24
            'data' => $data,
1111 24
            'createdAt' => $dataRow['createdAt'],
1112 24
            'updatedAt' => $dataRow['updatedAt'],
1113 24
        ]);
1114
    }
1115
}
1116