Completed
Push — 2.1-master-merge ( 240673 )
by Alexander
13:45
created

User   D

Complexity

Total Complexity 104

Size/Duplication

Total Lines 705
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 76.74%

Importance

Changes 0
Metric Value
wmc 104
lcom 1
cbo 9
dl 0
loc 705
rs 4.4444
c 0
b 0
f 0
ccs 165
cts 215
cp 0.7674

26 Methods

Rating   Name   Duplication   Size   Complexity  
A sendIdentityCookie() 0 11 1
A afterLogout() 0 6 1
A removeIdentityCookie() 0 4 1
C switchIdentity() 0 33 11
F renewAuthStatus() 0 33 17
B can() 0 15 7
B checkRedirectAcceptable() 0 15 6
A init() 0 11 4
A getIdentity() 0 13 4
A setIdentity() 0 11 3
A login() 0 17 3
A loginByAccessToken() 0 11 3
B logout() 0 16 5
A getIsGuest() 0 4 1
A getId() 0 6 2
A getReturnUrl() 0 13 4
A setReturnUrl() 0 4 1
B loginRequired() 0 19 10
A beforeLogin() 0 11 1
A afterLogin() 0 8 1
A beforeLogout() 0 9 1
A renewIdentityCookie() 0 14 4
A getAuthManager() 0 4 1
A getAccessChecker() 0 4 2
A loginByCookie() 0 15 4
B getIdentityAndDurationFromCookie() 0 25 6

How to fix   Complexity   

Complex Class

Complex classes like User often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use User, and based on these observations, apply Extract Interface, too.

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\web;
9
10
use Yii;
11
use yii\base\Component;
12
use yii\base\InvalidConfigException;
13
use yii\base\InvalidValueException;
14
use yii\http\Cookie;
15
use yii\rbac\CheckAccessInterface;
16
17
/**
18
 * User is the class for the `user` application component that manages the user authentication status.
19
 *
20
 * You may use [[isGuest]] to determine whether the current user is a guest or not.
21
 * If the user is a guest, the [[identity]] property would return `null`. Otherwise, it would
22
 * be an instance of [[IdentityInterface]].
23
 *
24
 * You may call various methods to change the user authentication status:
25
 *
26
 * - [[login()]]: sets the specified identity and remembers the authentication status in session and cookie;
27
 * - [[logout()]]: marks the user as a guest and clears the relevant information from session and cookie;
28
 * - [[setIdentity()]]: changes the user identity without touching session or cookie
29
 *   (this is best used in stateless RESTful API implementation).
30
 *
31
 * Note that User only maintains the user authentication status. It does NOT handle how to authenticate
32
 * a user. The logic of how to authenticate a user should be done in the class implementing [[IdentityInterface]].
33
 * You are also required to set [[identityClass]] with the name of this class.
34
 *
35
 * User is configured as an application component in [[\yii\web\Application]] by default.
36
 * You can access that instance via `Yii::$app->user`.
37
 *
38
 * You can modify its configuration by adding an array to your application config under `components`
39
 * as it is shown in the following example:
40
 *
41
 * ```php
42
 * 'user' => [
43
 *     'identityClass' => 'app\models\User', // User must implement the IdentityInterface
44
 *     'enableAutoLogin' => true,
45
 *     // 'loginUrl' => ['user/login'],
46
 *     // ...
47
 * ]
48
 * ```
49
 *
50
 * @property string|int $id The unique identifier for the user. If `null`, it means the user is a guest. This
51
 * property is read-only.
52
 * @property IdentityInterface|null $identity The identity object associated with the currently logged-in
53
 * user. `null` is returned if the user is not logged in (not authenticated).
54
 * @property bool $isGuest Whether the current user is a guest. This property is read-only.
55
 * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type
56
 * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details.
57
 *
58
 * @author Qiang Xue <[email protected]>
59
 * @since 2.0
60
 */
