Passed
Push — master ( d4460f...a9a9de )
by Alexander
02:23
created

CurrentUser::overrideIdentity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\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\Session\SessionInterface;
12
use Yiisoft\User\Event\AfterLogout;
13
use Yiisoft\User\Event\AfterLogin;
14
use Yiisoft\User\Event\BeforeLogout;
15
use Yiisoft\User\Event\BeforeLogin;
16
17
/**
18
 * Maintains current identity and allows logging in and out using it.
19
 */
20
final class CurrentUser
21
{
22
    private const SESSION_AUTH_ID = '__auth_id';
23
    private const SESSION_AUTH_EXPIRE = '__auth_expire';
24
    private const SESSION_AUTH_ABSOLUTE_EXPIRE = '__auth_absolute_expire';
25
26
    private IdentityRepositoryInterface $identityRepository;
27
    private EventDispatcherInterface $eventDispatcher;
28
    private ?SessionInterface $session;
29
    private ?AccessCheckerInterface $accessChecker = null;
30
31
    private ?IdentityInterface $identity = null;
32
    private ?IdentityInterface $identityOverride = null;
33
34
    /**
35
     * @var int|null the number of seconds in which the user will be logged out automatically in case of
36
     * remaining inactive. If this property is not set, the user will be logged out after
37
     * the current session expires.
38
     */
39
    private ?int $authTimeout = null;
40
41
    /**
42
     * @var int|null the number of seconds in which the user will be logged out automatically
43
     * regardless of activity.
44
     */
45
    private ?int $absoluteAuthTimeout = null;
46
47 37
    public function __construct(
48
        IdentityRepositoryInterface $identityRepository,
49
        EventDispatcherInterface $eventDispatcher,
50
        ?SessionInterface $session = null
51
    ) {
52 37
        $this->identityRepository = $identityRepository;
53 37
        $this->eventDispatcher = $eventDispatcher;
54 37
        $this->session = $session;
55 37
    }
56
57 1
    public function setAccessChecker(AccessCheckerInterface $accessChecker): void
58
    {
59 1
        $this->accessChecker = $accessChecker;
60 1
    }
61
62 6
    public function setAuthTimeout(int $timeout = null): self
63
    {
64 6
        $this->authTimeout = $timeout;
65 6
        return $this;
66
    }
67
68 3
    public function setAbsoluteAuthTimeout(int $timeout = null): self
69
    {
70 3
        $this->absoluteAuthTimeout = $timeout;
71 3
        return $this;
72
    }
73
74
    /**
75
     * Returns the identity object associated with the currently logged-in user.
76
     */
77 32
    public function getIdentity(): IdentityInterface
78
    {
79 32
        $identity = $this->identityOverride ?? $this->identity;
80
81 32
        if ($identity === null) {
82 23
            $identity = null;
83
84 23
            $id = $this->getSavedId();
85 23
            if ($id !== null) {
86 7
                $identity = $this->identityRepository->findIdentity($id);
87
            }
88 23
            $identity = $identity ?? new GuestIdentity();
89
90 23
            $this->identity = $identity;
91
        }
92
93 32
        return $identity;
94
    }
95
96
    /**
97
     * Returns a value that uniquely represents the user.
98
     *
99
     * @see CurrentUser::getIdentity()
100
     *
101
     * @return string|null The unique identifier for the user. If `null`, it means the user is a guest.     *
102
     */
103 2
    public function getId(): ?string
104
    {
105 2
        return $this->getIdentity()->getId();
106
    }
107
108
    /**
109
     * Returns a value indicating whether the user is a guest (not authenticated).
110
     *
111
     * @see getIdentity()
112
     *
113
     * @return bool Whether the current user is a guest.
114
     */
115 20
    public function isGuest(): bool
116
    {
117 20
        return $this->getIdentity() instanceof GuestIdentity;
118
    }
119
120
    /**
121
     * Checks if the user can perform the operation as specified by the given permission.
122
     *
123
     * Note that you must provide access checker via {@see CurrentUser::setAccessChecker()} in order to use this
124
     * method. Otherwise it will always return `false`.
125
     *
126
     * @param string $permissionName The name of the permission (e.g. "edit post") that needs access check.
127
     * @param array $params Name-value pairs that would be passed to the rules associated with the roles and
128
     * permissions assigned to the user.
129
     *
130
     * @return bool Whether the user can perform the operation as specified by the given permission.
131
     */
132 2
    public function can(string $permissionName, array $params = []): bool
133
    {
134 2
        if ($this->accessChecker === null) {
135 1
            return false;
136
        }
137
138 1
        return $this->accessChecker->userHasPermission($this->getId(), $permissionName, $params);
139
    }
140
141
    /**
142
     * Logs in a user.
143
     *
144
     * @param IdentityInterface $identity The user identity (which should already be authenticated).
145
     *
146
     * @return bool Whether the user is logged in.
147
     */
148 13
    public function login(IdentityInterface $identity): bool
149
    {
150 13
        if ($this->beforeLogin($identity)) {
151 13
            $this->switchIdentity($identity);
152 13
            $this->afterLogin($identity);
153
        }
154 13
        return !$this->isGuest();
155
    }
156
157
    /**
158
     * This method is called before logging in a user.
159
     * The default implementation will trigger the {@see BeforeLogin} event.
160
     *
161
     * @param IdentityInterface $identity The user identity information.
162
     *
163
     * @return bool Whether the user should continue to be logged in.
164
     */
165 13
    private function beforeLogin(IdentityInterface $identity): bool
166
    {
167 13
        $event = new BeforeLogin($identity);
168 13
        $this->eventDispatcher->dispatch($event);
169 13
        return $event->isValid();
170
    }
171
172
    /**
173
     * This method is called after the user is successfully logged in.
174
     *
175
     * @param IdentityInterface $identity The user identity information.
176
     */
177 13
    private function afterLogin(IdentityInterface $identity): void
178
    {
179 13
        $this->eventDispatcher->dispatch(new AfterLogin($identity));
180 13
    }
181
182
    /**
183
     * Logs out the current user.
184
     *
185
     * @return bool Whether the user is logged out.
186
     */
187 4
    public function logout(): bool
188
    {
189 4
        if ($this->isGuest()) {
190 1
            return false;
191
        }
192
193 3
        $identity = $this->getIdentity();
194 3
        if ($this->beforeLogout($identity)) {
195 3
            $this->switchIdentity(new GuestIdentity());
196 3
            $this->afterLogout($identity);
197
        }
198
199 3
        return $this->isGuest();
200
    }
201
202
    /**
203
     * This method is invoked when calling {@see CurrentUser::logout()} to log out a user.
204
     *
205
     * @param IdentityInterface $identity The user identity information.
206
     *
207
     * @return bool Whether the user should continue to be logged out.
208
     */
209 3
    private function beforeLogout(IdentityInterface $identity): bool
210
    {
211 3
        $event = new BeforeLogout($identity);
212 3
        $this->eventDispatcher->dispatch($event);
213 3
        return $event->isValid();
214
    }
215
216
    /**
217
     * This method is invoked right after a user is logged out via {@see CurrentUser::logout()}.
218
     *
219
     * @param IdentityInterface $identity The user identity information.
220
     */
221 3
    private function afterLogout(IdentityInterface $identity): void
222
    {
223 3
        $this->eventDispatcher->dispatch(new AfterLogout($identity));
224 3
    }
225
226 3
    public function overrideIdentity(IdentityInterface $identity): void
227
    {
228 3
        $this->identityOverride = $identity;
229 3
    }
230
231 1
    public function clearIdentityOverride(): void
232
    {
233 1
        $this->identityOverride = null;
234 1
    }
235
236
    /**
237
     * Switches to a new identity for the current user.
238
     *
239
     * This method is called by {@see CurrentUser::login()} and {@see CurrentUser::logout()}
240
     * when the current user needs to be associated with the corresponding identity information.
241
     *
242
     * @param IdentityInterface $identity The identity information to be associated with the current user.
243
     * In order to indicate that the user is guest, use {@see GuestIdentity}.
244
     */
245 13
    private function switchIdentity(IdentityInterface $identity): void
246
    {
247 13
        $this->identity = $identity;
248 13
        $this->saveId($identity->getId());
249 13
    }
250
251 23
    private function getSavedId(): ?string
252
    {
253 23
        if ($this->session === null) {
254 1
            return null;
255
        }
256
257
        /** @var mixed $id */
258 22
        $id = $this->session->get(self::SESSION_AUTH_ID);
259
260
        if (
261 22
            $id !== null &&
262 22
            ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)
263
        ) {
264 7
            $expire = $this->getExpire();
265 7
            $expireAbsolute = $this->getExpireAbsolute();
266
267
            if (
268 7
                ($expire !== null && $expire < time()) ||
269 7
                ($expireAbsolute !== null && $expireAbsolute < time())
270
            ) {
271 2
                $this->saveId(null);
272 2
                return null;
273
            }
274
275 5
            if ($this->authTimeout !== null) {
276 3
                $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
277
            }
278
        }
279
280 20
        return $id === null ? null : (string)$id;
281
    }
