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 | use Yiisoft\User\Guest\GuestIdentity; |
||
17 | use Yiisoft\User\Guest\GuestIdentityFactory; |
||
18 | use Yiisoft\User\Guest\GuestIdentityFactoryInterface; |
||
19 | use Yiisoft\User\Guest\GuestIdentityInterface; |
||
20 | |||
21 | use function time; |
||
22 | |||
23 | /** |
||
24 | * Maintains current identity and allows logging in and out using it. |
||
25 | */ |
||
26 | final class CurrentUser |
||
27 | { |
||
28 | private const SESSION_AUTH_ID = '__auth_id'; |
||
29 | private const SESSION_AUTH_EXPIRE = '__auth_expire'; |
||
30 | private const SESSION_AUTH_ABSOLUTE_EXPIRE = '__auth_absolute_expire'; |
||
31 | private GuestIdentityFactoryInterface $guestIdentityFactory; |
||
32 | private ?AccessCheckerInterface $accessChecker = null; |
||
33 | private ?SessionInterface $session = null; |
||
34 | |||
35 | private ?IdentityInterface $identity = null; |
||
36 | private ?IdentityInterface $identityOverride = null; |
||
37 | |||
38 | private ?int $authTimeout = null; |
||
39 | private ?int $absoluteAuthTimeout = null; |
||
40 | |||
41 | 50 | public function __construct( |
|
42 | private IdentityRepositoryInterface $identityRepository, |
||
43 | private EventDispatcherInterface $eventDispatcher, |
||
44 | GuestIdentityFactoryInterface $guestIdentityFactory = null |
||
45 | ) { |
||
46 | 50 | $this->guestIdentityFactory = $guestIdentityFactory ?? new GuestIdentityFactory(); |
|
47 | } |
||
48 | |||
49 | /** |
||
50 | * Returns a new instance with the specified session to store current user ID and auth timeouts. |
||
51 | * |
||
52 | * @param SessionInterface $session The session instance. |
||
53 | */ |
||
54 | 42 | public function withSession(SessionInterface $session): self |
|
55 | { |
||
56 | 42 | $new = clone $this; |
|
57 | 42 | $new->session = $session; |
|
58 | 42 | return $new; |
|
59 | } |
||
60 | |||
61 | /** |
||
62 | * Returns a new instance with the specified access checker to check user permissions {@see can()}. |
||
63 | * |
||
64 | * @param AccessCheckerInterface $accessChecker The access checker instance. |
||
65 | */ |
||
66 | 2 | public function withAccessChecker(AccessCheckerInterface $accessChecker): self |
|
67 | { |
||
68 | 2 | $new = clone $this; |
|
69 | 2 | $new->accessChecker = $accessChecker; |
|
70 | 2 | return $new; |
|
71 | } |
||
72 | |||
73 | /** |
||
74 | * Returns a new instance with the specified number of seconds in which |
||
75 | * the user will be logged out automatically in case of remaining inactive. |
||
76 | * |
||
77 | * @param int $timeout The number of seconds in which the user will be logged out automatically in case of |
||
78 | * remaining inactive. Default is `null`, the user will be logged out after the current session expires. |
||
79 | */ |
||
80 | 7 | public function withAuthTimeout(int $timeout): self |
|
81 | { |
||
82 | 7 | $new = clone $this; |
|
83 | 7 | $new->authTimeout = $timeout; |
|
84 | 7 | return $new; |
|
85 | } |
||
86 | |||
87 | /** |
||
88 | * Returns a new instance with the specified number of seconds in which |
||
89 | * the user will be logged out automatically regardless of activity. |
||
90 | * |
||
91 | * @param int $timeout The number of seconds in which the user will be logged out automatically regardless |
||
92 | * of activity. Default is `null`, the user will be logged out after the current session expires. |
||
93 | */ |
||
94 | 5 | public function withAbsoluteAuthTimeout(int $timeout): self |
|
95 | { |
||
96 | 5 | $new = clone $this; |
|
97 | 5 | $new->absoluteAuthTimeout = $timeout; |
|
98 | 5 | return $new; |
|
99 | } |
||
100 | |||
101 | /** |
||
102 | * Returns the identity object associated with the currently logged-in user. |
||
103 | */ |
||
104 | 43 | public function getIdentity(): IdentityInterface |
|
105 | { |
||
106 | 43 | $identity = $this->identityOverride ?? $this->identity; |
|
107 | |||
108 | 43 | if ($identity === null) { |
|
109 | 36 | $id = $this->getSavedId(); |
|
110 | |||
111 | 36 | if ($id !== null) { |
|
112 | 8 | $identity = $this->identityRepository->findIdentity($id); |
|
113 | } |
||
114 | |||
115 | 36 | $identity ??= $this->guestIdentityFactory->create(); |
|
116 | 36 | $this->identity = $identity; |
|
117 | } |
||
118 | |||
119 | 43 | return $identity; |
|
120 | } |
||
121 | |||
122 | /** |
||
123 | * Returns a value that uniquely represents the user. |
||
124 | * |
||
125 | * @return string|null The unique identifier for the user. If `null`, it means the user is a guest. |
||
126 | * |
||
127 | * @see getIdentity() |
||
128 | */ |
||
129 | 3 | public function getId(): ?string |
|
130 | { |
||
131 | 3 | return $this->getIdentity()->getId(); |
|
132 | } |
||
133 | |||
134 | /** |
||
135 | * Returns a value indicating whether the user is a guest (not authenticated). |
||
136 | * |
||
137 | * @return bool Whether the current user is a guest. |
||
138 | * |
||
139 | * @see getIdentity() |
||
140 | */ |
||
141 | 30 | public function isGuest(): bool |
|
142 | { |
||
143 | 30 | return $this->getIdentity() instanceof GuestIdentityInterface; |
|
144 | } |
||
145 | |||
146 | /** |
||
147 | * Checks if the user can perform the operation as specified by the given permission. |
||
148 | * |
||
149 | * Note that you must provide access checker via {@see withAccessChecker()} in order to use this |
||
150 | * method. Otherwise, it will always return `false`. |
||
151 | * |
||
152 | * @param string $permissionName The name of the permission (e.g. "edit post") that needs access check. |
||
153 | * @param array $params Name-value pairs that would be passed to the rules associated with the roles and |
||
154 | * permissions assigned to the user. |
||
155 | * |
||
156 | * @return bool Whether the user can perform the operation as specified by the given permission. |
||
157 | */ |
||
158 | 2 | public function can(string $permissionName, array $params = []): bool |
|
159 | { |
||
160 | 2 | if ($this->accessChecker === null) { |
|
161 | 1 | return false; |
|
162 | } |
||
163 | |||
164 | 1 | return $this->accessChecker->userHasPermission($this->getId(), $permissionName, $params); |
|
165 | } |
||
166 | |||
167 | /** |
||
168 | * Logs in a user. |
||
169 | * |
||
170 | * @param IdentityInterface $identity The user identity (which should already be authenticated). |
||
171 | * |
||
172 | * @return bool Whether the user is logged in. |
||
173 | */ |
||
174 | 20 | public function login(IdentityInterface $identity): bool |
|
175 | { |
||
176 | 20 | if ($this->beforeLogin($identity)) { |
|
177 | 20 | $this->switchIdentity($identity); |
|
178 | 20 | $this->afterLogin($identity); |
|
179 | } |
||
180 | |||
181 | 20 | return !$this->isGuest(); |
|
182 | } |
||
183 | |||
184 | /** |
||
185 | * Logs out the current user. |
||
186 | * |
||
187 | * @return bool Whether the user is logged out. |
||
188 | */ |
||
189 | 4 | public function logout(): bool |
|
190 | { |
||
191 | 4 | if ($this->isGuest()) { |
|
192 | 1 | return false; |
|
193 | } |
||
194 | |||
195 | 3 | $identity = $this->getIdentity(); |
|
196 | |||
197 | 3 | if ($this->beforeLogout($identity)) { |
|
198 | 3 | $this->switchIdentity($this->guestIdentityFactory->create()); |
|
199 | 3 | $this->afterLogout($identity); |
|
200 | } |
||
201 | |||
202 | 3 | return $this->isGuest(); |
|
203 | } |
||
204 | |||
205 | /** |
||
206 | * Overrides identity. |
||
207 | * |
||
208 | * @param IdentityInterface $identity The identity instance to overriding. |
||
209 | */ |
||
210 | 3 | public function overrideIdentity(IdentityInterface $identity): void |
|
211 | { |
||
212 | 3 | $this->identityOverride = $identity; |
|
213 | } |
||
214 | |||
215 | /** |
||
216 | * Clears the identity override. |
||
217 | */ |
||
218 | 1 | public function clearIdentityOverride(): void |
|
219 | { |
||
220 | 1 | $this->identityOverride = null; |
|
221 | } |
||
222 | |||
223 | /** |
||
224 | * Clears the data for working with the event loop. |
||
225 | */ |
||
226 | 1 | public function clear(): void |
|
227 | { |
||
228 | 1 | $this->identity = null; |
|
229 | 1 | $this->identityOverride = null; |
|
230 | } |
||
231 | |||
232 | /** |
||
233 | * This method is called before logging in a user. |
||
234 | * The default implementation will trigger the {@see BeforeLogin} event. |
||
235 | * |
||
236 | * @param IdentityInterface $identity The user identity information. |
||
237 | * |
||
238 | * @return bool Whether the user should continue to be logged in. |
||
239 | */ |
||
240 | 20 | private function beforeLogin(IdentityInterface $identity): bool |
|
241 | { |
||
242 | 20 | $event = new BeforeLogin($identity); |
|
243 | /** @var BeforeLogin $event */ |
||
244 | 20 | $event = $this->eventDispatcher->dispatch($event); |
|
245 | 20 | return $event->isValid(); |
|
246 | } |
||
247 | |||
248 | /** |
||
249 | * This method is called after the user is successfully logged in. |
||
250 | * |
||
251 | * @param IdentityInterface $identity The user identity information. |
||
252 | */ |
||
253 | 20 | private function afterLogin(IdentityInterface $identity): void |
|
254 | { |
||
255 | 20 | $this->eventDispatcher->dispatch(new AfterLogin($identity)); |
|
256 | } |
||
257 | |||
258 | /** |
||
259 | * This method is invoked when calling {@see logout()} to log out a user. |
||
260 | * |
||
261 | * @param IdentityInterface $identity The user identity information. |
||
262 | * |
||
263 | * @return bool Whether the user should continue to be logged out. |
||
264 | */ |
||
265 | 3 | private function beforeLogout(IdentityInterface $identity): bool |
|
266 | { |
||
267 | 3 | $event = new BeforeLogout($identity); |
|
268 | /** @var BeforeLogout $event */ |
||
269 | 3 | $event = $this->eventDispatcher->dispatch($event); |
|
270 | 3 | return $event->isValid(); |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * This method is invoked right after a user is logged out via {@see logout()}. |
||
275 | * |
||
276 | * @param IdentityInterface $identity The user identity information. |
||
277 | */ |
||
278 | 3 | private function afterLogout(IdentityInterface $identity): void |
|
279 | { |
||
280 | 3 | $this->eventDispatcher->dispatch(new AfterLogout($identity)); |
|
281 | } |
||
282 | |||
283 | /** |
||
284 | * Switches to a new identity for the current user. |
||
285 | * |
||
286 | * This method is called by {@see login()} and {@see logout()} when the current |
||
287 | * user needs to be associated with the corresponding identity information. |
||
288 | * |
||
289 | * @param IdentityInterface $identity The identity information to be associated with the current user. |
||
290 | * In order to indicate that the user is guest, use {@see GuestIdentityInterface, GuestIdentity}. |
||
291 | */ |
||
292 | 20 | private function switchIdentity(IdentityInterface $identity): void |
|
293 | { |
||
294 | 20 | $this->identity = $identity; |
|
295 | 20 | $this->saveId($identity instanceof GuestIdentityInterface ? null : $identity->getId()); |
|
296 | } |
||
297 | |||
298 | 36 | private function getSavedId(): ?string |
|
299 | { |
||
300 | 36 | if ($this->session === null) { |
|
301 | 5 | return null; |
|
302 | } |
||
303 | |||
304 | /** @var mixed $id */ |
||
305 | 31 | $id = $this->session->get(self::SESSION_AUTH_ID); |
|
306 | |||
307 | 31 | if ($id !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) { |
|
308 | 7 | $expire = $this->getExpire(); |
|
309 | 7 | $expireAbsolute = $this->getExpireAbsolute(); |
|
310 | |||
311 | 7 | if (($expire !== null && $expire < time()) || ($expireAbsolute !== null && $expireAbsolute < time())) { |
|
312 | 2 | $this->saveId(null); |
|
313 | 2 | return null; |
|
314 | } |
||
315 | |||
316 | 5 | if ($this->authTimeout !== null) { |
|
317 | 3 | $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout); |
|
318 | } |
||
319 | } |
||
320 | |||
321 | 29 | return $id === null ? null : (string) $id; |
|
322 | } |
||
323 | |||
324 | 7 | private function getExpire(): ?int |
|
325 | { |
||
326 | /** |
||
327 | * @var mixed $expire |
||
328 | * |
||
329 | * @psalm-suppress PossiblyNullReference |
||
330 | */ |
||
331 | 7 | $expire = $this->authTimeout !== null |
|
332 | 4 | ? $this->session->get(self::SESSION_AUTH_EXPIRE) |
|
0 ignored issues
–
show
|
|||
333 | 3 | : null |
|
334 | 7 | ; |
|
335 | |||
336 | 7 | return $expire !== null ? (int) $expire : null; |
|
337 | } |
||
338 | |||
339 | 7 | private function getExpireAbsolute(): ?int |
|
340 | { |
||
341 | /** |
||
342 | * @var mixed $expire |
||
343 | * |
||
344 | * @psalm-suppress PossiblyNullReference |
||
345 | */ |
||
346 | 7 | $expire = $this->absoluteAuthTimeout !== null |
|
347 | 3 | ? $this->session->get(self::SESSION_AUTH_ABSOLUTE_EXPIRE) |
|
348 | 4 | : null |
|
349 | 7 | ; |
|
350 | |||
351 | 7 | return $expire !== null ? (int) $expire : null; |
|
352 | } |
||
353 | |||
354 | 22 | private function saveId(?string $id): void |
|
355 | { |
||
356 | 22 | if ($this->session === null) { |
|
357 | 4 | return; |
|
358 | } |
||
359 | |||
360 | 18 | $this->session->regenerateID(); |
|
361 | |||
362 | 18 | $this->session->remove(self::SESSION_AUTH_ID); |
|
363 | 18 | $this->session->remove(self::SESSION_AUTH_EXPIRE); |
|
364 | 18 | $this->session->remove(self::SESSION_AUTH_ABSOLUTE_EXPIRE); |
|
365 | |||
366 | 18 | if ($id === null) { |
|
367 | 5 | return; |
|
368 | } |
||
369 | |||
370 | 16 | $this->session->set(self::SESSION_AUTH_ID, $id); |
|
371 | |||
372 | 16 | if ($this->authTimeout !== null) { |
|
373 | 2 | $this->session->set(self::SESSION_AUTH_EXPIRE, time() + $this->authTimeout); |
|
374 | } |
||
375 | |||
376 | 16 | if ($this->absoluteAuthTimeout !== null) { |
|
377 | 2 | $this->session->set(self::SESSION_AUTH_ABSOLUTE_EXPIRE, time() + $this->absoluteAuthTimeout); |
|
378 | } |
||
379 | } |
||
380 | } |
||
381 |
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.