• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

php-casbin / php-casbin / 11444189732

21 Oct 2024 04:08PM UTC coverage: 94.006% (+0.2%) from 93.837%
11444189732

push

github

web-flow
Merge pull request #167 from php-casbin/4.x

BREAKING CHANGE: Upgrade the minimum PHP version to 8.0.

716 of 758 new or added lines in 23 files covered. (94.46%)

10 existing lines in 4 files now uncovered.

1976 of 2102 relevant lines covered (94.01%)

134.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/src/Model/Policy.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Casbin\Model;
6

7
use ArrayAccess;
8
use Casbin\Constant\Constants;
9
use Casbin\Exceptions\CasbinException;
10
use Casbin\Log\Logger;
11
use Casbin\Rbac\ConditionalRoleManager;
12
use Casbin\Rbac\RoleManager;
13
use Casbin\Util\Util;
14

15
/**
16
 * Class Policy.
17
 *
18
 * @package Casbin\Model
19
 * @implements ArrayAccess<string, array<string, Assertion>>
20
 * @author techlee@qq.com
21
 */
22
abstract class Policy implements ArrayAccess
23
{
24
    public const POLICY_ADD = 0;
25

26
    public const POLICY_REMOVE = 1;
27

28
    const DEFAULT_SEP = ",";
29

30
    /**
31
     * All of the Model items.
32
     *
33
     * @var array<string, array<string, Assertion>>
34
     */
35
    protected array $items = [];
36

37
    /**
38
     * $logger.
39
     *
40
     * @var Logger|null
41
     */
42
    protected ?Logger $logger = null;
43

44
    /**
45
     * BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
46
     *
47
     * @param RoleManager[] $rmMap
48
     * @param integer $op
49
     * @param string $sec
50
     * @param string $ptype
51
     * @param string[][] $rules
52
     * @return void
53
     */
54
    public function buildIncrementalRoleLinks(array $rmMap, int $op, string $sec, string $ptype, array $rules): void
55
    {
56
        if ($sec == "g" && isset($rmMap[$ptype]) && isset($this->items[$sec][$ptype])) {
84✔
57
            $this->items[$sec][$ptype]->buildIncrementalRoleLinks($rmMap[$ptype], $op, $rules);
78✔
58
        }
59
    }
60

61
    /**
62
     * Initializes the roles in RBAC.
63
     *
64
     * @param RoleManager[] $rmMap
65
     * @throws CasbinException
66
     */
67
    public function buildRoleLinks(array $rmMap): void
68
    {
69
        $this->printPolicy();
486✔
70
        if (!isset($this->items['g'])) {
486✔
UNCOV
71
            return;
×
72
        }
73

74
        foreach ($this->items['g'] as $ptype => $ast) {
486✔
75
            if (isset($rmMap[$ptype])) {
486✔
76
                $rm = $rmMap[$ptype];
486✔
77
                $ast->buildRoleLinks($rm);
486✔
78
            }
79
        }
80
    }
81

82
    /**
83
     * BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations.
84
     *
85
     * @param ConditionalRoleManager[] $condRmMap
86
     * @param integer $op
87
     * @param string $sec
88
     * @param string $ptype
89
     * @param string[][] $rules
90
     * @return void
91
     */
92
    public function buildIncrementalConditionalRoleLinks(array $condRmMap, int $op, string $sec, string $ptype, array $rules): void
93
    {
94
        if ($sec == "g" && isset($condRmMap[$ptype]) && isset($this->items[$sec][$ptype])) {
18✔
95
            $this->items[$sec][$ptype]->buildIncrementalConditionalRoleLinks($condRmMap[$ptype], $op, $rules);
6✔
96
        }
97
    }
98

99
    /**
100
     * Initializes the roles in RBAC with conditions.
101
     *
102
     * @param ConditionalRoleManager[] $condRmMap
103
     * @throws CasbinException
104
     */
105
    public function buildConditionalRoleLinks(array $condRmMap): void
106
    {
107
        $this->printPolicy();
18✔
108
        if (!isset($this->items['g'])) {
18✔
NEW
109
            return;
×
110
        }
111

112
        foreach ($this->items['g'] as $ptype => $ast) {
18✔
113
            if (isset($condRmMap[$ptype])) {
18✔
114
                $rm = $condRmMap[$ptype];
18✔
115
                $ast->buildConditionalRoleLinks($rm);
18✔
116
            }
117
        }
118
    }
119

120
    /**
121
     * Prints the policy to log.
122
     */
123
    public function printPolicy(): void
124
    {
125
        if (!$this->getLogger()->isEnabled()) {
498✔
126
            return;
498✔
127
        }
128

129
        $policy = [];
6✔
130
        foreach (['p', 'g'] as $sec) {
6✔
131
            if (!isset($this->items[$sec])) {
6✔
NEW
132
                continue;
×
133
            }
134

135
            foreach ($this->items[$sec] as $ptype => $ast) {
6✔
136
                $policy[$ptype] = array_merge(
6✔
137
                    $policy[$ptype] ?? [],
6✔
138
                    $ast->policy
6✔
139
                );
6✔
140
            }
141
        }
142

143
        $this->getLogger()->logPolicy($policy);
6✔
144
    }
145

146
    /**
147
     * Clears all current policy.
148
     */
149
    public function clearPolicy(): void
150
    {
151
        foreach (['p', 'g'] as $sec) {
750✔
152
            if (!isset($this->items[$sec])) {
750✔
153
                return;
246✔
154
            }
155

156
            foreach ($this->items[$sec] as $key => $ast) {
750✔
157
                $this->items[$sec][$key]->policy = [];
750✔
158
                $this->items[$sec][$key]->policyMap = [];
750✔
159
            }
160
        }
161
    }
162

163
    /**
164
     * Gets all rules in a policy.
165
     *
166
     * @param string $sec
167
     * @param string $ptype
168
     *
169
     * @return string[][]
170
     */
171
    public function getPolicy(string $sec, string $ptype): array
172
    {
173
        return $this->items[$sec][$ptype]->policy;
48✔
174
    }
175

176
    /**
177
     * Gets rules based on field filters from a policy.
178
     *
179
     * @param string $sec
180
     * @param string $ptype
181
     * @param int $fieldIndex
182
     * @param string ...$fieldValues
183
     *
184
     * @return string[][]
185
     */
186
    public function getFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): array
