Test Failed
Pull Request — master (#24)
by Sergei
01:58
created

Authenticator::setIdentity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\User;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Throwable;
9
use Yiisoft\Auth\IdentityInterface;
10
use Yiisoft\Auth\IdentityRepositoryInterface;
11
use Yiisoft\Session\SessionInterface;
12
use Yiisoft\User\Event\AfterLogin;
13
use Yiisoft\User\Event\AfterLogout;
14
use Yiisoft\User\Event\BeforeLogin;
15
use Yiisoft\User\Event\BeforeLogout;
16
17
final class Authenticator
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
    private ?IdentityInterface $identity = null;
24
25
    /**
26
     * @var int|null the number of seconds in which the user will be logged out automatically in case of
27
     * remaining inactive. If this property is not set, the user will be logged out after
28
     * the current session expires.
29
     */
30
    private ?int $authTimeout = null;
31
32
    /**
33
     * @var int|null the number of seconds in which the user will be logged out automatically
34
     * regardless of activity.
35
     */
36
    private ?int $absoluteAuthTimeout = null;
37
38
    private IdentityRepositoryInterface $identityRepository;
39
    private EventDispatcherInterface $eventDispatcher;
40
    private ?SessionInterface $session;
41
42
    /**
43
     * @param SessionInterface|null $session session to persist authentication status across multiple requests.
44
     * If not set, authentication has to be performed on each request, which is often the case for stateless
45
     * application such as RESTful API.
46
     */
47
    public function __construct(
48
        IdentityRepositoryInterface $identityRepository,
49
        EventDispatcherInterface $eventDispatcher,
50
        SessionInterface $session = null
51
    ) {
52
        $this->identityRepository = $identityRepository;
53
        $this->eventDispatcher = $eventDispatcher;
54
        $this->session = $session;
55
    }
56
57
    public function setAuthTimeout(int $timeout = null): self
58
    {
59
        $this->authTimeout = $timeout;
60
        return $this;
61
    }
62
63
    public function setAbsoluteAuthTimeout(int $timeout = null): self
64
    {
65
        $this->absoluteAuthTimeout = $timeout;
66
        return $this;
67
    }
68
69
    /**
70
     * Returns the identity object associated with the currently logged-in user.
71
     * This method read the user's authentication data
72
     * stored in session and reconstruct the corresponding identity object, if it has not done so before.
73
     *
74
     * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before.
75
     *
76
     * @throws Throwable
77
     *
78
     * @return IdentityInterface the identity object associated with the currently logged-in user.
79
     *
80
     * @see logout()
81
     * @see login()
82
     */
83
    public function getIdentity(bool $autoRenew = true): IdentityInterface
84
    {
85
        if ($this->identity !== null) {
86
            return $this->identity;
87
        }
88
        if ($this->session === null || !$autoRenew) {
89
            return new GuestIdentity();
90
        }
91
        try {
92
            $this->renewAuthStatus();
93
        } catch (Throwable $e) {
94
            $this->identity = null;
95
            throw $e;
96
        }
97
98
        /** @psalm-suppress TypeDoesNotContainType */
99
        return $this->identity ?? new GuestIdentity();
100
    }
101
102
    /**
103
     * Sets the user identity object.
104
     *
105
     * Note that this method does not deal with session. You should usually use {@see switchIdentity()}
106
     * to change the identity of the current user.
107
     *
108
     * @param IdentityInterface $identity the identity object associated with the currently logged user.
109
     * Use {{@see GuestIdentity}} to indicate that the current user is a guest.
110
     */
111
    public function setIdentity(IdentityInterface $identity): void
112
    {
113
        $this->identity = $identity;
114
    }
115
116
    /**
117
     * Switches to a new identity for the current user.
118
     *
119
     * This method use session to store the user identity information.
120
     * Please refer to {@see login()} for more details.
121
     *
122
     * This method is mainly called by {@see login()} and {@see logout()}
123
     * when the current user needs to be associated with the corresponding identity information.
124
     *
125
     * @param IdentityInterface $identity the identity information to be associated with the current user.
126
     * In order to indicate that the user is guest, use {{@see GuestIdentity}}.
127
     */
128
    public function switchIdentity(IdentityInterface $identity): void
129
    {
130
        $this->setIdentity($identity);
131
        if ($this->session === null) {
132
            return;
133
        }
134
135
        $this->session->regenerateID();
136
137
        $this->session->remove(self::SESSION_AUTH_ID);
138
        $this->session->remove(self::SESSION_AUTH_EXPIRE);
139
140
        if ($identity->getId() === null) {
141
            return;
142
        }
143
        $this->session->set(self::SESSION_AUTH_ID, $identity->getId());
144
        if ($this->authTimeout !== null) {
145
            $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
146
        }
147
        if ($this->absoluteAuthTimeout !== null) {
148
            $this->session->set(self::SESSION_AUTH_ABSOLUTE_EXPIRE, time() + $this->absoluteAuthTimeout);
149
        }
150
    }
151
152
    /**
153
     * Logs in a user.
154
     *
155
     * After logging in a user:
156
     * - the user's identity information is obtainable from the {@see getIdentity()}
157
     * - the identity information will be stored in session and be available in the next requests as long as the session
158
     *   remains active or till the user closes the browser. Some browsers, such as Chrome, are keeping session when
159
     *   browser is re-opened.
160
     *
161
     * @param IdentityInterface $identity the user identity (which should already be authenticated)
162
     *
163
     * @return bool whether the user is logged in
164
     */
165
    public function login(IdentityInterface $identity): bool
166
    {
167
        if ($this->beforeLogin($identity)) {
168
            $this->switchIdentity($identity);
169
            $this->afterLogin($identity);
170
        }
171
        return !$this->isGuest();
0 ignored issues
show
Bug introduced by
The method isGuest() does not exist on Yiisoft\User\Authenticator. ( Ignorable by Annotation )

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

171
        return !$this->/** @scrutinizer ignore-call */ isGuest();

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...
172
    }
173
174
    /**
175
     * This method is called before logging in a user.
176
     * The default implementation will trigger the {@see BeforeLogin} event.
177
     * If you override this method, make sure you call the parent implementation
178
     * so that the event is triggered.
179
     *
180
     * @param IdentityInterface $identity the user identity information
181
     *
182
     * @return bool whether the user should continue to be logged in
183
     */
184
    private function beforeLogin(IdentityInterface $identity): bool
185
    {
186
        $event = new BeforeLogin($identity);
187
        $this->eventDispatcher->dispatch($event);
188
        return $event->isValid();
189
    }
190
191
    /**
192
     * This method is called after the user is successfully logged in.
193
     *
194
     * @param IdentityInterface $identity the user identity information
195
     */
196
    private function afterLogin(IdentityInterface $identity): void
197
    {
198
        $this->eventDispatcher->dispatch(new AfterLogin($identity));
199
    }
200
201
    /**
202
     * Logs out the current user.
203
     * This will remove authentication-related session data.
204
     * If `$destroySession` is true, all session data will be removed.
205
     *
206
     * @param bool $destroySession whether to destroy the whole session. Defaults to true.
207
     *
208
     * @throws Throwable
209
     *
210
     * @return bool whether the user is logged out
211
     */
212
    public function logout(bool $destroySession = true): bool
213
    {
214
        $identity = $this->getIdentity();
215
        if ($this->isGuest()) {
216
            return false;
217
        }
218
        if ($this->beforeLogout($identity)) {
219
            $this->switchIdentity(new GuestIdentity());
220
            if ($destroySession && $this->session) {
221
                $this->session->destroy();
222
            }
223
224
            $this->afterLogout($identity);
225
        }
226
        return $this->isGuest();
227
    }
228
229
    /**
230
     * This method is invoked when calling {@see logout()} to log out a user.
231
     *
232
     * @param IdentityInterface $identity the user identity information
233
     *
234
     * @return bool whether the user should continue to be logged out
235
     */
236
    private function beforeLogout(IdentityInterface $identity): bool
237
    {
238
        $event = new BeforeLogout($identity);
239
        $this->eventDispatcher->dispatch($event);
240
        return $event->isValid();
241
    }
242
243
    /**
244
     * This method is invoked right after a user is logged out via {@see logout()}.
245
     *
246
     * @param IdentityInterface $identity the user identity information
247
     */
248
    private function afterLogout(IdentityInterface $identity): void
249
    {
250
        $this->eventDispatcher->dispatch(new AfterLogout($identity));
251
    }
252
253
    /**
254
     * Updates the authentication status using the information from session.
255
     *
256
     * This method will try to determine the user identity using a session variable.
257
     *
258
     * If {@see authTimeout} is set, this method will refresh the timer.
259
     *
260
     * @throws Throwable
261
     */
262
    private function renewAuthStatus(): void
263
    {
264
        if ($this->session === null) {
265
            $this->setIdentity(new GuestIdentity());
266
            return;
267
        }
268
269
        /** @var mixed $id */
270
        $id = $this->session->get(self::SESSION_AUTH_ID);
271
272
        $identity = null;
273
        if ($id !== null) {
274
            $identity = $this->identityRepository->findIdentity((string)$id);
275
        }
276
        if ($identity === null) {
277
            $identity = new GuestIdentity();
278
        }
279
        $this->setIdentity($identity);
280
281
        if (
282
            !($identity instanceof GuestIdentity) &&
283
            ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)
284
        ) {
285
            /** @var mixed $expire */
286
            $expire = $this->authTimeout !== null ? $this->session->get(self::SESSION_AUTH_EXPIRE) : null;
287
            if ($expire !== null) {
288
                $expire = (int)$expire;
289
            }
290
291
            /** @var mixed $expireAbsolute */
292
            $expireAbsolute = $this->absoluteAuthTimeout !== null
293
                ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE)
294
                : null;
295
            if ($expireAbsolute !== null) {
296
                $expireAbsolute = (int)$expire;
297
            }
298
299
            if (($expire !== null && $expire < time()) || ($expireAbsolute !== null && $expireAbsolute < time())) {
300
                $this->logout(false);
301
            } elseif ($this->authTimeout !== null) {
302
                $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
303
            }
304
        }
305
    }
306
}
307