Completed
Push — devel ( a8277a...da4c7c )
by Philippe
02:01
created

Manager::getRuleGuid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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