187
    {
188
        $res = [];
42✔
189

190
        foreach ($this->items[$sec][$ptype]->policy as $rule) {
42✔
191
            $matched = true;
42✔
192
            foreach ($fieldValues as $i => $fieldValue) {
42✔
193
                if ('' != $fieldValue && $rule[$fieldIndex + intval($i)] != $fieldValue) {
42✔
194
                    $matched = false;
42✔
195

196
                    break;
42✔
197
                }
198
            }
199

200
            if ($matched) {
42✔
201
                $res[] = $rule;
42✔
202
            }
203
        }
204

205
        return $res;
42✔
206
    }
207

208
    /**
209
     * Determines whether a model has the specified policy rule.
210
     *
211
     * @param string $sec
212
     * @param string $ptype
213
     * @param string[] $rule
214
     *
215
     * @return bool
216
     */
217
    public function hasPolicy(string $sec, string $ptype, array $rule): bool
218
    {
219
        if (!isset($this->items[$sec][$ptype])) {
234✔
220
            return false;
×
221
        }
222

223
        return isset($this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $rule)]);
234✔
224
    }
225

226
    /**
227
     * Determines whether a model has any of the specified policies. If one is found we return true.
228
     *
229
     * @param string $sec
230
     * @param string $ptype
231
     * @param string[][] $rules
232
     *
233
     * @return bool
234
     */
235
    public function hasPolicies(string $sec, string $ptype, array $rules): bool
236
    {
237
        foreach ($rules as $rule) {
102✔
238
            if ($this->hasPolicy($sec, $ptype, $rule)) {
102✔
239
                return true;
48✔
240
            }
241
        }
242

243
        return false;
84✔
244
    }
245

246
    /**
247
     * Adds a policy rule to the model.
248
     *
249
     * @param string $sec
250
     * @param string $ptype
251
     * @param string[] $rule
252
     */
253
    public function addPolicy(string $sec, string $ptype, array $rule): void
254
    {
255
        $assertion = &$this->items[$sec][$ptype];
204✔
256
        $assertion->policy[] = $rule;
204✔
257
        $assertion->policyMap[implode(self::DEFAULT_SEP, $rule)] = count($this->items[$sec][$ptype]->policy) - 1;
204✔
258

259
        $hasPriority = isset($assertion->fieldIndexMap[Constants::PRIORITY_INDEX]);
204✔
260
        if ($sec == 'p' && $hasPriority) {
204✔
261
            $idxInsert = $rule[$assertion->fieldIndexMap[Constants::PRIORITY_INDEX]];
12✔
262
            for ($i = count($assertion->policy) - 1; $i > 0; $i--) {
12✔
263
                $idx = $assertion->policy[$i - 1][$assertion->fieldIndexMap[Constants::PRIORITY_INDEX]];
12✔
264
                if ($idx > $idxInsert) {
12✔
265
                    $assertion->policy[$i] = $assertion->policy[$i - 1];
12✔
266
                    $assertion->policyMap[implode(self::DEFAULT_SEP, $assertion->policy[$i - 1])]++;
12✔
267
                } else {
268
                    break;
12✔
269
                }
270
            }
271
            $assertion->policy[$i] = $rule;
12✔
272
            $assertion->policyMap[implode(self::DEFAULT_SEP, $rule)] = $i;
12✔
273
        }
274
    }