61
class User extends Component
62
{
63
    const EVENT_BEFORE_LOGIN = 'beforeLogin';
64
    const EVENT_AFTER_LOGIN = 'afterLogin';
65
    const EVENT_BEFORE_LOGOUT = 'beforeLogout';
66
    const EVENT_AFTER_LOGOUT = 'afterLogout';
67
68
    /**
69
     * @var string the class name of the [[identity]] object.
70
     */
71
    public $identityClass;
72
    /**
73
     * @var bool whether to enable cookie-based login. Defaults to `false`.
74
     * Note that this property will be ignored if [[enableSession]] is `false`.
75
     */
76
    public $enableAutoLogin = false;
77
    /**
78
     * @var bool whether to use session to persist authentication status across multiple requests.
79
     * You set this property to be `false` if your application is stateless, which is often the case
80
     * for RESTful APIs.
81
     */
82
    public $enableSession = true;
83
    /**
84
     * @var string|array the URL for login when [[loginRequired()]] is called.
85
     * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
86
     * The first element of the array should be the route to the login action, and the rest of
87
     * the name-value pairs are GET parameters used to construct the login URL. For example,
88
     *
89
     * ```php
90
     * ['site/login', 'ref' => 1]
91
     * ```
92
     *
93
     * If this property is `null`, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
94
     */
95
    public $loginUrl = ['site/login'];
96
    /**
97
     * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is `true`.
98
     * @see Cookie
99
     */
100
    public $identityCookie = ['name' => '_identity', 'httpOnly' => true];
101
    /**
102
     * @var int the number of seconds in which the user will be logged out automatically if he
103
     * remains inactive. If this property is not set, the user will be logged out after
104
     * the current session expires (c.f. [[Session::timeout]]).
105
     * Note that this will not work if [[enableAutoLogin]] is `true`.
106
     */
107
    public $authTimeout;
108
    /**
109
     * @var CheckAccessInterface The access checker to use for checking access.
110
     * If not set the application auth manager will be used.
111
     * @since 2.0.9
112
     */
113
    public $accessChecker;
114
    /**
115
     * @var int the number of seconds in which the user will be logged out automatically
116
     * regardless of activity.
117
     * Note that this will not work if [[enableAutoLogin]] is `true`.
118
     */
119
    public $absoluteAuthTimeout;
120
    /**
121
     * @var bool whether to automatically renew the identity cookie each time a page is requested.
122
     * This property is effective only when [[enableAutoLogin]] is `true`.
123
     * When this is `false`, the identity cookie will expire after the specified duration since the user
124
     * is initially logged in. When this is `true`, the identity cookie will expire after the specified duration
125
     * since the user visits the site the last time.
126
     * @see enableAutoLogin
127
     */
128
    public $autoRenewCookie = true;
129
    /**
130
     * @var string the session variable name used to store the value of [[id]].
131
     */
132
    public $idParam = '__id';
133
    /**
134
     * @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
135
     * This is used when [[authTimeout]] is set.
136
     */
137
    public $authTimeoutParam = '__expire';
138
    /**
139
     * @var string the session variable name used to store the value of absolute expiration timestamp of the authenticated state.
140
     * This is used when [[absoluteAuthTimeout]] is set.
141
     */
142
    public $absoluteAuthTimeoutParam = '__absoluteExpire';
143
    /**
144
     * @var string the session variable name used to store the value of [[returnUrl]].
145
     */
146
    public $returnUrlParam = '__returnUrl';
147
    /**
148
     * @var array MIME types for which this component should redirect to the [[loginUrl]].
149
     * @since 2.0.8
150
     */
151
    public $acceptableRedirectTypes = ['text/html', 'application/xhtml+xml'];
152
153
    private $_access = [];
154
155
156
    /**
157
     * Initializes the application component.
158
     */
159 50
    public function init()
160
    {
161 50
        parent::init();
162
163 50
        if ($this->identityClass === null) {
164
            throw new InvalidConfigException('User::identityClass must be set.');
165
        }
166 50
        if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
167
            throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
168
        }
169 50
    }
