Completed
Push — unique-validator-fix ( 963824...d331b7 )
by Alexander
29:51 queued 14:14
created

AccessRule::matchRole()   C

Complexity

Conditions 9
Paths 9

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 10.5365

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 24
ccs 11
cts 15
cp 0.7332
rs 5.3563
cc 9
eloc 15
nc 9
nop 1
crap 10.5365
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 yii\base\Component;
11
use yii\base\Action;
12
use yii\base\InvalidConfigException;
13
use yii\web\User;
14
use yii\web\Request;
15
use yii\base\Controller;
16
17
/**
18
 * This class represents an access rule defined by the [[AccessControl]] action filter
19
 *
20
 * @author Qiang Xue <[email protected]>
21
 * @since 2.0
22
 */
23
class AccessRule extends Component
24
{
25
    /**
26
     * @var bool whether this is an 'allow' rule or 'deny' rule.
27
     */
28
    public $allow;
29
    /**
30
     * @var array list of action IDs that this rule applies to. The comparison is case-sensitive.
31
     * If not set or empty, it means this rule applies to all actions.
32
     */
33
    public $actions;
34
    /**
35
     * @var array list of the controller IDs that this rule applies to.
36
     *
37
     * The comparison uses [[\yii\base\Controller::uniqueId]], so each controller ID is prefixed
38
     * with the module ID (if any). For a `product` controller in the application, you would specify
39
     * this property like `['product']` and if that controller is located in a `shop` module, this
40
     * would be `['shop/product']`.
41
     *
42
     * The comparison is case-sensitive.
43
     *
44
     * If not set or empty, it means this rule applies to all controllers.
45
     */
46
    public $controllers;
47
    /**
48
     * @var array list of roles that this rule applies to (requires properly configured User component).
49
     * Two special roles are recognized, and they are checked via [[User::isGuest]]:
50
     *
51
     * - `?`: matches a guest user (not authenticated yet)
52
     * - `@`: matches an authenticated user
53
     *
54
     * If you are using RBAC (Role-Based Access Control), you may also specify role or permission names.
55
     * In this case, [[User::can()]] will be called to check access.
56
     *
57
     * If this property is not set or empty, it means this rule applies to all roles.
58
     */
59
    public $roles;
60
    /**
61
     * @var array list of user IP addresses that this rule applies to. An IP address
62
     * can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
63
     * For example, '192.168.*' matches all IP addresses in the segment '192.168.'.
64
     * If not set or empty, it means this rule applies to all IP addresses.
65
     * @see Request::userIP
66
     */
67
    public $ips;
68
    /**
69
     * @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to.
70
     * If not set or empty, it means this rule applies to all request methods.
71
     * @see \yii\web\Request::method
72
     */
73
    public $verbs;
74
    /**
75
     * @var callable a callback that will be called to determine if the rule should be applied.
76
     * The signature of the callback should be as follows:
77
     *
78
     * ```php
79
     * function ($rule, $action)
80
     * ```
81
     *
82
     * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
83
     * The callback should return a boolean value indicating whether this rule should be applied.
84
     */
85
    public $matchCallback;
86
    /**
87
     * @var callable a callback that will be called if this rule determines the access to
88
     * the current action should be denied. If not set, the behavior will be determined by
89
     * [[AccessControl]].
90
     *
91
     * The signature of the callback should be as follows:
92
     *
93
     * ```php
94
     * function ($rule, $action)
95
     * ```
96
     *
97
     * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
98
     */
99
    public $denyCallback;
100
101
102
    /**
103
     * Checks whether the Web user is allowed to perform the specified action.
104
     * @param Action $action the action to be performed
105
     * @param User|false $user the user object or `false` in case of detached User component
106
     * @param Request $request
107
     * @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
108
     */
109 13
    public function allows($action, $user, $request)
110
    {
111 13
        if ($this->matchAction($action)
112 13
            && $this->matchRole($user)
0 ignored issues
show
Bug introduced by
It seems like $user defined by parameter $user on line 109 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...
113 8
            && $this->matchIP($request->getUserIP())
114 8
            && $this->matchVerb($request->getMethod())
115 8
            && $this->matchController($action->controller)
116 8
            && $this->matchCustom($action)
117
        ) {
118 8
            return $this->allow ? true : false;
119
        }
120
121 8
        return null;
122
    }
123
124
    /**
125
     * @param Action $action the action
126
     * @return bool whether the rule applies to the action
127
     */
128 13
    protected function matchAction($action)
129
    {
130 13
        return empty($this->actions) || in_array($action->id, $this->actions, true);
131
    }
132
133
    /**
134
     * @param Controller $controller the controller
135
     * @return bool whether the rule applies to the controller
136
     */
137 8
    protected function matchController($controller)
138
    {
139 8
        return empty($this->controllers) || in_array($controller->uniqueId, $this->controllers, true);
140
    }
141
142
    /**
143
     * @param User $user the user object
144
     * @return bool whether the rule applies to the role
145
     * @throws InvalidConfigException if User component is detached
146
     */
147 13
    protected function matchRole($user)
148
    {
149 13
        if (empty($this->roles)) {
150 4
            return true;
151
        }
152 9
        if ($user === false) {
153 1
            throw new InvalidConfigException('The user application component must be available to specify roles in AccessRule.');
154
        }
155 8
        foreach ($this->roles as $role) {
156 8
            if ($role === '?') {
157
                if ($user->getIsGuest()) {
158
                    return true;
159
                }
160 8
            } elseif ($role === '@') {
161
                if (!$user->getIsGuest()) {
162
                    return true;
163
                }
164 8
            } elseif ($user->can($role)) {
165 4
                return true;
166
            }
167
        }
168
169 4
        return false;
170
    }
171
172
    /**
173
     * @param string|null $ip the IP address
174
     * @return bool whether the rule applies to the IP address
175
     */
176 8
    protected function matchIP($ip)
177
    {
178 8
        if (empty($this->ips)) {
179 7
            return true;
180
        }
181 2
        foreach ($this->ips as $rule) {
182 2
            if ($rule === '*' ||
183 2
                $rule === $ip ||
184
                (
185 2
                    $ip !== null &&
186 2
                    ($pos = strpos($rule, '*')) !== false &&
187 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...
188
                )
189
            ) {
190 2
                return true;
191
            }
192
        }
193
194 2
        return false;
195
    }
196
197
    /**
198
     * @param string $verb the request method.
199
     * @return bool whether the rule applies to the request
200
     */
201 8
    protected function matchVerb($verb)
202
    {
203 8
        return empty($this->verbs) || in_array(strtoupper($verb), array_map('strtoupper', $this->verbs), true);
204
    }
205
206
    /**
207
     * @param Action $action the action to be performed
208
     * @return bool whether the rule should be applied
209
     */
210 8
    protected function matchCustom($action)
211
    {
212 8
        return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
213
    }
214
}
215