275

276
    /**
277
     * Adds a policy rules to the model.
278
     *
279
     * @param string $sec
280
     * @param string $ptype
281
     * @param string[][] $rules
282
     */
283
    public function addPolicies(string $sec, string $ptype, array $rules): void
284
    {
285
        $this->addPoliciesWithAffected($sec, $ptype, $rules);
78✔
286
    }
287

288
    /**
289
     * Adds policy rules to the model, and returns affected rules.
290
     * 
291
     * @param string $sec
292
     * @param string $ptype
293
     * @param string[][] $rules
294
     * 
295
     * @return string[][]
296
     */
297
    public function addPoliciesWithAffected(string $sec, string $ptype, array $rules): array
298
    {
299
        $affected = [];
78✔
300

301
        foreach ($rules as $rule) {
78✔
302
            $hashKey = implode(self::DEFAULT_SEP, $rule);
78✔
303
            if (isset($this->items[$sec][$ptype]->policyMap[$hashKey])) {
78✔
304
                continue;
24✔
305
            }
306

307
            $affected[] = $rule;
78✔
308
            $this->addPolicy($sec, $ptype, $rule);
78✔
309
        }
310

311
        return $affected;
78✔
312
    }
313

314
    /**
315
     * Updates a policy rule from the model.
316
     *
317
     * @param string $sec
318
     * @param string $ptype
319
     * @param string[] $oldRule
320
     * @param string[] $newRule
321
     *
322
     * @return bool
323
     */
324
    public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newRule): bool
325
    {
326
        $oldPolicy = implode(self::DEFAULT_SEP, $oldRule);
12✔
327
        if (!isset($this->items[$sec][$ptype]->policyMap[$oldPolicy])) {
12✔
328
            return false;
×
329
        }
330

331
        $index = $this->items[$sec][$ptype]->policyMap[$oldPolicy];
12✔
332
        $this->items[$sec][$ptype]->policy[$index] = $newRule;
12✔
333
        unset($this->items[$sec][$ptype]->policyMap[$oldPolicy]);
12✔
334
        $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $newRule)] = $index;
12✔
335

336
        return true;
12✔
337
    }
338

339
    /**
340
     * UpdatePolicies updates a policy rule from the model.
341
     *
342
     * @param string $sec
343
     * @param string $ptype
344
     * @param string[][] $oldRules
345
     * @param string[][] $newRules
346
     * @return boolean
347
     */
348
    public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): bool
349
    {
350
        $modifiedRuleIndex = [];
12✔
351

352
        $newIndex = 0;
12✔
353
        foreach ($oldRules as $oldIndex => $oldRule) {
12✔
354
            $oldPolicy = implode(self::DEFAULT_SEP, $oldRule);
12✔
355
            $index = $this->items[$sec][$ptype]->policyMap[$oldPolicy] ?? null;
12✔
356
            if (is_null($index)) {
12✔
357
                // rollback
358
                foreach ($modifiedRuleIndex as $index => $oldNewIndex) {
12✔
359
                    $this->items[$sec][$ptype]->policy[$index] = $oldRules[$oldNewIndex[0]];
6✔
360
                    $oldPolicy = implode(self::DEFAULT_SEP, $oldRules[$oldNewIndex[0]]);
6✔
361
                    $newPolicy = implode(self::DEFAULT_SEP, $newRules[$oldNewIndex[1]]);
6✔
362
                    unset($this->items[$sec][$ptype]->policyMap[$newPolicy]);
6✔
363
                    $this->items[$sec][$ptype]->policyMap[$oldPolicy] = $index;
6✔
364
                }
365
                return false;
12✔
366
            }
367

368
            $this->items[$sec][$ptype]->policy[$index] = $newRules[$newIndex];
12✔
369
            unset($this->items[$sec][$ptype]->policyMap[$oldPolicy]);
12✔
370
            $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $newRules[$newIndex])] = $index;
12✔
371
            $modifiedRuleIndex[$index] = [$oldIndex, $newIndex];
