Completed
Push — devel ( 318476...71a152 )
by Philippe
02:08
created

Manager::remap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

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