1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @author Jared King <[email protected]> |
5
|
|
|
* |
6
|
|
|
* @see http://jaredtking.com |
7
|
|
|
* |
8
|
|
|
* @copyright 2015 Jared King |
9
|
|
|
* @license MIT |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Pulsar; |
13
|
|
|
|
14
|
|
|
use Pulsar\Event\ModelCreating; |
15
|
|
|
use Pulsar\Event\ModelDeleting; |
16
|
|
|
use Pulsar\Event\ModelUpdating; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class ACLModel. |
20
|
|
|
*/ |
21
|
|
|
abstract class ACLModel extends Model |
22
|
|
|
{ |
23
|
|
|
const ERROR_NO_PERMISSION = 'no_permission'; |
24
|
|
|
|
25
|
|
|
const LISTENER_PRIORITY = 1000; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var array |
29
|
|
|
*/ |
30
|
|
|
private $permissionsCache = []; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var bool |
34
|
|
|
*/ |
35
|
|
|
private $permissionsDisabled = false; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Checks if the requesting model has a specific permission |
39
|
|
|
* on this object. |
40
|
|
|
* |
41
|
|
|
* @param string $permission |
42
|
|
|
*/ |
43
|
|
|
public function can($permission, Model $requester): bool |
44
|
|
|
{ |
45
|
|
|
if ($this->permissionsDisabled) { |
46
|
|
|
return true; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
// cache when checking permissions |
50
|
|
|
$k = $permission.'.'.get_class($requester).'.'.$requester->id(); |
51
|
|
|
if (!isset($this->permissionsCache[$k])) { |
52
|
|
|
$this->permissionsCache[$k] = $this->hasPermission($permission, $requester); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
return $this->permissionsCache[$k]; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
abstract protected function hasPermission($permission, Model $requester); |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Disables all permissions checking in can() for this object |
62
|
|
|
* DANGER: this should only be used when objects are mutated from application code |
63
|
|
|
* Granting all permissions to anyone else, i.e. HTTP requests is dangerous. |
64
|
|
|
* |
65
|
|
|
* @return $this |
66
|
|
|
*/ |
67
|
|
|
public function grantAllPermissions() |
68
|
|
|
{ |
69
|
|
|
$this->permissionsDisabled = true; |
70
|
|
|
|
71
|
|
|
return $this; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Ensures that permissions are enforced for this object. |
76
|
|
|
* |
77
|
|
|
* @return $this |
78
|
|
|
*/ |
79
|
|
|
public function enforcePermissions() |
80
|
|
|
{ |
81
|
|
|
$this->permissionsDisabled = false; |
82
|
|
|
|
83
|
|
|
return $this; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
protected function initialize() |
87
|
|
|
{ |
88
|
|
|
parent::initialize(); |
89
|
|
|
|
90
|
|
|
// check the if the requester has the `create` |
91
|
|
|
// permission before creating |
92
|
|
|
static::creating([self::class, 'checkCreatePermission'], self::LISTENER_PRIORITY); |
93
|
|
|
|
94
|
|
|
// check the if the requester has the `edit` |
95
|
|
|
// permission before updating |
96
|
|
|
static::updating([self::class, 'checkUpdatePermission'], self::LISTENER_PRIORITY); |
97
|
|
|
|
98
|
|
|
// check the if the requester has the `delete` |
99
|
|
|
// permission before deleting |
100
|
|
|
static::deleting([self::class, 'checkDeletePermission'], self::LISTENER_PRIORITY); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
View Code Duplication |
public static function checkCreatePermission(ModelCreating $event): void |
|
|
|
|
104
|
|
|
{ |
105
|
|
|
$model = $event->getModel(); |
106
|
|
|
|
107
|
|
|
if (!$model->can('create', ACLModelRequester::get())) { |
108
|
|
|
$model->getErrors()->add(ACLModel::ERROR_NO_PERMISSION); |
109
|
|
|
|
110
|
|
|
$event->stopPropagation(); |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
View Code Duplication |
public static function checkUpdatePermission(ModelUpdating $event): void |
|
|
|
|
115
|
|
|
{ |
116
|
|
|
$model = $event->getModel(); |
117
|
|
|
|
118
|
|
|
if (!$model->can('edit', ACLModelRequester::get())) { |
119
|
|
|
$model->getErrors()->add(ACLModel::ERROR_NO_PERMISSION); |
120
|
|
|
|
121
|
|
|
$event->stopPropagation(); |
122
|
|
|
} |
123
|
|
|
} |
124
|
|
|
|
125
|
|
View Code Duplication |
public static function checkDeletePermission(ModelDeleting $event): void |
|
|
|
|
126
|
|
|
{ |
127
|
|
|
$model = $event->getModel(); |
128
|
|
|
|
129
|
|
|
if (!$model->can('delete', ACLModelRequester::get())) { |
130
|
|
|
$model->getErrors()->add(ACLModel::ERROR_NO_PERMISSION); |
131
|
|
|
|
132
|
|
|
$event->stopPropagation(); |
133
|
|
|
} |
134
|
|
|
} |
135
|
|
|
} |
136
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.