282
283 7
    private function getExpire(): ?int
284
    {
285
        /**
286
         * @var mixed $expire
287
         * @psalm-suppress PossiblyNullReference
288
         */
289 7
        $expire = $this->authTimeout !== null
290 4
            ? $this->session->get(self::SESSION_AUTH_EXPIRE)
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

290
            ? $this->session->/** @scrutinizer ignore-call */ get(self::SESSION_AUTH_EXPIRE)

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...
291 7
            : null;
292 7
        return $expire !== null ? (int)$expire : null;
293
    }
294
295 7
    private function getExpireAbsolute(): ?int
296
    {
297
        /**
298
         * @var mixed $expire
299
         * @psalm-suppress PossiblyNullReference
300
         */
301 7
        $expire = $this->absoluteAuthTimeout !== null
302 3
            ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE)
303 7
            : null;
304 7
        return $expire !== null ? (int)$expire : null;
305
    }
306
307 15
    private function saveId(?string $id): void
308
    {
309 15
        if ($this->session === null) {
310 1
            return;
311
        }
312
313 14
        $this->session->regenerateID();
314
315 14
        $this->session->remove(self::SESSION_AUTH_ID);
316 14
        $this->session->remove(self::SESSION_AUTH_EXPIRE);
317
318 14
        if ($id === null) {
319 5
            return;
320
        }
321
322 12
        $this->session->set(self::SESSION_AUTH_ID, $id);
323 12
        if ($this->authTimeout !== null) {
324 2
            $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout);
325
        }
326 12
        if ($this->absoluteAuthTimeout !== null) {
327 1
            $this->session->set(self::SESSION_AUTH_ABSOLUTE_EXPIRE, time() + $this->absoluteAuthTimeout);
328
        }
329 12
    }
330
}
331