• 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

86.45
/src/CoreEnforcer.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Casbin;
6

7
use Casbin\Effector\DefaultEffector;
8
use Casbin\Effector\Effector;
9
use Casbin\Exceptions\CasbinException;
10
use Casbin\Exceptions\EvalFunctionException;
11
use Casbin\Exceptions\InvalidFilePathException;
12
use Casbin\Log\Logger;
13
use Casbin\Log\Logger\DefaultLogger;
14
use Casbin\Model\FunctionMap;
15
use Casbin\Model\Model;
16
use Casbin\Persist\Adapter;
17
use Casbin\Persist\Adapters\FileAdapter;
18
use Casbin\Persist\FilteredAdapter;
19
use Casbin\Persist\Watcher;
20
use Casbin\Persist\WatcherEx;
21
use Casbin\Rbac\DefaultRoleManager\ConditionalDomainManager as DefaultConditionalDomainManager;
22
use Casbin\Rbac\DefaultRoleManager\ConditionalRoleManager as DefaultConditionalRoleManager;
23
use Casbin\Rbac\DefaultRoleManager\RoleManager as DefaultRoleManager;
24
use Casbin\Rbac\DefaultRoleManager\DomainManager as DefaultDomainManager;
25
use Casbin\Rbac\ConditionalRoleManager;
26
use Casbin\Rbac\RoleManager;
27
use Casbin\Util\BuiltinOperations;
28
use Casbin\Util\Util;
29
use Closure;
30
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
31

32
/**
33
 * Class CoreEnforcer
34
 * The main interface for authorization enforcement and policy management.
35
 *
36
 * @author techlee@qq.com
37
 */
