Completed
Push — devel ( da4c7c...318476 )
by Philippe
02:00
created

Manager::getUserAssignmentsKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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