170
171
    private $_identity = false;
172
173
    /**
174
     * Returns the identity object associated with the currently logged-in user.
175
     * When [[enableSession]] is true, this method may attempt to read the user's authentication data
176
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
177
     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
178
     * This is only useful when [[enableSession]] is true.
179
     * @return IdentityInterface|null the identity object associated with the currently logged-in user.
180
     * `null` is returned if the user is not logged in (not authenticated).
181
     * @see login()
182
     * @see logout()
183
     */
184 44
    public function getIdentity($autoRenew = true)
185
    {
186 44
        if ($this->_identity === false) {
187 11
            if ($this->enableSession && $autoRenew) {
188 10
                $this->_identity = null;
189 10
                $this->renewAuthStatus();
190
            } else {
191 1
                return null;
192
            }
193
        }
194
195 43
        return $this->_identity;
196
    }
197
198
    /**
199
     * Sets the user identity object.
200
     *
201
     * Note that this method does not deal with session or cookie. You should usually use [[switchIdentity()]]
202
     * to change the identity of the current user.
203
     *
204
     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
205
     * If null, it means the current user will be a guest without any associated identity.
206
     * @throws InvalidValueException if `$identity` object does not implement [[IdentityInterface]].
207
     */
208 43
    public function setIdentity($identity)
209
    {
210 43
        if ($identity instanceof IdentityInterface) {
211 29
            $this->_identity = $identity;
0 ignored issues
show
Documentation Bug introduced by
It seems like $identity of type object<yii\web\IdentityInterface> is incompatible with the declared type boolean of property $_identity.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
212 29
            $this->_access = [];
213 16
        } elseif ($identity === null) {
214 16
            $this->_identity = null;
215
        } else {
216
            throw new InvalidValueException('The identity object must implement IdentityInterface.');
217
        }
218 43
    }
219
220
    /**
221
     * Logs in a user.
222
     *
223
     * After logging in a user:
224
     * - the user's identity information is obtainable from the [[identity]] property
225
     *
226
     * If [[enableSession]] is `true`:
227
     * - the identity information will be stored in session and be available in the next requests
228
     * - in case of `$duration == 0`: as long as the session remains active or till the user closes the browser
229
     * - in case of `$duration > 0`: as long as the session remains active or as long as the cookie
230
     *   remains valid by it's `$duration` in seconds when [[enableAutoLogin]] is set `true`.
231
     *
232
     * If [[enableSession]] is `false`:
233
     * - the `$duration` parameter will be ignored
234
     *
235
     * @param IdentityInterface $identity the user identity (which should already be authenticated)
236
     * @param int $duration number of seconds that the user can remain in logged-in status, defaults to `0`
237
     * @return bool whether the user is logged in
238
     */
239 12
    public function login(IdentityInterface $identity, $duration = 0)
240
    {
241 12
        if ($this->beforeLogin($identity, false, $duration)) {
242 12
            $this->switchIdentity($identity, $duration);
243 12
            $id = $identity->getId();
244 12
            $ip = Yii::$app->getRequest()->getUserIP();
245 12
            if ($this->enableSession) {
246 12
                $log = "User '$id' logged in from $ip with duration $duration.";
247
            } else {
248
                $log = "User '$id' logged in from $ip. Session not enabled.";
249
            }
250 12
            Yii::info($log, __METHOD__);
251 12
            $this->afterLogin($identity, false, $duration);
252
        }
253
254 12
        return !$this->getIsGuest();
255
    }
