Completed
Push — devel ( c872d4...3f535d )
by Philippe
02:47 queued 50s
created

Manager::getRules()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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