12✔
372
            $newIndex++;
12✔
373
        }
374

375
        return true;
12✔
376
    }
377

378
    /**
379
     * Removes a policy rule from the model.
380
     *
381
     * @param string $sec
382
     * @param string $ptype
383
     * @param array $rule
384
     *
385
     * @return bool
386
     */
387
    public function removePolicy(string $sec, string $ptype, array $rule): bool
388
    {
389
        if (!isset($this->items[$sec][$ptype])) {
96✔
390
            return false;
×
391
        }
392

393
        $hashKey = implode(self::DEFAULT_SEP, $rule);
96✔
394
        if (!isset($this->items[$sec][$ptype]->policyMap[$hashKey])) {
96✔
395
            return false;
24✔
396
        }
397

398
        $index = $this->items[$sec][$ptype]->policyMap[$hashKey];
96✔
399
        array_splice($this->items[$sec][$ptype]->policy, $index, 1);
96✔
400

401
        unset($this->items[$sec][$ptype]->policyMap[$hashKey]);
96✔
402

403
        $count = count($this->items[$sec][$ptype]->policy);
96✔
404
        for ($i = $index; $i < $count; $i++) {
96✔
405
            $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $this->items[$sec][$ptype]->policy[$i])] = $i;
72✔
406
        }
407

408
        return true;
96✔
409
    }
410

411
    /**
412
     * Removes a policy rules from the model.
413
     *
414
     * @param string $sec
415
     * @param string $ptype
416
     * @param string[][] $rules
417
     *
418
     * @return bool
419
     */
420
    public function removePolicies(string $sec, string $ptype, array $rules): bool
421
    {
422
        if (!isset($this->items[$sec][$ptype])) {
48✔
423
            return false;
×
424
        }
425

426
        foreach ($rules as $rule) {
48✔
427
            $this->removePolicy($sec, $ptype, $rule);
48✔
428
        }
429

430
        return true;
48✔
431
    }
432

433
    /**
434
     * Removes policy rules based on field filters from the model.
435
     *
436
     * @param string $sec
437
     * @param string $ptype
438
     * @param int $fieldIndex
439
     * @param string ...$fieldValues
440
     *
441
     * If more than one rule is removed, return the removed rule array, otherwise return false
442
     * @return string[][]|false
443
     */
444
    public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues)
445
    {
446
        $tmp = [];
54✔
447
        $effects = [];
54✔
448
        $res = false;
54✔
449

450
        if (!isset($this->items[$sec][$ptype])) {
54✔
451
            return $res;
6✔
452
        }
453

454
        $this->items[$sec][$ptype]->policyMap = [];
54✔
455

456
        foreach ($this->items[$sec][$ptype]->policy as $index => $rule) {
54✔
457
            $matched = true;
54✔
458
            foreach ($fieldValues as $i => $fieldValue) {
54✔
459
                if ('' != $fieldValue && $rule[$fieldIndex + intval($i)] != $fieldValue) {
54✔
460
                    $matched = false;
42✔
461
                    break;
42✔
462
                }
463
            }
464

465
            if ($matched) {
54✔
466
                $effects[] = $rule;
54✔
467
            } else {
468
                $tmp[] = $rule;
42✔
469
                $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $rule)] = count($tmp) - 1;
42✔
470
            }
471
        }
472

473
        if (count($tmp) != count($this->items[$sec][$ptype]->policy)) {
54✔
474
            $this->items[$sec][$ptype]->policy = $tmp;
54✔
475
            $res = true;
54✔
476
        }
477

478
        return $res ? $effects : false;
54✔
479
    }
480

481
    /**
482
     * Gets all values for a field for all rules in a policy, duplicated values are removed.
483
     *
484
     * @param string $sec
485
     * @param string $ptype
486
     * @param int $fieldIndex
487
     *
488
     * @return string[]
489
     */
490
    public function getValuesForFieldInPolicy(string $sec, string $ptype, int $fieldIndex): array
491
    {
492
        $values = [];
54✔
493

494
        if (!isset($this->items[$sec][$ptype])) {
54✔
495
            return $values;
12✔
496
        }
497

498
        foreach ($this->items[$sec][$ptype]->policy as $rule) {
54✔
499
            $values[] = $rule[$fieldIndex];
54✔
500
        }
501

502
        Util::arrayRemoveDuplicates($values);
54✔
503

504
        return $values;
54✔
505
    }
506

507
    /**
508
     * Gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
509
     *
510
     * @param string $sec
511
     * @param int $fieldIndex
512
     *
513
     * @return string[]
514
     */
