Completed
Push — 2.1 ( 7c8525...0afc41 )
by Alexander
21:05 queued 16:02
created

AccessRule   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 92.16%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 37
c 2
b 0
f 0
lcom 1
cbo 6
dl 0
loc 245
ccs 47
cts 51
cp 0.9216
rs 8.6

7 Methods

Rating   Name   Duplication   Size   Complexity  
B allows() 0 14 8
A matchAction() 0 4 2
A matchController() 0 15 4
C matchRole() 0 29 11
B matchIP() 0 20 8
A matchVerb() 0 4 2
A matchCustom() 0 4 2
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)
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 147 can also be of type false; however, yii\filters\AccessRule::matchRole() does only seem to accept object<yii\web\User>, maybe add an additional type check?

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.

Loading history...
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
0 ignored issues
show
Bug introduced by
The variable $pos does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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