Completed
Push — master ( 55b06d...9f2a87 )
by Alexander
35:57
created

framework/filters/AccessRule.php (1 issue)

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