38
class CoreEnforcer
39
{
40
    /**
41
     * Model path.
42
     *
43
     * @var string
44
     */
45
    protected string $modelPath;
46

47
    /**
48
     * Model.
49
     *
50
     * @var Model
51
     */
52
    protected Model $model;
53

54
    /**
55
     * FunctionMap.
56
     *
57
     * @var FunctionMap
58
     */
59
    protected FunctionMap $fm;
60

61
    /**
62
     * Effector.
63
     *
64
     * @var Effector
65
     */
66
    protected Effector $eft;
67

68
    /**
69
     * Adapter.
70
     *
71
     * @var Adapter|null
72
     */
73
    protected ?Adapter $adapter;
74

75
    /**
76
     * Watcher.
77
     *
78
     * @var Watcher|null
79
     */
80
    protected ?Watcher $watcher;
81

82
    /**
83
     * RmMap.
84
     *
85
     * @var array<string, RoleManager>
86
     */
87
    protected array $rmMap;
88

89
    /**
90
     * CondRmMap.
91
     * 
92
     * @var array<string, ConditionalRoleManager>
93
     */
94
    protected array $condRmMap;
95

96
    /**
97
     * $enabled.
98
     *
99
     * @var bool
100
     */
101
    protected bool $enabled;
102

103
    /**
104
     * $autoSave.
105
     *
106
     * @var bool
107
     */
108
    protected bool $autoSave;
109

110
    /**
111
     * $autoBuildRoleLinks.
112
     *
113
     * @var bool
114
     */
115
    protected bool $autoBuildRoleLinks;
116

117
    /**
118
     * $autoNotifyWatcher.
119
     *
120
     * @var bool
121
     */
122
    protected bool $autoNotifyWatcher;
123

124
    /**
125
     * $logger.
126
     *
127
     * @var Logger
128
     */
129
    protected Logger $logger;
130

131
    /**
132
     * Enforcer constructor.
133
     * Creates an enforcer via file or DB.
134
     * File:
135
     * $e = new Enforcer("path/to/basic_model.conf", "path/to/basic_policy.csv")
136
     * MySQL DB:
137
     * $a = DatabaseAdapter::newAdapter([
138
     *      'type'     => 'mysql', // mysql,pgsql,sqlite,sqlsrv
139
     *      'hostname' => '127.0.0.1',
140
     *      'database' => 'test',
141
     *      'username' => 'root',
142
     *      'password' => '123456',
143
     *      'hostport' => '3306',
144
     *  ]);
145
     * $e = new Enforcer("path/to/basic_model.conf", $a).
146
     *
147
     * @param string|Model|null $model
148
     * @param string|Adapter|null $adapter
149
     * @param Logger|null $logger
150
     * @param bool|null $enableLog
151
     *
152
     * @throws CasbinException
153
     */
154
    public function __construct(string|Model|null $model = null, string|Adapter|null $adapter = null, ?Logger $logger = null, ?bool $enableLog = null)
155
    {
156
        $this->logger = $logger ?? new DefaultLogger();
756✔
157

158
        if (!is_null($enableLog)) {
756✔
159
            $this->enableLog($enableLog);
12✔
160
        }
161

162
        if (is_null($model) && is_null($adapter)) {
756✔
163
            return;
12✔
164
        }
165
        if (is_string($model)) {
744✔
166
            if (is_string($adapter) || is_null($adapter)) {
720✔
167
                $this->initWithFile($model, $adapter ?? '');
696✔
168
            } else if ($adapter instanceof Adapter) {
24✔
169
                $this->initWithAdapter($model, $adapter);
488✔
170
            }
171
        } else if ($model instanceof Model) {
24✔
172
            if ($adapter instanceof Adapter || is_null($adapter)) {
24✔
173
                $this->initWithModelAndAdapter($model, $adapter);
24✔
174
            } else {
175
                throw new CasbinException('Invalid parameters for enforcer.');
16✔
176
            }
177
        } else {
UNCOV
178
            throw new CasbinException('Invalid parameters for enforcer.');
×
179
        }
180
    }
181

182
    /**
183
     * Initializes an enforcer with a model file and a policy file.
184
     *
185
     * @param string $modelPath
186
     * @param string $policyPath
187
     *
188
     * @throws CasbinException
189
     */
190
    public function initWithFile(string $modelPath, string $policyPath): void
191
    {
192
        $adapter = new FileAdapter($policyPath);
696✔
193
        $this->initWithAdapter($modelPath, $adapter);
696✔
194
    }
195

196
    /**
197
     * Initializes an enforcer with a database adapter.
198
     *
199
     * @param string $modelPath
200
     * @param Adapter $adapter
201
     *
202
     * @throws CasbinException
203
     */
204
    public function initWithAdapter(string $modelPath, Adapter $adapter): void
205
    {
206
        $m = Model::newModelFromFile($modelPath);
726✔
207
        $this->initWithModelAndAdapter($m, $adapter);
726✔
208

209
        $this->modelPath = $modelPath;
720✔
210
    }
211

212
    /**
213
     * InitWithModelAndAdapter initializes an enforcer with a model and a database adapter.
214
     *
215
     * @param Model $m
216
     * @param Adapter|null $adapter
217
     */
218
    public function initWithModelAndAdapter(Model $m, ?Adapter $adapter): void
219
    {
220
        $this->adapter = $adapter;
750✔
221
        $this->model = $m;
750✔
222
        $this->model->setLogger($this->logger);
750✔
223
        $this->model->printModel();
750✔
224

225
        $this->fm = Model::loadFunctionMap();
750✔
226

227
        $this->initialize();
750✔
228

229
        // Do not initialize the full policy when using a filtered adapter
230
        $ok = $this->adapter instanceof FilteredAdapter ? $this->adapter->isFiltered() : false;
750✔
231

232
        if (!is_null($this->adapter) && !$ok) {
750✔
233
            $this->loadPolicy();
726✔
234
        }
235
    }
236

237
    /**
238
     * Sets the current logger.
239
     *
240
     * @param Logger $logger
241
     */
242
    public function setLogger(Logger $logger): void
243
    {
244
        $this->logger = $logger;
6✔
245
        $this->model->setLogger($this->logger);
6✔
246
        foreach ($this->rmMap as $rm) {
6✔
247
            $rm->setLogger($this->logger);
6✔
248
        }
249
        foreach ($this->condRmMap as $rm) {
6✔
NEW
250
            $rm->setLogger($this->logger);
×
251
        }
252
    }
253

254
    /**
255
     * Initializes an enforcer with a database adapter.
256
     */
257
    protected function initialize(): void
258
    {
259
        $this->rmMap = [];
756✔
260
        $this->condRmMap = [];
756✔
261
        $this->eft = new DefaultEffector();
756✔
262
        $this->watcher = null;
756✔
263

264
        $this->enabled = true;
756✔
265
        $this->autoSave = true;
756✔
266
        $this->autoBuildRoleLinks = true;
756✔
267
        $this->autoNotifyWatcher = true;
756✔
268
        $this->initRmMap();
756✔
269
    }
270

271
    /**
272
     * Reloads the model from the model CONF file.
273
     * Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy().
274
     *
275
     * @throws CasbinException
276
     */
277
    public function loadModel(): void
278
    {
279
        $this->model = Model::newModelFromFile($this->modelPath);
6✔
280
        $this->model->printModel();
6✔
281
        $this->fm = Model::loadFunctionMap();
6✔
282

283
        $this->initialize();
6✔
284
    }
285

286
    /**
287
     * Gets the current model.
288
     *
289
     * @return Model
290
     */
291
    public function getModel(): Model
292
    {
293
        return $this->model;
30✔
294
    }
295

296
    /**
297
     * Sets the current model.
298
     *
299
     * @param Model $model
300
     */
301
    public function setModel(Model $model): void
302
    {
303
        $this->model = $model;
12✔
304
        $this->fm = $this->model->loadFunctionMap();
12✔
305

306
        $this->initialize();
12✔
307
    }
308

309
    /**
310
     * Gets the current adapter.
311
     *
312
     * @return Adapter|null
313
     */
314
    public function getAdapter(): ?Adapter
315
    {
316
        return $this->adapter;
12✔
317
    }
318

319
    /**
320
     * Sets the current adapter.
321
     *
322
     * @param Adapter $adapter
323
     */
324
    public function setAdapter(Adapter $adapter): void
325
    {
326
        $this->adapter = $adapter;
24✔
327
    }
328

329
    /**
330
     * Sets the current watcher.
331
     *
332
     * @param Watcher $watcher
333
     */
334
    public function setWatcher(Watcher $watcher): void
335
    {
336
        $this->watcher = $watcher;
66✔
337
        $this->watcher->setUpdateCallback(function () {
66✔
338
            $this->loadPolicy();
×
339
        });
66✔
340
    }
341

342
    /**
343
     * Gets the current role manager.
344
     *
345
     * @return RoleManager
346
     */
347
    public function getRoleManager(): RoleManager
348
    {
349
        return $this->rmMap['g'];
48✔
350
    }
351

352
    /**
353
     * Gets the current role manager.
354
     *
355
     * @param RoleManager $rm
356
     */
357
    public function setRoleManager(RoleManager $rm): void
358
    {
359
        $this->rmMap['g'] = $rm;
×
360
    }
361

362
    /**
363
     * Sets the current effector.
364
     *
365
     * @param Effector $eft
366
     */
367
    public function setEffector(Effector $eft): void
368
    {
369
        $this->eft = $eft;
×
370
    }
371

372
    /**
373
     * Clears all policy.
374
     */
375
    public function clearPolicy(): void
376
    {
377
        $this->model->clearPolicy();
36✔
378
    }
379

380
    /**
381
     * Reloads the policy from file/database.
382
     */
383
    public function loadPolicy(): void
384
    {
385
        $newModel = $this->loadPolicyFromAdapter($this->model);
744✔
386
        if (!is_null($newModel)) {
738✔
387
            $this->applyModifiedModel($newModel);
690✔
388
        }
389
    }
390

391
    /**
392
     * Loads policy from the current adapter.
393
     *
394
     * @param Model $baseModel
395
     *
396
     * @return Model|null
397
     */
398
    public function loadPolicyFromAdapter(Model $baseModel): ?Model
399
    {
400
        $newModel = clone $baseModel;
744✔
401
        $newModel->clearPolicy();
744✔
402

403
        try {
404
            $this->adapter?->loadPolicy($newModel);
744✔
405
            $newModel->sortPoliciesBySubjectHierarchy();
690✔
406
            $newModel->sortPoliciesByPriority();
690✔
407
        } catch (InvalidFilePathException) {
66✔
408
            return null;
60✔
409
        } catch (\Throwable $e) {
6✔
410
            throw $e;
6✔
411
        }
412

413
        return $newModel;
690✔
414
    }
415

416
    /**
417
     * Applies a modified model to the current enforcer.
418
     *
419
     * @param Model $newModel
420
     */
421
    public function applyModifiedModel(Model $newModel): void
422
    {
423
        $flag = false;
690✔
424
        $needToRebuild = false;
690✔
425

426
        try {
427
            if ($this->autoBuildRoleLinks) {
690✔
428
                $needToRebuild = true;
690✔
429

430
                $this->rebuildRoleLinks($newModel);
690✔
431
                $this->rebuildConditionalRoleLinks($newModel);
690✔
432
            }
433
            $this->model = $newModel;
690✔
UNCOV
434
        } catch (\Throwable $e) {
×
UNCOV
435
            $flag = true;
×
436
            throw $e;
×
437
        } finally {
438
            if ($flag) {
690✔
439
                if ($this->autoBuildRoleLinks && $needToRebuild) {
×
440
                    $this->buildRoleLinks();
690✔
441
                }
442
            }
443
        }
444
    }
445

446
    /**
447
     * Rebuilds the role inheritance relations based on the new model.
448
     *
449
     * @param Model $newModel
450
     */
451
    public function rebuildRoleLinks(Model $newModel): void
452
    {
453
        if (count($this->rmMap) !== 0) {
690✔
454
            foreach ($this->rmMap as $rm) {
486✔
455
                $rm->clear();
486✔
456
            }
457

458
            $newModel->buildRoleLinks($this->rmMap);
486✔
459
        }
460
    }
461

462
    /**
463
     * Rebuilds the conditional role inheritance relations based on the new model.
464
     *
465
     * @param Model $newModel
466
     */
467
    public function rebuildConditionalRoleLinks(Model $newModel): void
468
    {
469
        if (!empty($this->condRmMap)) {
690✔
470
            foreach ($this->condRmMap as $rm) {
18✔
471
                $rm->clear();
18✔
472
            }
473

474
            $newModel->buildConditionalRoleLinks($this->condRmMap);
18✔
475
        }
476
    }
477

478
    /**
479
     * Reloads a filtered policy from file/database.
480
     *
481
     * @param mixed $filter
482
     *
483
     * @throws CasbinException
484
     */
485
    public function _loadFilteredPolicy($filter): void
486
    {
487
        if ($this->adapter instanceof FilteredAdapter) {
12✔
488
            $filteredAdapter = $this->adapter;
12✔
489
            $filteredAdapter->loadFilteredPolicy($this->model, $filter);
12✔
490
        } else {
491
            throw new CasbinException('filtered policies are not supported by this adapter');
×
492
        }
493

494
        $this->model->sortPoliciesBySubjectHierarchy();
12✔
495
        $this->model->sortPoliciesByPriority();
12✔
496
        $this->initRmMap();
12✔
497
        $this->model->printPolicy();
12✔
498
        if ($this->autoBuildRoleLinks) {
12✔
499
            $this->buildRoleLinks();
12✔
500
        }
501
    }
502

503
    /**
504
     * Reloads a filtered policy from file/database.
505
     *
506
     * @param mixed $filter
507
     *
508
     * @throws CasbinException
509
     */
510
    public function loadFilteredPolicy($filter): void
511
    {
512
        $this->model->clearPolicy();
12✔
513

514
        $this->_loadFilteredPolicy($filter);
12✔
515
    }
516

517
    /**
518
     * LoadIncrementalFilteredPolicy append a filtered policy from file/database.
519
     *
520
     * @param mixed $filter
521
     * @return void
522
     */
523
    public function loadIncrementalFilteredPolicy($filter): void
524
    {
525
        $this->_loadFilteredPolicy($filter);
6✔
526
    }
527

528
    /**
529
     * Returns true if the loaded policy has been filtered.
530
     *
531
     * @return bool
532
     */
533
    public function isFiltered(): bool
534
    {
535
        if (!$this->adapter instanceof FilteredAdapter) {
12✔
536
            return false;
6✔
537
        }
538

539
        $filteredAdapter = $this->adapter;
6✔
540

541
        return $filteredAdapter->isFiltered();
6✔
542
    }
543

544
    /**
545
     * Saves the current policy (usually after changed with Casbin API) back to file/database.
546
     *
547
     * @throws CasbinException
548
     */
549
    public function savePolicy(): void
550
    {
551
        if ($this->isFiltered()) {
12✔
552
            throw new CasbinException('cannot save a filtered policy');
6✔
553
        }
554

555
        $this->adapter?->savePolicy($this->model);
6✔
556

557
        if ($this->autoNotifyWatcher) {
6✔
558
            if ($this->watcher instanceof WatcherEx) {
6✔
559
                $this->watcher->updateForSavePolicy($this->model);
×
560
            } else {
561
                $this->watcher?->update();
6✔
562
            }
563
        }
564
    }
565

566
    /**
567
     * initRmMap initializes rmMap.
568
     *
569
     * @return void
570
     */
571
    public function initRmMap(): void
572
    {
573
        if (isset($this->model['g'])) {
756✔
574
            foreach ($this->model['g'] as $ptype => $value) {
522✔
575
                if (isset($this->rmMap[$ptype])) {
522✔
576
                    $rm = $this->rmMap[$ptype];
12✔
577
                    $rm->clear();
12✔
578
                    continue;
12✔
579
                }
580

581
                $tokensCount = count($value->tokens);
522✔
582
                $paramsTokensCount = count($value->paramsTokens);
522✔
583
                if ($tokensCount <= 2) {
522✔
584
                    if ($paramsTokensCount === 0) {
396✔
585
                        $value->rm = new DefaultRoleManager(10);
384✔
586
                        $this->rmMap[$ptype] = $value->rm;
384✔
587
                    } else {
588
                        $value->condRm = new DefaultConditionalRoleManager(10);
12✔
589
                        $this->condRmMap[$ptype] = $value->condRm;
12✔
590
                    }
591
                }
592
                if ($tokensCount > 2) {
522✔
593
                    if ($paramsTokensCount === 0) {
180✔
594
                        $value->rm = new DefaultDomainManager(10);
162✔
595
                        $this->rmMap[$ptype] = $value->rm;
162✔
596
                    } else {
597
                        $value->condRm = new DefaultConditionalDomainManager(10);
18✔
598
                        $this->condRmMap[$ptype] = $value->condRm;
18✔
599
                    }
600
                    $matchFunc = 'keyMatch(r_dom, p_dom)';
180✔
601
                    if (str_contains($this->model['m']['m']->value, $matchFunc)) {
180✔
602
                        $this->addNamedDomainMatchingFunc('g', 'keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2));
12✔
603
                    }
604
                }
605
            }
606
        }
607
    }
608

609
    /**
610
     * Changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function.
611
     *
612
     * @param bool $enabled
613
     */
614
    public function enableEnforce(bool $enabled = true): void
615
    {
616
        $this->enabled = $enabled;
6✔
617
    }
618

619
    /**
620
     * Changes whether Casbin will log messages to the Logger.
621
     *
622
     * @param bool $enabled
623
     */
624
    public function enableLog(bool $enabled = true): void
625
    {
626
        $this->logger->enableLog($enabled);
12✔
627
    }
628

629
    /**
630
     * Controls whether to save a policy rule automatically notify the Watcher when it is added or removed.
631
     *
632
     * @param bool $enabled
633
     */
634
    public function enableAutoNotifyWatcher(bool $enabled = true): void
635
    {
636
        $this->autoNotifyWatcher = $enabled;
×
637
    }
638

639
    /**
640
     * Controls whether to save a policy rule automatically to the adapter when it is added or removed.
641
     *
642
     * @param bool $autoSave
643
     */
644
    public function enableAutoSave(bool $autoSave = true): void
645
    {
646
        $this->autoSave = $autoSave;
6✔
647
    }
648

649
    /**
650
     * Controls whether to rebuild the role inheritance relations when a role is added or deleted.
651
     *
652
     * @param bool $autoBuildRoleLinks
653
     */
654
    public function enableAutoBuildRoleLinks(bool $autoBuildRoleLinks = true): void
655
    {
656
        $this->autoBuildRoleLinks = $autoBuildRoleLinks;
×
657
    }
658

659
    /**
660
     * Manually rebuild the role inheritance relations.
661
     */
662
    public function buildRoleLinks(): void
663
    {
664
        foreach ($this->rmMap as $rm) {
84✔
665
            $rm->clear();
84✔
666
        }
667

668
        $this->model->buildRoleLinks($this->rmMap);
84✔
669
    }
670

671
    /**
672
     * Use a custom matcher to decides whether a "subject" can access a "object" with the operation "action",
673
     * input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
674
     *
675
     * @param string $matcher
676
     * @param array $explains
677
     * @param mixed ...$rvals
678
     *
679
     * @return bool
680
     *
681
     * @throws CasbinException
682
     */
683
    protected function enforcing(string $matcher, &$explains = [], ...$rvals): bool
684
    {
685
        if (!$this->enabled) {
444✔
686
            return true;
6✔
687
        }
688

689
        $functions = $this->fm->getFunctions();
444✔
690

691
        if (isset($this->model['g'])) {
444✔
692
            foreach ($this->model['g'] as $key => $ast) {
222✔
693
                if (!is_null($ast->rm)) {
222✔
694
                    $functions[$key] = BuiltinOperations::generateGFunction($ast->rm);
204✔
695
                }
696
                if (!is_null($ast->condRm)) {
222✔
697
                    $functions[$key] = BuiltinOperations::generateConditionalGFunction($ast->condRm);
24✔
698
                }
699
            }
700
        }
701

702
        if (!isset($this->model['m']['m'])) {
444✔
703
            throw new CasbinException('model is undefined');
×
704
        }
705

706
        $rType = "r";
444✔
707
        $pType = "p";
444✔
708
        $eType = "e";
444✔
709
        $mType = "m";
444✔
710

711
        switch (true) {
712
            case $rvals[0] instanceof EnforceContext:
444✔
713
                $enforceContext = $rvals[0];
6✔
714
                $rType = $enforceContext->rType;
6✔
715
                $pType = $enforceContext->pType;
6✔
716
                $eType = $enforceContext->eType;
6✔
717
                $mType = $enforceContext->mType;
6✔
718
                array_shift($rvals);
6✔
719
                break;
6✔
720
            default:
721
                break;
444✔
722
        }
723

724
        $expString = '';
444✔
725
        if ('' === $matcher) {
444✔
726
            $expString = $this->model['m'][$mType]->value;
444✔
727
        } else {
728
            $expString = Util::removeComments(Util::escapeAssertion($matcher));
×
729
        }
730

731
        $rTokens = array_values($this->model['r'][$rType]->tokens);
444✔
732
        $pTokens = array_values($this->model['p'][$pType]->tokens);
444✔
733

734
        if (count($rTokens) != count($rvals)) {
444✔
NEW
735
            throw new CasbinException(\sprintf('invalid request size: expected %d, got %d', count($rTokens), count($rvals)));
×
736
        }
737
        $rParameters = array_combine($rTokens, $rvals);
444✔
738

739
        if (false == $rParameters) {
444✔
740
            throw new CasbinException('invalid request size');
×
741
        }
742

743
        $expressionLanguage = $this->getExpressionLanguage($functions);
444✔
744
        $expression = "";
444✔
745

746
        $hasEval = Util::hasEval($expString);
444✔
747

748
        if (!$hasEval) {
444✔
749
            $expression = $expressionLanguage->parse($expString, array_merge($rTokens, $pTokens));
432✔
750
        }
751

752
        $policyEffects = [];
444✔
753
        $matcherResults = [];
444✔
754

755
        $effect = 0;
444✔
756
        $explainIndex = 0;
444✔
757

758
        $policyLen = count($this->model['p'][$pType]->policy);
444✔
759
        if (0 != $policyLen && str_contains($expString, $pType . '_')) {
444✔
760
            foreach ($this->model['p'][$pType]->policy as $policyIndex => $pvals) {
408✔
761
                $parameters = array_combine($pTokens, $pvals);
408✔
762
                if (false == $parameters) {
408✔
763
                    throw new CasbinException('invalid policy size');
×
764
                }
765

766
                if ($hasEval) {
408✔
767
                    $ruleNames = Util::getEvalValue($expString);
12✔
768
                    $replacements = [];
12✔
769
                    $pTokens_flipped = array_flip($pTokens);
12✔
770
                    foreach ($ruleNames as $ruleName) {
12✔
771
                        if (isset($pTokens_flipped[$ruleName])) {
12✔
772
                            $rule = Util::escapeAssertion($pvals[$pTokens_flipped[$ruleName]]);
12✔
773
                            $replacements[$ruleName] = $rule;
12✔
774
                        } else {
UNCOV
775
                            throw new CasbinException('please make sure rule exists in policy when using eval() in matcher');
×
776
                        }
777
                    }
778

779
                    $expWithRule = Util::replaceEvalWithMap($expString, $replacements);
12✔
780
                    $expression = $expressionLanguage->parse($expWithRule, array_merge($rTokens, $pTokens));
12✔
781
                }
782

783
                $parameters = array_merge($rParameters, $parameters);
408✔
784
                $result = $expressionLanguage->evaluate($expression, $parameters);
408✔
785

786
                // set to no-match at first
787
                $matcherResults[$policyIndex] = 0;
408✔
788
                if (is_bool($result)) {
408✔
789
                    if ($result) {
408✔
790
                        $matcherResults[$policyIndex] = 1;
404✔
791
                    }
NEW
792
                } elseif (is_float($result)) {
×
793
                    if ($result != 0) {
×
794
                        $matcherResults[$policyIndex] = 1;
×
795
                    }
796
                } else {
797
                    throw new CasbinException('matcher result should be bool, int or float');
×
798
                }
799
                if (isset($parameters[$pType . '_eft'])) {
408✔
800
                    $eft = $parameters[$pType . '_eft'];
78✔
801
                    if ('allow' == $eft) {
78✔
802
                        $policyEffects[$policyIndex] = Effector::ALLOW;
66✔
803
                    } elseif ('deny' == $eft) {
66✔
804
                        $policyEffects[$policyIndex] = Effector::DENY;
54✔
805
                    } else {
806
                        $policyEffects[$policyIndex] = Effector::INDETERMINATE;
56✔
807
                    }
808
                } else {
809
                    $policyEffects[$policyIndex] = Effector::ALLOW;
336✔
810
                }
811

812
                list($effect, $explainIndex) = $this->eft->mergeEffects($this->model['e'][$eType]->value, $policyEffects, $matcherResults, $policyIndex, $policyLen);
408✔
813
                if ($effect != Effector::INDETERMINATE) {
408✔
814
                    break;
384✔
815
                }
816
            }
817
        } else {
818
            if ($hasEval) {
54✔
819
                throw new EvalFunctionException("please make sure rule exists in policy when using eval() in matcher");
6✔
820
            }
821

822
            $matcherResults[0] = 1;
48✔
823

824
            $parameters = $rParameters;
48✔
825
            foreach ($this->model['p'][$pType]->tokens as $token) {
48✔
826
                $parameters[$token] = '';
48✔
827
            }
828

829
            $result = $expressionLanguage->evaluate($expression, $parameters);
48✔
830

831
            if ($result) {
48✔
832
                $policyEffects[0] = Effector::ALLOW;
18✔
833
            } else {
834
                $policyEffects[0] = Effector::INDETERMINATE;
48✔
835
            }
836

837
            list($effect, $explainIndex) = $this->eft->mergeEffects($this->model['e'][$eType]->value, $policyEffects, $matcherResults, 0, 1);
48✔
838
        }
839

840
        if ($explains !== null) {
438✔
841
            if (($explainIndex != -1) && (count($this->model['p'][$pType]->policy) > $explainIndex)) {
438✔
842
                $explains = $this->model['p'][$pType]->policy[$explainIndex];
384✔
843
            }
844
        }
845

846
        $result = $effect == Effector::ALLOW;
438✔
847

848
        $this->logger->logEnforce($matcher, $rvals, $result, $explains);
438✔
849

850
        return $result;
438✔
851
    }
852

853
    /**
854
     * @param array $functions
855
     *
856
     * @return ExpressionLanguage
857
     */
858
    protected function getExpressionLanguage(array $functions): ExpressionLanguage
859
    {
860
        $expressionLanguage = new ExpressionLanguage();
444✔
861
        foreach ($functions as $key => $func) {
444✔
862
            $expressionLanguage->register($key, function (...$args) use ($key) {
444✔
863
                return sprintf($key . '(%1$s)', implode(',', $args));
×
864
            }, function ($arguments, ...$args) use ($func) {
444✔
865
                return $func(...$args);
270✔
866
            });
444✔
867
        }
868

869
        return $expressionLanguage;
444✔
870
    }
871

872
    /**
873
     * @param string $expString
874
     *
875
     * @return string
876
     */
877
    protected function getExpString(string $expString): string
878
    {
879
        return preg_replace_callback(
×
880
            '/([\s\S]*in\s+)\(([\s\S]+)\)([\s\S]*)/',
×
UNCOV
881
            function ($m) {
×
882
                return $m[1] . '[' . $m[2] . ']' . $m[3];
×
883
            },
×
884
            $expString
×
UNCOV
885
        );
×
886
    }
887

888
    /**
889
     * Decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
890
     *
891
     * @param mixed ...$rvals
892
     *
893
     * @return bool
894
     *
895
     * @throws CasbinException
896
     */
897
    public function enforce(...$rvals): bool
898
    {
899
        $explains = [];
348✔
900
        return $this->enforcing('', $explains, ...$rvals);
348✔
901
    }
902

903
    /**
904
     * Use a custom matcher to decides whether a "subject" can access a "object" with the operation "action",
905
     * input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
906
     *
907
     * @param string $matcher
908
     * @param mixed ...$rvals
909
     *
910
     * @return bool
911
     *
912
     * @throws CasbinException
913
     */
914
    public function enforceWithMatcher(string $matcher, ...$rvals): bool
915
    {
916
        $explains = [];
×
917
        return $this->enforcing($matcher, $explains, ...$rvals);
×
918
    }
919

920
    /**
921
     * EnforceEx explain enforcement by informing matched rules
922
     *
923
     * @param mixed ...$rvals
924
     * @return array
925
     */
926
    public function enforceEx(...$rvals)
927
    {
928
        $explain = [];
96✔
929
        $result = $this->enforcing("", $explain, ...$rvals);
96✔
930
        return [$result, $explain];
96✔
931
    }
932

933
    /**
934
     * BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
935
     *
936
     * @param integer $op policy operations.
937
     * @param string $ptype policy type.
938
     * @param string[][] $rules the rules.
939
     * @return void
940
     */
941
    public function buildIncrementalRoleLinks(int $op, string $ptype, array $rules): void
942
    {
943
        $this->model->buildIncrementalRoleLinks($this->rmMap, $op, "g", $ptype, $rules);
84✔
944
    }
945

946
    /**
947
     * BuildIncrementalConditionalRoleLinks provides incremental build the conditional role inheritance relations.
948
     *
949
     * @param integer $op policy operations.
950
     * @param string $ptype policy type.
951
     * @param string[][] $rules the rules.
952
     * @return void
953
     */
954
    public function buildIncrementalConditionalRoleLinks(int $op, string $ptype, array $rules): void
955
    {
956
        $this->model->buildIncrementalConditionalRoleLinks($this->condRmMap, $op, "g", $ptype, $rules);
18✔
957
    }
958

959
    /**
960
     * BatchEnforce enforce in batches
961
     *
962
     * @param string[][] $requests
963
     * @return bool[]
964
     */
965
    public function batchEnforce(array $requests): array
966
    {
967
        return array_map(function (array $request) {
6✔
968
            return  $this->enforce(...$request);
6✔
969
        }, $requests);
6✔
970
    }
971

972
    /**
973
     * BatchEnforceWithMatcher enforce with matcher in batches
974
     *
975
     * @param string $matcher
976
     * @param string[][] $requests
977
     * @return bool[]
978
     */
979
    public function batchEnforceWithMatcher(string $matcher, array $requests): array
980
    {
UNCOV
981
        return array_map(function (array $request) use ($matcher) {
×
982
            return  $this->enforceWithMatcher($matcher, ...$request);
×
983
        }, $requests);
×
984
    }
985

986
    /**
987
     * AddNamedMatchingFunc add MatchingFunc by ptype RoleManager
988
     *
989
     * @param string $ptype
990
     * @param string $name
991
     * @param Closure $fn
992
     * @return boolean
993
     */
994
    public function addNamedMatchingFunc(string $ptype, string $name, Closure $fn): bool
995
    {
996
        if (isset($this->rmMap[$ptype])) {
24✔
997
            $rm = &$this->rmMap[$ptype];
24✔
998
            $rm->addMatchingFunc($name, $fn);
24✔
999
            return true;
24✔
1000
        }
1001
        return false;
×
1002
    }
1003

1004
    /**
1005
     * AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager
1006
     *
1007
     * @param string $ptype
1008
     * @param string $name
1009
     * @param Closure $fn
1010
     * @return boolean
1011
     */
1012
    public function addNamedDomainMatchingFunc(string $ptype, string $name, Closure $fn): bool
1013
    {
1014
        if (isset($this->rmMap[$ptype])) {
24✔
1015
            $rm = &$this->rmMap[$ptype];
24✔
1016
            $rm->addDomainMatchingFunc($name, $fn);
24✔
1017
            return true;
24✔
1018
        }
1019
        return false;
×
1020
    }
1021

1022
    /** 
1023
     * AddNamedLinkConditionFunc Add condition function fn for Link userName->roleName,
1024
     * when fn returns true, Link is valid, otherwise invalid.
1025
     * 
1026
     * @param string $ptype
1027
     * @param string $user
1028
     * @param string $role
1029
     * @param Closure $fn
1030

1031
     * @return boolean
1032
     */
1033
    public function addNamedLinkConditionFunc(string $ptype, string $user, string $role, Closure $fn): bool
1034
    {
1035
        if (isset($this->condRmMap[$ptype])) {
12✔
1036
            $rm = &$this->condRmMap[$ptype];
12✔
1037
            $rm->addLinkConditionFunc($user, $role, $fn);
12✔
1038
            return true;
12✔
1039
        }
NEW
1040
        return false;
×
1041
    }
1042

1043
    /**
1044
     * AddNamedDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},
1045
     * when fn returns true, Link is valid, otherwise invalid.
1046
     * 
1047
     * @param string $ptype
1048
     * @param string $user
1049
     * @param string $role
1050
     * @param string $domain
1051
     * @param Closure $fn
1052
     * 
1053
     * @return boolean
1054
     */
1055
    public function addNamedDomainLinkConditionFunc(string $ptype, string $user, string $role, string $domain, Closure $fn): bool
1056
    {
1057
        if (isset($this->condRmMap[$ptype])) {
18✔
1058
            $rm = &$this->condRmMap[$ptype];
18✔
1059
            $rm->addDomainLinkConditionFunc($user, $role, $domain, $fn);
18✔
1060
            return true;
18✔
1061
        }
NEW
1062
        return false;
×
1063
    }
1064

1065
    /**
1066
     * SetNamedLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName.
1067
     * 
1068
     * @param string $ptype
1069
     * @param string $user
1070
     * @param string $role
1071
     * @param string ...$params
1072
     * 
1073
     * @return boolean
1074
     */
1075
    public function setNamedLinkConditionFuncParams(string $ptype, string $user, string $role, string ...$params): bool
1076
    {
1077
        if (isset($this->condRmMap[$ptype])) {
6✔
1078
            $rm = &$this->condRmMap[$ptype];
6✔
1079
            $rm->setLinkConditionFuncParams($user, $role, ...$params);
6✔
1080
            return true;
6✔
1081
        }
NEW
1082
        return false;
×
1083
    }
1084

1085
    /**
1086
     * SetNamedDomainLinkConditionFuncParams Sets the parameters of the condition function fn 
1087
     * for Link userName->{roleName, domain}.
1088
     * 
1089
     * @param string $ptype
1090
     * @param string $user
1091
     * @param string $role
1092
     * @param string $domain
1093
     * @param string ...$params
1094
     * 
1095
     * @return boolean
1096
     */
1097
    public function setNamedDomainLinkConditionFuncParams(string $ptype, string $user, string $role, string $domain, string ...$params): bool
1098
    {
1099
        if (isset($this->condRmMap[$ptype])) {
6✔
1100
            $rm = &$this->condRmMap[$ptype];
6✔
1101
            $rm->setDomainLinkConditionFuncParams($user, $role, $domain, ...$params);
6✔
1102
            return true;
6✔
1103
        }
NEW
1104
        return false;
×
1105
    }
1106
}
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