256
257
    /**
258
     * Logs in a user by the given access token.
259
     * This method will first authenticate the user by calling [[IdentityInterface::findIdentityByAccessToken()]]
260
     * with the provided access token. If successful, it will call [[login()]] to log in the authenticated user.
261
     * If authentication fails or [[login()]] is unsuccessful, it will return null.
262
     * @param string $token the access token
263
     * @param mixed $type the type of the token. The value of this parameter depends on the implementation.
264
     * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`.
265
     * @return IdentityInterface|null the identity associated with the given access token. Null is returned if
266
     * the access token is invalid or [[login()]] is unsuccessful.
267
     */
268 13
    public function loginByAccessToken($token, $type = null)
269
    {
270
        /* @var $class IdentityInterface */
271 13
        $class = $this->identityClass;
272 13
        $identity = $class::findIdentityByAccessToken($token, $type);
273 13
        if ($identity && $this->login($identity)) {
274 9
            return $identity;
275
        }
276
277 4
        return null;
278
    }
279
280
    /**
281
     * Logs in a user by cookie.
282
     *
283
     * This method attempts to log in a user using the ID and authKey information
284
     * provided by the [[identityCookie|identity cookie]].
285
     */
286 2
    protected function loginByCookie()
287
    {
288 2
        $data = $this->getIdentityAndDurationFromCookie();
289 2
        if (isset($data['identity'], $data['duration'])) {
290 1
            $identity = $data['identity'];
291 1
            $duration = $data['duration'];
292 1
            if ($this->beforeLogin($identity, true, $duration)) {
0 ignored issues
show
Documentation introduced by
$duration is of type object<yii\web\IdentityInterface>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
293 1
                $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
294 1
                $id = $identity->getId();
295 1
                $ip = Yii::$app->getRequest()->getUserIP();
296 1
                Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
297 1
                $this->afterLogin($identity, true, $duration);
0 ignored issues
show
Documentation introduced by
$duration is of type object<yii\web\IdentityInterface>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
298
            }
299
        }
300 2
    }
301
302
    /**
303
     * Logs out the current user.
304
     * This will remove authentication-related session data.
305
     * If `$destroySession` is true, all session data will be removed.
306
     * @param bool $destroySession whether to destroy the whole session. Defaults to true.
307
     * This parameter is ignored if [[enableSession]] is false.
308
     * @return bool whether the user is logged out
309
     */
310
    public function logout($destroySession = true)