515
    public function getValuesForFieldInPolicyAllTypes(string $sec, int $fieldIndex): array
516
    {
517
        $values = [];
24✔
518

519
        foreach ($this->items[$sec] as $key => $ptype) {
24✔
520
            $values = array_merge($values, $this->getValuesForFieldInPolicy($sec, $key, $fieldIndex));
24✔
521
        }
522

523
        Util::arrayRemoveDuplicates($values);
24✔
524

525
        return $values;
24✔
526
    }
527

528
    /**
529
     * Gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
530
     *
531
     * @param string $sec
532
     * @param string $field
533
     * 
534
     * @return array<string>
535
     * @throws CasbinException
536
     */
537
    public function getValuesForFieldInPolicyAllTypesByName(string $sec, string $field): array
538
    {
539
        $values = [];
18✔
540

541
        foreach ($this->items[$sec] as $ptype => $rules) {
18✔
542
            $index = $this->getFieldIndex($ptype, $field);
18✔
543
            $v = $this->getValuesForFieldInPolicy($sec, $ptype, $index);
18✔
544

545
            $values = array_merge($values, $v);
18✔
546
        }
547

548
        Util::arrayRemoveDuplicates($values);
18✔
549

550
        return $values;
18✔
551
    }
552

553
    /**
554
     * Gets the index for a given ptype and field.
555
     *
556
     * @param string $ptype
557
     * @param string $field
558
     * 
559
     * @return int $fieldIndex
560
     * @throws CasbinException
561
     */
562
    public function getFieldIndex(string $ptype, string $field): int
563
    {
564
        $assertion = &$this->items['p'][$ptype];
690✔
565
        if (isset($assertion->fieldIndexMap[$field])) {
690✔
566
            return $assertion->fieldIndexMap[$field];
78✔
567
        }
568
        $pattern = $ptype . '_' . $field;
690✔
569
        $index = -1;
690✔
570
        foreach ($assertion->tokens as $i => $token) {
690✔
571
            if ($token == $pattern) {
690✔
572
                $index = $i;
132✔
573
                break;
132✔
574
            }
575
        }
576
        if ($index == -1) {
690✔
577
            throw new CasbinException($field . ' index is not set, please use enforcer.SetFieldIndex() to set index');
684✔
578
        }
579
        $assertion->fieldIndexMap[$field] = $index;
132✔
580
        return $index;
132✔
581
    }
582

583
    /**
584
     * Sets the index for a given ptype and field.
585
     *
586
     * @param string $ptype
587
     * @param string $field
588
     * @param int $index
589
     */
590
    public function setFieldIndex(string $ptype, string $field, int $index): void
591
    {
592
        $assertion = &$this->items['p'][$ptype];
6✔
593
        $assertion->fieldIndexMap[$field] = $index;
6✔
594
    }
595

596
    /**
597
     * Sets the current logger.
598
     *
599
     * @param Logger $logger
600
     *
601
     * @return void
602
     */
603
    public function setLogger(Logger $logger): void
604
    {
605
        foreach ($this->items as $sec => $astMap) {
822✔
606
            foreach ($astMap as $ast) {
750✔
607
                $ast->setLogger($logger);
750✔
608
            }
609
        }
610

611
        $this->logger = $logger;
822✔
612
    }
613

614
    /**
615
     * Returns the current logger.
616
     *
617
     * @return Logger
618
     */
619
    public function getLogger(): Logger
620
    {
621
        return $this->logger;
750✔
622
    }
623

624
    /**
625
     * Determine if the given Model option exists.
626
     *
627
     * @param string $offset
628
     *
629
     * @return bool
630
     */
631
    public function offsetExists($offset): bool
632
    {
633
        return isset($this->items[$offset]);
768✔
634
    }
635

636
    /**
637
     * Get a Model option.
638
     *
639
     * @param string $offset
640
     *
641
     * @return array<string, Assertion>|null
642
     */
643
    public function offsetGet($offset): ?array
644
    {
645
        return $this->items[$offset] ?? null;
762✔
646
    }
647

648
    /**
649
     * Set a Model option.
650
     *
651
     * @param string $offset
652
     * @param array<string, Assertion> $value
653
     */
654
    public function offsetSet($offset, $value): void
655
    {
656
        $this->items[$offset] = $value;
684✔
657
    }
658

659
    /**
660
     * Unset a Model option.
661
     *
662
     * @param string $offset
663
     */
664
    public function offsetUnset($offset): void
665
    {
666
        unset($this->items[$offset]);
×
667
    }
668
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc