1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @link http://www.yiiframework.com/ |
4
|
|
|
* @copyright Copyright (c) 2008 Yii Software LLC |
5
|
|
|
* @license http://www.yiiframework.com/license/ |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace yii\filters; |
9
|
|
|
|
10
|
|
|
use Closure; |
11
|
|
|
use yii\base\Component; |
12
|
|
|
use yii\base\Action; |
13
|
|
|
use yii\base\InvalidConfigException; |
14
|
|
|
use yii\web\User; |
15
|
|
|
use yii\web\Request; |
16
|
|
|
use yii\base\Controller; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* This class represents an access rule defined by the [[AccessControl]] action filter |
20
|
|
|
* |
21
|
|
|
* @author Qiang Xue <[email protected]> |
22
|
|
|
* @since 2.0 |
23
|
|
|
*/ |
24
|
|
|
class AccessRule extends Component |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* @var bool whether this is an 'allow' rule or 'deny' rule. |
28
|
|
|
*/ |
29
|
|
|
public $allow; |
30
|
|
|
/** |
31
|
|
|
* @var array list of action IDs that this rule applies to. The comparison is case-sensitive. |
32
|
|
|
* If not set or empty, it means this rule applies to all actions. |
33
|
|
|
*/ |
34
|
|
|
public $actions; |
35
|
|
|
/** |
36
|
|
|
* @var array list of the controller IDs that this rule applies to. |
37
|
|
|
* |
38
|
|
|
* The comparison uses [[\yii\base\Controller::uniqueId]], so each controller ID is prefixed |
39
|
|
|
* with the module ID (if any). For a `product` controller in the application, you would specify |
40
|
|
|
* this property like `['product']` and if that controller is located in a `shop` module, this |
41
|
|
|
* would be `['shop/product']`. |
42
|
|
|
* |
43
|
|
|
* The comparison is case-sensitive. |
44
|
|
|
* |
45
|
|
|
* If not set or empty, it means this rule applies to all controllers. |
46
|
|
|
* |
47
|
|
|
* Since version 2.0.12 controller IDs can be specified as wildcards, e.g. `module/*`. |
48
|
|
|
*/ |
49
|
|
|
public $controllers; |
50
|
|
|
/** |
51
|
|
|
* @var array list of roles that this rule applies to (requires properly configured User component). |
52
|
|
|
* Two special roles are recognized, and they are checked via [[User::isGuest]]: |
53
|
|
|
* |
54
|
|
|
* - `?`: matches a guest user (not authenticated yet) |
55
|
|
|
* - `@`: matches an authenticated user |
56
|
|
|
* |
57
|
|
|
* If you are using RBAC (Role-Based Access Control), you may also specify role or permission names. |
58
|
|
|
* In this case, [[User::can()]] will be called to check access. |
59
|
|
|
* |
60
|
|
|
* If this property is not set or empty, it means this rule applies to all roles. |
61
|
|
|
* @see $roleParams |
62
|
|
|
*/ |
63
|
|
|
public $roles; |
64
|
|
|
/** |
65
|
|
|
* @var array|Closure parameters to pass to the [[User::can()]] function for evaluating |
66
|
|
|
* user permissions in [[$roles]]. |
67
|
|
|
* |
68
|
|
|
* If this is an array, it will be passed directly to [[User::can()]]. For example for passing an |
69
|
|
|
* ID from the current request, you may use the following: |
70
|
|
|
* |
71
|
|
|
* ```php |
72
|
|
|
* ['postId' => Yii::$app->request->get('id')] |
73
|
|
|
* ``` |
74
|
|
|
* |
75
|
|
|
* You may also specify a closure that returns an array. This can be used to |
76
|
|
|
* evaluate the array values only if they are needed, for example when a model needs to be |
77
|
|
|
* loaded like in the following code: |
78
|
|
|
* |
79
|
|
|
* ```php |
80
|
|
|
* 'rules' => [ |
81
|
|
|
* [ |
82
|
|
|
* 'allow' => true, |
83
|
|
|
* 'actions' => ['update'], |
84
|
|
|
* 'roles' => ['updatePost'], |
85
|
|
|
* 'roleParams' => function($rule) { |
86
|
|
|
* return ['post' => Post::findOne(Yii::$app->request->get('id'))]; |
87
|
|
|
* }, |
88
|
|
|
* ], |
89
|
|
|
* ], |
90
|
|
|
* ``` |
91
|
|
|
* |
92
|
|
|
* A reference to the [[AccessRule]] instance will be passed to the closure as the first parameter. |
93
|
|
|
* |
94
|
|
|
* @see $roles |
95
|
|
|
* @since 2.0.12 |
96
|
|
|
*/ |
97
|
|
|
public $roleParams = []; |
98
|
|
|
/** |
99
|
|
|
* @var array list of user IP addresses that this rule applies to. An IP address |
100
|
|
|
* can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix. |
101
|
|
|
* For example, '192.168.*' matches all IP addresses in the segment '192.168.'. |
102
|
|
|
* If not set or empty, it means this rule applies to all IP addresses. |
103
|
|
|
* @see Request::userIP |
104
|
|
|
*/ |
105
|
|
|
public $ips; |
106
|
|
|
/** |
107
|
|
|
* @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to. |
108
|
|
|
* If not set or empty, it means this rule applies to all request methods. |
109
|
|
|
* @see \yii\web\Request::method |
110
|
|
|
*/ |
111
|
|
|
public $verbs; |
112
|
|
|
/** |
113
|
|
|
* @var callable a callback that will be called to determine if the rule should be applied. |
114
|
|
|
* The signature of the callback should be as follows: |
115
|
|
|
* |
116
|
|
|
* ```php |
117
|
|
|
* function ($rule, $action) |
118
|
|
|
* ``` |
119
|
|
|
* |
120
|
|
|
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object. |
121
|
|
|
* The callback should return a boolean value indicating whether this rule should be applied. |
122
|
|
|
*/ |
123
|
|
|
public $matchCallback; |
124
|
|
|
/** |
125
|
|
|
* @var callable a callback that will be called if this rule determines the access to |
126
|
|
|
* the current action should be denied. If not set, the behavior will be determined by |
127
|
|
|
* [[AccessControl]]. |
128
|
|
|
* |
129
|
|
|
* The signature of the callback should be as follows: |
130
|
|
|
* |
131
|
|
|
* ```php |
132
|
|
|
* function ($rule, $action) |
133
|
|
|
* ``` |
134
|
|
|
* |
135
|
|
|
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object. |
136
|
|
|
*/ |
137
|
|
|
public $denyCallback; |
138
|
|
|
|
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Checks whether the Web user is allowed to perform the specified action. |
142
|
|
|
* @param Action $action the action to be performed |
143
|
|
|
* @param User|false $user the user object or `false` in case of detached User component |
144
|
|
|
* @param Request $request |
145
|
|
|
* @return bool|null `true` if the user is allowed, `false` if the user is denied, `null` if the rule does not apply to the user |
146
|
|
|
*/ |
147
|
27 |
|
public function allows($action, $user, $request) |
148
|
|
|
{ |
149
|
27 |
|
if ($this->matchAction($action) |
150
|
27 |
|
&& $this->matchRole($user) |
|
|
|
|
151
|
16 |
|
&& $this->matchIP($request->getUserIP()) |
152
|
16 |
|
&& $this->matchVerb($request->getMethod()) |
153
|
16 |
|
&& $this->matchController($action->controller) |
154
|
16 |
|
&& $this->matchCustom($action) |
155
|
|
|
) { |
156
|
16 |
|
return $this->allow ? true : false; |
157
|
|
|
} |
158
|
|
|
|
159
|
16 |
|
return null; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @param Action $action the action |
164
|
|
|
* @return bool whether the rule applies to the action |
165
|
|
|
*/ |
166
|
27 |
|
protected function matchAction($action) |
167
|
|
|
{ |
168
|
27 |
|
return empty($this->actions) || in_array($action->id, $this->actions, true); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* @param Controller $controller the controller |
173
|
|
|
* @return bool whether the rule applies to the controller |
174
|
|
|
*/ |
175
|
16 |
|
protected function matchController($controller) |
176
|
|
|
{ |
177
|
16 |
|
if (empty($this->controllers)) { |
178
|
14 |
|
return true; |
179
|
|
|
} |
180
|
|
|
|
181
|
2 |
|
$id = $controller->getUniqueId(); |
182
|
2 |
|
foreach ($this->controllers as $pattern) { |
183
|
2 |
|
if (fnmatch($pattern, $id)) { |
184
|
2 |
|
return true; |
185
|
|
|
} |
186
|
|
|
} |
187
|
|
|
|
188
|
2 |
|
return false; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @param User $user the user object |
193
|
|
|
* @return bool whether the rule applies to the role |
194
|
|
|
* @throws InvalidConfigException if User component is detached |
195
|
|
|
*/ |
196
|
27 |
|
protected function matchRole($user) |
197
|
|
|
{ |
198
|
27 |
|
if (empty($this->roles)) { |
199
|
6 |
|
return true; |
200
|
|
|
} |
201
|
21 |
|
if ($user === false) { |
202
|
1 |
|
throw new InvalidConfigException('The user application component must be available to specify roles in AccessRule.'); |
203
|
|
|
} |
204
|
20 |
|
foreach ($this->roles as $role) { |
205
|
20 |
|
if ($role === '?') { |
206
|
|
|
if ($user->getIsGuest()) { |
207
|
|
|
return true; |
208
|
|
|
} |
209
|
20 |
|
} elseif ($role === '@') { |
210
|
|
|
if (!$user->getIsGuest()) { |
211
|
|
|
return true; |
212
|
|
|
} |
213
|
|
|
} else { |
214
|
20 |
|
if (!isset($roleParams)) { |
215
|
20 |
|
$roleParams = $this->roleParams instanceof Closure ? call_user_func($this->roleParams, $this) : $this->roleParams; |
216
|
|
|
} |
217
|
20 |
|
if ($user->can($role, $roleParams)) { |
218
|
10 |
|
return true; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
10 |
|
return false; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* @param string|null $ip the IP address |
228
|
|
|
* @return bool whether the rule applies to the IP address |
229
|
|
|
*/ |
230
|
16 |
|
protected function matchIP($ip) |
231
|
|
|
{ |
232
|
16 |
|
if (empty($this->ips)) { |
233
|
15 |
|
return true; |
234
|
|
|
} |
235
|
2 |
|
foreach ($this->ips as $rule) { |
236
|
2 |
|
if ($rule === '*' || |
237
|
2 |
|
$rule === $ip || |
238
|
|
|
( |
239
|
2 |
|
$ip !== null && |
240
|
2 |
|
($pos = strpos($rule, '*')) !== false && |
241
|
1 |
|
strncmp($ip, $rule, $pos) === 0 |
|
|
|
|
242
|
|
|
) |
243
|
|
|
) { |
244
|
2 |
|
return true; |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
2 |
|
return false; |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
/** |
252
|
|
|
* @param string $verb the request method. |
253
|
|
|
* @return bool whether the rule applies to the request |
254
|
|
|
*/ |
255
|
16 |
|
protected function matchVerb($verb) |
256
|
|
|
{ |
257
|
16 |
|
return empty($this->verbs) || in_array(strtoupper($verb), array_map('strtoupper', $this->verbs), true); |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* @param Action $action the action to be performed |
262
|
|
|
* @return bool whether the rule should be applied |
263
|
|
|
*/ |
264
|
16 |
|
protected function matchCustom($action) |
265
|
|
|
{ |
266
|
16 |
|
return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action); |
267
|
|
|
} |
268
|
|
|
} |
269
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.