311
    {
312
        $identity = $this->getIdentity();
313
        if ($identity !== null && $this->beforeLogout($identity)) {
0 ignored issues
show
Documentation introduced by
$identity is of type boolean, but the function expects a object<yii\web\IdentityInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
314
            $this->switchIdentity(null);
315
            $id = $identity->getId();
0 ignored issues
show
Bug introduced by
The method getId cannot be called on $identity (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
316
            $ip = Yii::$app->getRequest()->getUserIP();
317
            Yii::info("User '$id' logged out from $ip.", __METHOD__);
318
            if ($destroySession && $this->enableSession) {
319
                Yii::$app->getSession()->destroy();
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
320
            }
321
            $this->afterLogout($identity);
0 ignored issues
show
Documentation introduced by
$identity is of type boolean, but the function expects a object<yii\web\IdentityInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
322
        }
323
324
        return $this->getIsGuest();
325
    }
326
327
    /**
328
     * Returns a value indicating whether the user is a guest (not authenticated).
329
     * @return bool whether the current user is a guest.
330
     * @see getIdentity()
331
     */
332 12
    public function getIsGuest()
333
    {
334 12
        return $this->getIdentity() === null;
335
    }
336
337
    /**
338
     * Returns a value that uniquely represents the user.
339
     * @return string|int the unique identifier for the user. If `null`, it means the user is a guest.
340
     * @see getIdentity()
341
     */
342 41
    public function getId()
343
    {
344 41
        $identity = $this->getIdentity();
345
346 41
        return $identity !== null ? $identity->getId() : null;
0 ignored issues
show
Bug introduced by
The method getId cannot be called on $identity (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
347
    }
348
349
    /**
350
     * Returns the URL that the browser should be redirected to after successful login.
351
     *
352
     * This method reads the return URL from the session. It is usually used by the login action which
353
     * may call this method to redirect the browser to where it goes after successful authentication.
354
     *
355
     * @param string|array $defaultUrl the default return URL in case it was not set previously.
356
     * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
357
     * Please refer to [[setReturnUrl()]] on accepted format of the URL.
358
     * @return string the URL that the user should be redirected to after login.
359
     * @see loginRequired()
360
     */
361 2
    public function getReturnUrl($defaultUrl = null)
362
    {
363 2
        $url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl);
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
364 2
        if (is_array($url)) {
365
            if (isset($url[0])) {
366
                return Yii::$app->getUrlManager()->createUrl($url);
367
            }
368
369
            $url = null;
370
        }
371
372 2
        return $url === null ? Yii::$app->getHomeUrl() : $url;
0 ignored issues
show
Bug introduced by
The method getHomeUrl does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
373
    }
374
375
    /**
376
     * Remembers the URL in the session so that it can be retrieved back later by [[getReturnUrl()]].
377
     * @param string|array $url the URL that the user should be redirected to after login.
378
     * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
379
     * The first element of the array should be the route, and the rest of
380
     * the name-value pairs are GET parameters used to construct the URL. For example,
381
     *
382
     * ```php
383
     * ['admin/index', 'ref' => 1]
384
     * ```
385
     */
386 2
    public function setReturnUrl($url)
387
    {
388 2
        Yii::$app->getSession()->set($this->returnUrlParam, $url);
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
389 2
    }
390
391
    /**
392
     * Redirects the user browser to the login page.
393
     *
394
     * Before the redirection, the current URL (if it's not an AJAX url) will be kept as [[returnUrl]] so that
395
     * the user browser may be redirected back to the current page after successful login.
396
     *
397
     * Make sure you set [[loginUrl]] so that the user browser can be redirected to the specified login URL after
398
     * calling this method.
399
     *
400
     * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution.
401
     *
402
     * @param bool $checkAjax whether to check if the request is an AJAX request. When this is true and the request
403
     * is an AJAX request, the current URL (for AJAX request) will NOT be set as the return URL.
404
     * @param bool $checkAcceptHeader whether to check if the request accepts HTML responses. Defaults to `true`. When this is true and
405
     * the request does not accept HTML responses the current URL will not be SET as the return URL. Also instead of
406
     * redirecting the user an ForbiddenHttpException is thrown. This parameter is available since version 2.0.8.
407
     * @return Response the redirection response if [[loginUrl]] is set
408
     * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is
409
     * not applicable.
410
     */
411 2
    public function loginRequired($checkAjax = true, $checkAcceptHeader = true)
412
    {
413 2
        $request = Yii::$app->getRequest();
414 2
        $canRedirect = !$checkAcceptHeader || $this->checkRedirectAcceptable();
415 2
        if ($this->enableSession
416 2
            && $request->getIsGet()
417 2
            && (!$checkAjax || !$request->getIsAjax())
418 2
            && $canRedirect
419
        ) {
420 1
            $this->setReturnUrl($request->getUrl());
421
        }
422 2
        if ($this->loginUrl !== null && $canRedirect) {
423 1
            $loginUrl = (array) $this->loginUrl;
424 1
            if ($loginUrl[0] !== Yii::$app->requestedRoute) {
425 1
                return Yii::$app->getResponse()->redirect($this->loginUrl);
426
            }
427
        }
428 2
        throw new ForbiddenHttpException(Yii::t('yii', 'Login Required'));
429
    }
430
431
    /**
432
     * This method is called before logging in a user.
433
     * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event.
434
     * If you override this method, make sure you call the parent implementation
435
     * so that the event is triggered.
436
     * @param IdentityInterface $identity the user identity information
437
     * @param bool $cookieBased whether the login is cookie-based
438
     * @param int $duration number of seconds that the user can remain in logged-in status.
439
     * If 0, it means login till the user closes the browser or the session is manually destroyed.
440
     * @return bool whether the user should continue to be logged in
441
     */
442 12
    protected function beforeLogin($identity, $cookieBased, $duration)
443
    {
444 12
        $event = new UserEvent([
445 12
            'identity' => $identity,
446 12
            'cookieBased' => $cookieBased,
447 12
            'duration' => $duration,
448
        ]);
449 12
        $this->trigger(self::EVENT_BEFORE_LOGIN, $event);
450
451 12
        return $event->isValid;
452
    }
453
454
    /**
455
     * This method is called after the user is successfully logged in.
456
     * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event.
457
     * If you override this method, make sure you call the parent implementation
458
     * so that the event is triggered.
459
     * @param IdentityInterface $identity the user identity information
460
     * @param bool $cookieBased whether the login is cookie-based
461
     * @param int $duration number of seconds that the user can remain in logged-in status.
462
     * If 0, it means login till the user closes the browser or the session is manually destroyed.
463
     */
464 12
    protected function afterLogin($identity, $cookieBased, $duration)
465
    {
466 12
        $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([
467 12
            'identity' => $identity,
468 12
            'cookieBased' => $cookieBased,
469 12
            'duration' => $duration,
470
        ]));
471 12
    }
