Completed
Push — master ( e4c888...b002ee )
by Alexander
10:41 queued 05:38
created

User   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 352
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 81
c 1
b 0
f 0
dl 0
loc 352
ccs 0
cts 137
cp 0
rs 8.8798
wmc 44

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getIdentity() 0 16 5
A login() 0 7 2
A logout() 0 11 5
A loginByAccessToken() 0 7 3
C renewAuthStatus() 0 17 12
A afterLogout() 0 3 1
A setAccessChecker() 0 3 1
A __construct() 0 4 1
A setSession() 0 3 1
A getId() 0 3 1
A can() 0 7 2
A setIdentity() 0 3 1
A isGuest() 0 3 1
A switchIdentity() 0 19 5
A beforeLogin() 0 5 1
A afterLogin() 0 3 1
A beforeLogout() 0 5 1

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.

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
namespace Yiisoft\Yii\Web\User;
3
4
use Psr\EventDispatcher\EventDispatcherInterface;
5
use Yiisoft\Access\CheckAccessInterface;
6
use Yiisoft\Yii\Web\Session\Session;
7
use Yiisoft\Yii\Web\User\Event\AfterLoginEvent;
8
use Yiisoft\Yii\Web\User\Event\AfterLogoutEvent;
9
use Yiisoft\Yii\Web\User\Event\BeforeLoginEvent;
10
use Yiisoft\Yii\Web\User\Event\BeforeLogout;
11
12
class User
13
{
14
    private const SESSION_AUTH_ID = '__auth_id';
15
    private const SESSION_AUTH_EXPIRE = '__auth_expire';
16
    private const SESSION_AUTH_ABSOLUTE_EXPIRE = '__auth_absolute_expire';
17
18
    /**
19
     * @var IdentityRepositoryInterface
20
     */
21
    private $identityRepository;
22
23
    /**
24
     * @var CheckAccessInterface
25
     */
26
    private $accessChecker;
27
28
    /**
29
     * @var IdentityInterface
30
     */
31
    private $identity;
32
33
    /**
34
     * @var Session
35
     */
36
    private $session;
37
38
    /**
39
     * @var EventDispatcherInterface
40
     */
41
    private $eventDispatcher;
42
43
    public function __construct(IdentityRepositoryInterface $identityRepository, EventDispatcherInterface $eventDispatcher)
44
    {
45
        $this->identityRepository = $identityRepository;
46
        $this->eventDispatcher = $eventDispatcher;
47
    }
48
49
    public function setAccessChecker(CheckAccessInterface $accessChecker): void
50
    {
51
        $this->accessChecker = $accessChecker;
52
    }
53
54
    /**
55
     * Set session to persist authentication status across multiple requests.
56
     * If not set, authentication has to be performed on each request, which is often the case
57
     * for stateless application such as RESTful API.
58
     *
59
     * @param Session $session
60
     */
61
    public function setSession(Session $session): void
62
    {
63
        $this->session = $session;
64
    }
65
66
    /**
67
     * @var int the number of seconds in which the user will be logged out automatically if he
68
     * remains inactive. If this property is not set, the user will be logged out after
69
     * the current session expires (c.f. [[Session::timeout]]).
70
     */
71
    public $authTimeout;
72
73
    /**
74
     * @var int the number of seconds in which the user will be logged out automatically
75
     * regardless of activity.
76
     * Note that this will not work if [[enableAutoLogin]] is `true`.
77
     */
78
    public $absoluteAuthTimeout;
79
80
81
    /**
82
     * @var array MIME types for which this component should redirect to the [[loginUrl]].
83
     */
84
    public $acceptableRedirectTypes = ['text/html', 'application/xhtml+xml'];
85
86
    /**
87
     * Returns the identity object associated with the currently logged-in user.
88
     * When [[enableSession]] is true, this method may attempt to read the user's authentication data
89
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
90
     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
91
     * This is only useful when [[enableSession]] is true.
92
     * @return IdentityInterface the identity object associated with the currently logged-in user.
93
     * @throws \Throwable
94
     * @see logout()
95
     * @see login()
96
     */
97
    public function getIdentity($autoRenew = true): IdentityInterface
98
    {
99
        if ($this->identity === null) {
100
            if ($this->session !== null && $autoRenew) {
101
                try {
102
                    $this->identity = null;
103
                    $this->renewAuthStatus();
104
                } catch (\Throwable $e) {
105
                    $this->identity = null;
106
                    throw $e;
107
                }
108
            } else {
109
                return new GuestIdentity();
110
            }
111
        }
112
        return $this->identity;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->identity could return the type null which is incompatible with the type-hinted return Yiisoft\Yii\Web\User\IdentityInterface. Consider adding an additional type-check to rule them out.
Loading history...
113
    }
114
115
    /**
116
     * Sets the user identity object.
117
     *
118
     * Note that this method does not deal with session or cookie. You should usually use [[switchIdentity()]]
119
     * to change the identity of the current user.
120
     *
121
     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
122
     * Use {{@see GuestIdentity}} to indicate that the current user is a guest.
123
     */
124
    public function setIdentity(IdentityInterface $identity): void
125
    {
126
        $this->identity = $identity;
127
    }
128
129
    /**
130
     * Logs in a user.
131
     *
132
     * After logging in a user:
133
     * - the user's identity information is obtainable from the [[identity]] property
134
     *
135
     * If [[enableSession]] is `true`:
136
     * - the identity information will be stored in session and be available in the next requests
137
     * - in case of `$duration == 0`: as long as the session remains active or till the user closes the browser
138
     * - in case of `$duration > 0`: as long as the session remains active or as long as the cookie
139
     *   remains valid by it's `$duration` in seconds when [[enableAutoLogin]] is set `true`.
140
     *
141
     * If [[enableSession]] is `false`:
142
     * - the `$duration` parameter will be ignored
143
     *
144
     * @param IdentityInterface $identity the user identity (which should already be authenticated)
145
     * @param int $duration number of seconds that the user can remain in logged-in status, defaults to `0`
146
     * @return bool whether the user is logged in
147
     */
148
    public function login(IdentityInterface $identity, $duration = 0)
149
    {
150
        if ($this->beforeLogin($identity, $duration)) {
151
            $this->switchIdentity($identity);
152
            $this->afterLogin($identity, $duration);
153
        }
154
        return !$this->isGuest();
155
    }
156
157
    /**
158
     * Logs in a user by the given access token.
159
     * This method will first authenticate the user by calling [[IdentityInterface::findIdentityByAccessToken()]]
160
     * with the provided access token. If successful, it will call [[login()]] to log in the authenticated user.
161
     * If authentication fails or [[login()]] is unsuccessful, it will return null.
162
     * @param string $token the access token
163
     * @param mixed $type the type of the token. The value of this parameter depends on the implementation.
164
     * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`.
165
     * @return IdentityInterface|null the identity associated with the given access token. Null is returned if
166
     * the access token is invalid or [[login()]] is unsuccessful.
167
     */
168
    public function loginByAccessToken($token, $type = null): ?IdentityInterface
169
    {
170
        $identity = $this->identityRepository->findIdentityByToken($token, $type);
0 ignored issues
show
Bug introduced by
It seems like $type can also be of type null; however, parameter $type of Yiisoft\Yii\Web\User\Ide...::findIdentityByToken() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

170
        $identity = $this->identityRepository->findIdentityByToken($token, /** @scrutinizer ignore-type */ $type);
Loading history...
171
        if ($identity && $this->login($identity)) {
172
            return $identity;
173
        }
174
        return null;
175
    }
176
177
    /**
178
     * Logs out the current user.
179
     * This will remove authentication-related session data.
180
     * If `$destroySession` is true, all session data will be removed.
181
     * @param bool $destroySession whether to destroy the whole session. Defaults to true.
182
     * This parameter is ignored if [[enableSession]] is false.
183
     * @return bool whether the user is logged out
184
     * @throws \Throwable
185
     */
186
    public function logout($destroySession = true): bool
187
    {
188
        $identity = $this->getIdentity();
189
        if (!$this->isGuest() && $this->beforeLogout($identity)) {
190
            $this->switchIdentity(new GuestIdentity());
191
            if ($destroySession && $this->session) {
192
                $this->session->destroy();
193
            }
194
            $this->afterLogout($identity);
195
        }
196
        return $this->isGuest();
197
    }
198
199
    /**
200
     * Returns a value indicating whether the user is a guest (not authenticated).
201
     * @return bool whether the current user is a guest.
202
     * @see getIdentity()
203
     */
204
    public function isGuest(): bool
205
    {
206
        return $this->identity instanceof GuestIdentity;
207
    }
208
209
    /**
210
     * Returns a value that uniquely represents the user.
211
     * @return string the unique identifier for the user. If `null`, it means the user is a guest.
212
     * @throws \Throwable
213
     * @see getIdentity()
214
     */
215
    public function getId(): ?string
216
    {
217
        return $this->getIdentity()->getId();
218
    }
219
220
    /**
221
     * This method is called before logging in a user.
222
     * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event.
223
     * If you override this method, make sure you call the parent implementation
224
     * so that the event is triggered.
225
     * @param IdentityInterface $identity the user identity information
226
     * @param int $duration number of seconds that the user can remain in logged-in status.
227
     * If 0, it means login till the user closes the browser or the session is manually destroyed.
228
     * @return bool whether the user should continue to be logged in
229
     */
230
    protected function beforeLogin(IdentityInterface $identity, int $duration): bool
231
    {
232
        $event = new BeforeLoginEvent($identity, $duration);
233
        $this->eventDispatcher->dispatch($event);
234
        return $event->isValid();
235
    }
236
237
    /**
238
     * This method is called after the user is successfully logged in.
239
     * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event.
240
     * If you override this method, make sure you call the parent implementation
241
     * so that the event is triggered.
242
     * @param IdentityInterface $identity the user identity information
243
     * @param int $duration number of seconds that the user can remain in logged-in status.
244
     * If 0, it means login till the user closes the browser or the session is manually destroyed.
245
     */
246
    protected function afterLogin(IdentityInterface $identity, int $duration): void
247
    {
248
        $this->eventDispatcher->dispatch(new AfterLoginEvent($identity, $duration));
249
    }
250
251
    /**
252
     * This method is invoked when calling [[logout()]] to log out a user.
253
     * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event.
254
     * If you override this method, make sure you call the parent implementation
255
     * so that the event is triggered.
256
     * @param IdentityInterface $identity the user identity information
257
     * @return bool whether the user should continue to be logged out
258
     */
259
    protected function beforeLogout(IdentityInterface $identity): bool
260
    {
261
        $event = new BeforeLogout($identity);
262
        $this->eventDispatcher->dispatch($event);
263
        return $event->isValid();
264
    }
265
266
    /**
267
     * This method is invoked right after a user is logged out via [[logout()]].
268
     * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event.
269
     * If you override this method, make sure you call the parent implementation
270
     * so that the event is triggered.
271
     * @param IdentityInterface $identity the user identity information
272
     */
273
    protected function afterLogout($identity): void
274
    {
275
        $this->eventDispatcher->dispatch(new AfterLogoutEvent($identity));
276
    }
277
278
    /**
279
     * Switches to a new identity for the current user.
280
     *
281
     * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information,
282
     * according to the value of `$duration`. Please refer to [[login()]] for more details.
283
     *
284
     * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
285
     * when the current user needs to be associated with the corresponding identity information.
286
     *
287
     * @param IdentityInterface $identity the identity information to be associated with the current user.
288
     * In order to indicate that the user is guest, use {{@see GuestIdentity}}.
289
     */
290
    public function switchIdentity(IdentityInterface $identity): void
291
    {
292
        $this->setIdentity($identity);
293
        if ($this->session === null) {
294
            return;
295
        }
296
297
        $this->session->regenerateID();
298
299
        $this->session->remove(self::SESSION_AUTH_ID);
300
        $this->session->remove(self::SESSION_AUTH_EXPIRE);
301
302
        if ($identity->getId() !== null) {
303
            $this->session->set(self::SESSION_AUTH_ID, $identity->getId());
304
            if ($this->authTimeout !== null) {
305
                $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
306
            }
307
            if ($this->absoluteAuthTimeout !== null) {
308
                $this->session->set(self::SESSION_AUTH_ABSOLUTE_EXPIRE, time() + $this->absoluteAuthTimeout);
309
            }
310
        }
311
    }
312
313
    /**
314
     * Updates the authentication status using the information from session and cookie.
315
     *
316
     * This method will try to determine the user identity using a session variable.
317
     *
318
     * If [[authTimeout]] is set, this method will refresh the timer.
319
     *
320
     * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]]
321
     * if [[enableAutoLogin]] is true.
322
     * @throws \Throwable
323
     */
324
    protected function renewAuthStatus(): void
325
    {
326
        $id = $this->session->get(self::SESSION_AUTH_ID);
327
        if ($id === null) {
328
            $identity = new GuestIdentity();
329
        } else {
330
            $identity = $this->identityRepository->findIdentity($id);
331
        }
332
        $this->setIdentity($identity);
0 ignored issues
show
Bug introduced by
It seems like $identity can also be of type null; however, parameter $identity of Yiisoft\Yii\Web\User\User::setIdentity() does only seem to accept Yiisoft\Yii\Web\User\IdentityInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

332
        $this->setIdentity(/** @scrutinizer ignore-type */ $identity);
Loading history...
333
334
        if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
335
            $expire = $this->authTimeout !== null ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE) : null;
336
            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE) : null;
337
            if (($expire !== null && $expire < time()) || ($expireAbsolute !== null && $expireAbsolute < time())) {
338
                $this->logout(false);
339
            } elseif ($this->authTimeout !== null) {
340
                $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
341
            }
342
        }
343
    }
344
345
    /**
346
     * Checks if the user can perform the operation as specified by the given permission.
347
     *
348
     * Note that you must provide access checker via {{@see User::setAccessChecker()}} in order to use this method.
349
     * Otherwise it will always return false.
350
     *
351
     * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check.
352
     * @param array $params name-value pairs that would be passed to the rules associated
353
     * with the roles and permissions assigned to the user.
354
     * @return bool whether the user can perform the operation as specified by the given permission.
355
     * @throws \Throwable
356
     */
357
    public function can(string $permissionName, array $params = []): bool
358
    {
359
        if ($this->accessChecker === null) {
360
            return false;
361
        }
362
363
        return $this->accessChecker->checkAccess($this->getId(), $permissionName, $params);
364
    }
365
}
366