Passed
Push — master ( a5c058...4c5495 )
by Alexander
16:34 queued 14:14
created

User::renewAuthStatus()   C

Complexity

Conditions 13
Paths 52

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 13

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 13
eloc 14
nc 52
nop 0
dl 0
loc 20
ccs 15
cts 15
cp 1
crap 13
rs 6.6166
c 3
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web\User;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Yiisoft\Access\AccessCheckerInterface;
9
use Yiisoft\Auth\IdentityInterface;
10
use Yiisoft\Auth\IdentityRepositoryInterface;
11
use Yiisoft\Yii\Web\Session\SessionInterface;
12
use Yiisoft\Yii\Web\User\Event\AfterLogin;
13
use Yiisoft\Yii\Web\User\Event\AfterLogout;
14
use Yiisoft\Yii\Web\User\Event\BeforeLogin;
15
use Yiisoft\Yii\Web\User\Event\BeforeLogout;
16
17
class User
18
{
19
    private const SESSION_AUTH_ID = '__auth_id';
20
    private const SESSION_AUTH_EXPIRE = '__auth_expire';
21
    private const SESSION_AUTH_ABSOLUTE_EXPIRE = '__auth_absolute_expire';
22
23
    /**
24
     * @var int|null the number of seconds in which the user will be logged out automatically in case of
25
     * remaining inactive. If this property is not set, the user will be logged out after
26
     * the current session expires.
27
     */
28
    public ?int $authTimeout = null;
29
30
    /**
31
     * @var int|null the number of seconds in which the user will be logged out automatically
32
     * regardless of activity.
33
     */
34
    public ?int $absoluteAuthTimeout = null;
35
36
    private IdentityRepositoryInterface $identityRepository;
37
    private EventDispatcherInterface $eventDispatcher;
38
39
    private ?AccessCheckerInterface $accessChecker = null;
40
    private ?IdentityInterface $identity = null;
41
    private ?SessionInterface $session;
42
43
    /**
44
     * @param IdentityRepositoryInterface $identityRepository
45
     * @param EventDispatcherInterface $eventDispatcher
46
     * @param SessionInterface|null $session session to persist authentication status across multiple requests.
47
     * If not set, authentication has to be performed on each request, which is often the case for stateless
48
     * application such as RESTful API.
49
     */
50 19
    public function __construct(
51
        IdentityRepositoryInterface $identityRepository,
52
        EventDispatcherInterface $eventDispatcher,
53
        SessionInterface $session = null
54
    ) {
55 19
        $this->identityRepository = $identityRepository;
56 19
        $this->eventDispatcher = $eventDispatcher;
57 19
        $this->session = $session;
58 19
    }
59
60 1
    public function setAccessChecker(AccessCheckerInterface $accessChecker): void
61
    {
62 1
        $this->accessChecker = $accessChecker;
63 1
    }
64
65
    /**
66
     * Returns the identity object associated with the currently logged-in user.
67
     * This method read the user's authentication data
68
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
69
     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
70
     * @return IdentityInterface the identity object associated with the currently logged-in user.
71
     * @throws \Throwable
72
     * @see logout()
73
     * @see login()
74
     */
75 17
    public function getIdentity(bool $autoRenew = true): IdentityInterface
76
    {
77 17
        if ($this->identity !== null) {
78 10
            return $this->identity;
79
        }
80 9
        if ($this->session === null || !$autoRenew) {
81 3
            return new GuestIdentity();
82
        }
83
        try {
84 6
            $this->renewAuthStatus();
85 1
        } catch (\Throwable $e) {
86 1
            $this->identity = null;
87 1
            throw $e;
88
        }
89 5
        return $this->identity ?? new GuestIdentity();
90
    }
91
92
    /**
93
     * Sets the user identity object.
94
     *
95
     * Note that this method does not deal with session. You should usually use {@see switchIdentity()}
96
     * to change the identity of the current user.
97
     *
98
     * @param IdentityInterface|null $identity the identity object associated with the currently logged user.
99
     * Use {{@see GuestIdentity}} to indicate that the current user is a guest.
100
     */
101 13
    public function setIdentity(IdentityInterface $identity): void
102
    {
103 13
        $this->identity = $identity;
104 13
    }
105
106
    /**
107
     * Logs in a user.
108
     *
109
     * After logging in a user:
110
     * - the user's identity information is obtainable from the {@see getIdentity()}
111
     * - the identity information will be stored in session and be available in the next requests as long as the session
112
     *   remains active or till the user closes the browser. Some browsers, such as Chrome, are keeping session when
113
     *   browser is re-opened.
114
     *
115
     * @param IdentityInterface $identity the user identity (which should already be authenticated)
116
     * @return bool whether the user is logged in
117
     */
118 2
    public function login(IdentityInterface $identity): bool
119
    {
120 2
        if ($this->beforeLogin($identity)) {
121 2
            $this->switchIdentity($identity);
122 2
            $this->afterLogin($identity);
123
        }
124 2
        return !$this->isGuest();
125
    }
126
127
    /**
128
     * Logs in a user by the given access token.
129
     * This method will first authenticate the user by calling {@see IdentityInterface::findIdentityByToken()}
130
     * with the provided access token. If successful, it will call {@see login()} to log in the authenticated user.
131
     * If authentication fails or {@see login()} is unsuccessful, it will return null.
132
     * @param string $token the access token
133
     * @param string $type the type of the token. The value of this parameter depends on the implementation.
134
     * @return IdentityInterface|null the identity associated with the given access token. Null is returned if
135
     * the access token is invalid or {@see login()} is unsuccessful.
136
     */
137 2
    public function loginByAccessToken(string $token, string $type): ?IdentityInterface
138
    {
139 2
        $identity = $this->identityRepository->findIdentityByToken($token, $type);
140 2
        if ($identity && $this->login($identity)) {
141 1
            return $identity;
142
        }
143 1
        return null;
144
    }
145
146
    /**
147
     * Logs out the current user.
148
     * This will remove authentication-related session data.
149
     * If `$destroySession` is true, all session data will be removed.
150
     * @param bool $destroySession whether to destroy the whole session. Defaults to true.
151
     * @return bool whether the user is logged out
152
     * @throws \Throwable
153
     */
154 5
    public function logout(bool $destroySession = true): bool
155
    {
156 5
        $identity = $this->getIdentity();
157 5
        if ($this->isGuest()) {
158 1
            return false;
159
        }
160 4
        if ($this->beforeLogout($identity)) {
161 4
            $this->switchIdentity(new GuestIdentity());
162 4
            if ($destroySession && $this->session) {
163 1
                $this->session->destroy();
164
            }
165
166 4
            $this->afterLogout($identity);
167
        }
168 4
        return $this->isGuest();
169
    }
170
171
    /**
172
     * Returns a value indicating whether the user is a guest (not authenticated).
173
     * @return bool whether the current user is a guest.
174
     * @see getIdentity()
175
     */
176 7
    public function isGuest(): bool
177
    {
178 7
        return $this->getIdentity() instanceof GuestIdentity;
179
    }
180
181
    /**
182
     * Returns a value that uniquely represents the user.
183
     * @return string the unique identifier for the user. If `null`, it means the user is a guest.
184
     * @throws \Throwable
185
     * @see getIdentity()
186
     */
187 3
    public function getId(): ?string
188
    {
189 3
        return $this->getIdentity()->getId();
190
    }
191
192
    /**
193
     * This method is called before logging in a user.
194
     * The default implementation will trigger the {@see BeforeLogin} event.
195
     * If you override this method, make sure you call the parent implementation
196
     * so that the event is triggered.
197
     * @param IdentityInterface $identity the user identity information
198
     * @return bool whether the user should continue to be logged in
199
     */
200 2
    private function beforeLogin(IdentityInterface $identity): bool
201
    {
202 2
        $event = new BeforeLogin($identity);
203 2
        $this->eventDispatcher->dispatch($event);
204 2
        return $event->isValid();
205
    }
206
207
    /**
208
     * This method is called after the user is successfully logged in.
209
     * @param IdentityInterface $identity the user identity information
210
     */
211 2
    private function afterLogin(IdentityInterface $identity): void
212
    {
213 2
        $this->eventDispatcher->dispatch(new AfterLogin($identity));
214 2
    }
215
216
    /**
217
     * This method is invoked when calling {@see logout()} to log out a user.
218
     * @param IdentityInterface $identity the user identity information
219
     * @return bool whether the user should continue to be logged out
220
     */
221 4
    private function beforeLogout(IdentityInterface $identity): bool
222
    {
223 4
        $event = new BeforeLogout($identity);
224 4
        $this->eventDispatcher->dispatch($event);
225 4
        return $event->isValid();
226
    }
227
228
    /**
229
     * This method is invoked right after a user is logged out via {@see logout()}.
230
     * @param IdentityInterface $identity the user identity information
231
     */
232 4
    private function afterLogout(IdentityInterface $identity): void
233
    {
234 4
        $this->eventDispatcher->dispatch(new AfterLogout($identity));
235 4
    }
236
237
    /**
238
     * Switches to a new identity for the current user.
239
     *
240
     * This method use session to store the user identity information.
241
     * Please refer to {@see login()} for more details.
242
     *
243
     * This method is mainly called by {@see login()} and {@see logout()}
244
     * when the current user needs to be associated with the corresponding identity information.
245
     *
246
     * @param IdentityInterface $identity the identity information to be associated with the current user.
247
     * In order to indicate that the user is guest, use {{@see GuestIdentity}}.
248
     */
249 8
    public function switchIdentity(IdentityInterface $identity): void
250
    {
251 8
        $this->setIdentity($identity);
252 8
        if ($this->session === null) {
253 2
            return;
254
        }
255
256 6
        $this->session->regenerateID();
257
258 6
        $this->session->remove(self::SESSION_AUTH_ID);
259 6
        $this->session->remove(self::SESSION_AUTH_EXPIRE);
260
261 6
        if ($identity->getId() === null) {
262 3
            return;
263
        }
264 3
        $this->session->set(self::SESSION_AUTH_ID, $identity->getId());
265 3
        if ($this->authTimeout !== null) {
266 1
            $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
267
        }
268 3
        if ($this->absoluteAuthTimeout !== null) {
269 1
            $this->session->set(self::SESSION_AUTH_ABSOLUTE_EXPIRE, time() + $this->absoluteAuthTimeout);
270
        }
271 3
    }
272
273
    /**
274
     * Updates the authentication status using the information from session.
275
     *
276
     * This method will try to determine the user identity using a session variable.
277
     *
278
     * If {@see authTimeout} is set, this method will refresh the timer.
279
     *
280
     * @throws \Throwable
281
     */
282 6
    private function renewAuthStatus(): void
283
    {
284 6
        $id = $this->session->get(self::SESSION_AUTH_ID);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

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

284
        /** @scrutinizer ignore-call */ 
285
        $id = $this->session->get(self::SESSION_AUTH_ID);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
285
286 6
        $identity = null;
287 6
        if ($id !== null) {
288 4
            $identity = $this->identityRepository->findIdentity($id);
289
        }
290 5
        if ($identity === null) {
291 2
            $identity = new GuestIdentity();
292
        }
293 5
        $this->setIdentity($identity);
294
295 5
        if (!($identity instanceof GuestIdentity) && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) {
296 3
            $expire = $this->authTimeout !== null ? $this->session->get(self::SESSION_AUTH_EXPIRE) : null;
297 3
            $expireAbsolute = $this->absoluteAuthTimeout !== null ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE) : null;
298 3
            if (($expire !== null && $expire < time()) || ($expireAbsolute !== null && $expireAbsolute < time())) {
299 2
                $this->logout(false);
300 1
            } elseif ($this->authTimeout !== null) {
301 1
                $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
302
            }
303
        }
304 5
    }
305
306
    /**
307
     * Checks if the user can perform the operation as specified by the given permission.
308
     *
309
     * Note that you must provide access checker via {{@see User::setAccessChecker()}} in order to use this method.
310
     * Otherwise it will always return false.
311
     *
312
     * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check.
313
     * @param array $params name-value pairs that would be passed to the rules associated
314
     * with the roles and permissions assigned to the user.
315
     * @return bool whether the user can perform the operation as specified by the given permission.
316
     * @throws \Throwable
317
     */
318 2
    public function can(string $permissionName, array $params = []): bool
319
    {
320 2
        if ($this->accessChecker === null) {
321 1
            return false;
322
        }
323
324 1
        return $this->accessChecker->userHasPermission($this->getId(), $permissionName, $params);
325
    }
326
}
327