472
473
    /**
474
     * This method is invoked when calling [[logout()]] to log out a user.
475
     * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event.
476
     * If you override this method, make sure you call the parent implementation
477
     * so that the event is triggered.
478
     * @param IdentityInterface $identity the user identity information
479
     * @return bool whether the user should continue to be logged out
480
     */
481
    protected function beforeLogout($identity)
482
    {
483
        $event = new UserEvent([
484
            'identity' => $identity,
485
        ]);
486
        $this->trigger(self::EVENT_BEFORE_LOGOUT, $event);
487
488
        return $event->isValid;
489
    }
490
491
    /**
492
     * This method is invoked right after a user is logged out via [[logout()]].
493
     * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event.
494
     * If you override this method, make sure you call the parent implementation
495
     * so that the event is triggered.
496
     * @param IdentityInterface $identity the user identity information
497
     */
498
    protected function afterLogout($identity)
499
    {
500
        $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([
501
            'identity' => $identity,
502
        ]));
503
    }
504
505
    /**
506
     * Renews the identity cookie.
507
     * This method will set the expiration time of the identity cookie to be the current time
508
     * plus the originally specified cookie duration.
509
     */
510
    protected function renewIdentityCookie()
511
    {
512
        $name = $this->identityCookie['name'];
513
        $value = Yii::$app->getRequest()->getCookies()->getValue($name);
514
        if ($value !== null) {
515
            $data = json_decode($value, true);
516
            if (is_array($data) && isset($data[2])) {
517
                $cookie = new Cookie($this->identityCookie);
518
                $cookie->value = $value;
519
                $cookie->expire = time() + (int) $data[2];
520
                Yii::$app->getResponse()->getCookies()->add($cookie);
521
            }
522
        }
523
    }
524
525
    /**
526
     * Sends an identity cookie.
527
     * This method is used when [[enableAutoLogin]] is true.
528
     * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login
529
     * information in the cookie.
530
     * @param IdentityInterface $identity
531
     * @param int $duration number of seconds that the user can remain in logged-in status.
532
     * @see loginByCookie()
533
     */
534 2
    protected function sendIdentityCookie($identity, $duration)
535
    {
536 2
        $cookie = new Cookie($this->identityCookie);
537 2
        $cookie->value = json_encode([
538 2
            $identity->getId(),
539 2
            $identity->getAuthKey(),
540 2
            $duration,
541 2
        ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
542 2
        $cookie->expire = time() + $duration;
543 2
        Yii::$app->getResponse()->getCookies()->add($cookie);
544 2
    }
545
546
    /**
547
     * Determines if an identity cookie has a valid format and contains a valid auth key.
548
     * This method is used when [[enableAutoLogin]] is true.
549
     * This method attempts to authenticate a user using the information in the identity cookie.
550
     * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null.
551
     * @see loginByCookie()
552
     * @since 2.0.9
553
     */
554 2
    protected function getIdentityAndDurationFromCookie()
555
    {
556 2
        $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']);
557 2
        if ($value === null) {
558
            return null;
559
        }
560 2
        $data = json_decode($value, true);
561 2
        if (count($data) == 3) {
562 1
            [$id, $authKey, $duration] = $data;
0 ignored issues
show
Bug introduced by
The variable $id does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $authKey does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $duration does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
563
            /* @var $class IdentityInterface */
564 1
            $class = $this->identityClass;
565 1
            $identity = $class::findIdentity($id);
566 1
            if ($identity !== null) {
567 1
                if (!$identity instanceof IdentityInterface) {
568
                    throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface.");
569 1
                } elseif (!$identity->validateAuthKey($authKey)) {
570
                    Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
571
                } else {
572 1
                    return ['identity' => $identity, 'duration' => $duration];
573
                }
574
            }
575
        }
576 2
        $this->removeIdentityCookie();
577 2
        return null;
578
    }
579
580
    /**
581
     * Removes the identity cookie.
582
     * This method is used when [[enableAutoLogin]] is true.
583
     * @since 2.0.9
584
     */
585 2
    protected function removeIdentityCookie()
586
    {
587 2
        Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));
588 2
    }
589
590
    /**
591
     * Switches to a new identity for the current user.
592
     *
593
     * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information,
594
     * according to the value of `$duration`. Please refer to [[login()]] for more details.
595
     *
596
     * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
597
     * when the current user needs to be associated with the corresponding identity information.
598
     *
599
     * @param IdentityInterface|null $identity the identity information to be associated with the current user.
600
     * If null, it means switching the current user to be a guest.
601
     * @param int $duration number of seconds that the user can remain in logged-in status.
602
     * This parameter is used only when `$identity` is not null.
603
     */
604 15
    public function switchIdentity($identity, $duration = 0)
605
    {
606 15
        $this->setIdentity($identity);
607
608 15
        if (!$this->enableSession) {
609
            return;
610
        }
611
612
        /* Ensure any existing identity cookies are removed. */
613 15
        if ($this->enableAutoLogin && ($this->autoRenewCookie || $identity === null)) {
614 1
            $this->removeIdentityCookie();
615
        }
616
617 15
        $session = Yii::$app->getSession();
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
618 15
        if (!YII_ENV_TEST) {
619
            $session->regenerateID(true);
620
        }
621 15
        $session->remove($this->idParam);
622 15
        $session->remove($this->authTimeoutParam);
623
624 15
        if ($identity) {
625 15
            $session->set($this->idParam, $identity->getId());
626 15
            if ($this->authTimeout !== null) {
627 1
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
628
            }
629 15
            if ($this->absoluteAuthTimeout !== null) {
630
                $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout);
631
            }
632 15
            if ($this->enableAutoLogin && $duration > 0) {
633 2
                $this->sendIdentityCookie($identity, $duration);
634
            }
635
        }
636 15
    }
637
638
    /**
639
     * Updates the authentication status using the information from session and cookie.
640
     *
641
     * This method will try to determine the user identity using the [[idParam]] session variable.
642
     *
643
     * If [[authTimeout]] is set, this method will refresh the timer.
644
     *
645
     * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]]
646
     * if [[enableAutoLogin]] is true.
647
     */
648 10
    protected function renewAuthStatus()
649
    {
650 10
        $session = Yii::$app->getSession();
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
651 10
        $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null;
652
653 10
        if ($id === null) {
654 10
            $identity = null;
655
        } else {
656
            /* @var $class IdentityInterface */
657 1
            $class = $this->identityClass;
658 1
            $identity = $class::findIdentity($id);
659
        }
660
661 10
        $this->setIdentity($identity);
662
663 10
        if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
664 1
            $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null;
665 1
            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null;
666 1
            if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) {
667
                $this->logout(false);
668 1
            } elseif ($this->authTimeout !== null) {
669 1
                $session->set($this->authTimeoutParam, time() + $this->authTimeout);
670
            }
671
        }
672
673 10
        if ($this->enableAutoLogin) {
674 2
            if ($this->getIsGuest()) {
675 2
                $this->loginByCookie();
676 1
            } elseif ($this->autoRenewCookie) {
677
                $this->renewIdentityCookie();
678
            }
679
        }
680 10
    }
681
682
    /**
683
     * Checks if the user can perform the operation as specified by the given permission.
684
     *
685
     * Note that you must configure "authManager" application component in order to use this method.
686
     * Otherwise it will always return false.
687
     *
688
     * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check.
689
     * @param array $params name-value pairs that would be passed to the rules associated
690
     * with the roles and permissions assigned to the user.
691
     * @param bool $allowCaching whether to allow caching the result of access check.
692
     * When this parameter is true (default), if the access check of an operation was performed
693
     * before, its result will be directly returned when calling this method to check the same
694
     * operation. If this parameter is false, this method will always call
695
     * [[\yii\rbac\CheckAccessInterface::checkAccess()]] to obtain the up-to-date access result. Note that this
696
     * caching is effective only within the same request and only works when `$params = []`.
697
     * @return bool whether the user can perform the operation as specified by the given permission.
698
     */
699 20
    public function can($permissionName, $params = [], $allowCaching = true)
700
    {
701 20
        if ($allowCaching && empty($params) && isset($this->_access[$permissionName])) {
702
            return $this->_access[$permissionName];
703
        }
704 20
        if (($accessChecker = $this->getAccessChecker()) === null) {
705
            return false;
706
        }
707 20
        $access = $accessChecker->checkAccess($this->getId(), $permissionName, $params);
708 20
        if ($allowCaching && empty($params)) {
709 8
            $this->_access[$permissionName] = $access;
710
        }
711
712 20
        return $access;
713
    }
714
715
    /**
716
     * Checks if the `Accept` header contains a content type that allows redirection to the login page.
717
     * The login page is assumed to serve `text/html` or `application/xhtml+xml` by default. You can change acceptable
718
     * content types by modifying [[acceptableRedirectTypes]] property.
719
     * @return bool whether this request may be redirected to the login page.
720
     * @see acceptableRedirectTypes
721
     * @since 2.0.8
722
     */
723 2
    protected function checkRedirectAcceptable()
724
    {
725 2
        $acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes();
726 2
        if (empty($acceptableTypes) || count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*') {
727 1
            return true;
728
        }
729
730 2
        foreach ($acceptableTypes as $type => $params) {
731 2
            if (in_array($type, $this->acceptableRedirectTypes, true)) {
732 2
                return true;
733
            }
734
        }
735
736 2
        return false;
737
    }
738
739
    /**
740
     * Returns auth manager associated with the user component.
741
     *
742
     * By default this is the `authManager` application component.
743
     * You may override this method to return a different auth manager instance if needed.
744
     * @return \yii\rbac\ManagerInterface
745
     * @since 2.0.6
746
     * @deprecated since version 2.0.9, to be removed in 2.1. Use [[getAccessChecker()]] instead.
747
     */
748
    protected function getAuthManager()
749
    {
750
        return Yii::$app->getAuthManager();
751
    }
752
753
    /**
754
     * Returns the access checker used for checking access.
755
     *
756
     * By default this is the `authManager` application component.
757
     *
758
     * @return CheckAccessInterface
759
     * @since 2.0.9
760
     */
761 20
    protected function getAccessChecker()
762
    {
763 20
        return $this->accessChecker !== null ? $this->accessChecker : Yii::$app->getAuthManager();
764
    }
765
}
766