• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / security / 26321307209

23 May 2026 02:39AM UTC coverage: 92.487%. Remained the same
26321307209

push

github

dg
added guest identity

11 of 11 new or added lines in 1 file covered. (100.0%)

6 existing lines in 1 file now uncovered.

554 of 599 relevant lines covered (92.49%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.62
/src/Security/User.php
1
<?php declare(strict_types=1);
1✔
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
namespace Nette\Security;
9

10
use Nette;
11
use Nette\Utils\Arrays;
12
use function func_get_args;
13

14

15
/**
16
 * User authentication and authorization.
17
 *
18
 * @property-read bool $loggedIn
19
 * @property-read ?IIdentity $identity
20
 * @property-read string|int|null $id
21
 * @property-read list<string> $roles
22
 * @property-read ?int $logoutReason
23
 * @property   IAuthenticator $authenticator
24
 * @property   Authorizator $authorizator
25
 */
26
class User
27
{
28
        use Nette\SmartObject;
29

30
        /** Log-out reason */
31
        public const
32
                LogoutManual = 1,
33
                LogoutInactivity = 2;
34

35
        /** @deprecated use User::LogoutManual */
36
        public const LOGOUT_MANUAL = self::LogoutManual;
37

38
        /** @deprecated use User::LogoutManual */
39
        public const MANUAL = self::LogoutManual;
40

41
        /** @deprecated use User::LogoutInactivity */
42
        public const LOGOUT_INACTIVITY = self::LogoutInactivity;
43

44
        /** @deprecated use User::LogoutInactivity */
45
        public const INACTIVITY = self::LogoutInactivity;
46

47
        /** role for an unauthenticated user, unless a guest identity provides its own roles */
48
        public string $guestRole = 'guest';
49

50
        /** default role for authenticated user without own identity */
51
        public string $authenticatedRole = 'authenticated';
52

53
        /** keep identity available (via getIdentity() and getId()) after logout or expiration; depends on the storage implementation */
54
        public bool $persistIdentity = true;
55

56
        /** @var array<callable(static): void>  Occurs when the user is successfully logged in */
57
        public array $onLoggedIn = [];
58

59
        /** @var array<callable(static): void>  Occurs when the user is logged out */
60
        public array $onLoggedOut = [];
61

62
        private ?IIdentity $identity = null;
63
        private ?bool $authenticated = null;
64
        private ?int $logoutReason = null;
65
        private ?IIdentity $guestIdentity = null;
66
        private bool $guestIdentityResolved = false;
67

68

69
        public function __construct(
1✔
70
                private UserStorage $storage,
71
                private ?IAuthenticator $authenticator = null,
72
                private ?Authorizator $authorizator = null,
73
        ) {
74
        }
1✔
75

76

77
        final public function getStorage(): UserStorage
78
        {
UNCOV
79
                return $this->storage;
×
80
        }
81

82

83
        /********************* Authentication ****************d*g**/
84

85

86
        /**
87
         * Authenticates the user. Accepts username and password, or an IIdentity directly.
88
         * @param  string|IIdentity  $username  username or identity
89
         * @throws AuthenticationException if authentication was not successful
90
         */
91
        public function login(
1✔
92
                string|IIdentity $username,
93
                #[\SensitiveParameter]
94
                ?string $password = null,
95
        ): void
96
        {
97
                $this->logout(clearIdentity: true);
1✔
98
                if ($username instanceof IIdentity) {
1✔
99
                        $this->identity = $username;
1✔
100
                } else {
101
                        $authenticator = $this->getAuthenticator();
1✔
102
                        $this->identity = $authenticator instanceof Authenticator
1✔
103
                                ? $authenticator->authenticate(...func_get_args())
1✔
UNCOV
104
                                : $authenticator->authenticate(func_get_args());
×
105
                }
106

107
                $id = $this->authenticator instanceof IdentityHandler
1✔
108
                        ? $this->authenticator->sleepIdentity($this->identity)
1✔
109
                        : $this->identity;
1✔
110

111
                $this->storage->saveAuthentication($id);
1✔
112
                $this->authenticated = true;
1✔
113
                $this->logoutReason = null;
1✔
114
                Arrays::invoke($this->onLoggedIn, $this);
1✔
115
        }
1✔
116

117

118
        /**
119
         * Logs out the user from the current session. The identity is kept available afterwards,
120
         * unless $clearIdentity is set or the $persistIdentity property is disabled.
121
         */
122
        final public function logout(bool $clearIdentity = false): void
1✔
123
        {
124
                $clearIdentity = $clearIdentity || !$this->persistIdentity;
1✔
125
                $logged = $this->isLoggedIn();
1✔
126
                $this->storage->clearAuthentication($clearIdentity);
1✔
127
                $this->authenticated = false;
1✔
128
                $this->logoutReason = self::LogoutManual;
1✔
129
                if ($logged) {
1✔
130
                        Arrays::invoke($this->onLoggedOut, $this);
1✔
131
                }
132

133
                $this->identity = $clearIdentity ? null : $this->identity;
1✔
134
        }
1✔
135

136

137
        /**
138
         * Checks whether the user is authenticated.
139
         */
140
        final public function isLoggedIn(): bool
141
        {
142
                $this->loadStoredData();
1✔
143
                return (bool) $this->authenticated;
1✔
144
        }
145

146

147
        /**
148
         * Returns the user identity. When not logged in, this is the retained identity (unless $persistIdentity
149
         * is disabled) or a guest identity if the authenticator provides one; null otherwise.
150
         */
151
        final public function getIdentity(): ?IIdentity
152
        {
153
                $this->loadStoredData();
1✔
154
                return $this->identity ?? $this->resolveGuestIdentity();
1✔
155
        }
156

157

158
        private function loadStoredData(): void
159
        {
160
                if ($this->authenticated !== null) {
1✔
161
                        return;
1✔
162
                }
163

164
                (function (bool $state, ?IIdentity $id, ?int $reason) use (&$identity) {
1✔
165
                        $identity = $id;
1✔
166
                        $this->authenticated = $state;
1✔
167
                        $this->logoutReason = $reason;
1✔
168
                })(...$this->storage->getState());
1✔
169

170
                $identity = $identity && $this->authenticator instanceof IdentityHandler
1✔
171
                        ? $this->authenticator->wakeupIdentity($identity)
1✔
172
                        : $identity;
1✔
173
                $this->authenticated = $this->authenticated && $identity !== null;
1✔
174
                $this->identity = !$this->authenticated && !$this->persistIdentity ? null : $identity;
1✔
175
        }
1✔
176

177

178
        /** Returns the guest identity provided by the IdentityHandler authenticator, or null. */
179
        private function resolveGuestIdentity(): ?IIdentity
180
        {
181
                if (!$this->guestIdentityResolved) {
1✔
182
                        $this->guestIdentityResolved = true;
1✔
183
                        $this->guestIdentity = $this->authenticator instanceof IdentityHandler && method_exists($this->authenticator, 'getGuestIdentity')
1✔
184
                                ? $this->authenticator->getGuestIdentity()
1✔
185
                                : null;
1✔
186
                }
187

188
                return $this->guestIdentity;
1✔
189
        }
190

191

192
        /**
193
         * Returns the ID of the identity returned by getIdentity(), so it may be the retained or guest
194
         * identity's ID even when not logged in; null if there is no identity.
195
         */
196
        public function getId(): string|int|null
197
        {
198
                $identity = $this->getIdentity();
1✔
199
                return $identity ? $identity->getId() : null;
1✔
200
        }
201

202

203
        /**
204
         * Discards the cached authentication state and identity, forcing a reload on next access.
205
         */
206
        final public function refreshStorage(): void
207
        {
208
                $this->identity = $this->authenticated = $this->logoutReason = null;
1✔
209
                $this->guestIdentity = null;
1✔
210
                $this->guestIdentityResolved = false;
1✔
211
        }
1✔
212

213

214
        /**
215
         * Sets authentication handler.
216
         */
217
        public function setAuthenticator(IAuthenticator $handler): static
1✔
218
        {
219
                $this->authenticator = $handler;
1✔
220
                $this->guestIdentityResolved = false;
1✔
221
                return $this;
1✔
222
        }
223

224

225
        /**
226
         * Returns authentication handler.
227
         */
228
        final public function getAuthenticator(): IAuthenticator
229
        {
230
                if (!$this->authenticator) {
1✔
231
                        throw new Nette\InvalidStateException('Authenticator has not been set.');
1✔
232
                }
233

234
                return $this->authenticator;
1✔
235
        }
236

237

238
        /**
239
         * Returns authentication handler, or null if none is set.
240
         */
241
        final public function getAuthenticatorIfExists(): ?IAuthenticator
242
        {
UNCOV
243
                return $this->authenticator;
×
244
        }
245

246

247
        /** @deprecated */
248
        final public function hasAuthenticator(): bool
249
        {
UNCOV
250
                return (bool) $this->authenticator;
×
251
        }
252

253

254
        /**
255
         * Enables log out after inactivity (like '20 minutes'). The identity is kept available afterwards,
256
         * unless $clearIdentity is set or the $persistIdentity property is disabled.
257
         */
258
        public function setExpiration(?string $expire, bool $clearIdentity = false): static
1✔
259
        {
260
                $this->storage->setExpiration($expire, $clearIdentity || !$this->persistIdentity);
1✔
261
                return $this;
1✔
262
        }
263

264

265
        /**
266
         * Returns the logout reason: LogoutManual or LogoutInactivity, or null if not applicable.
267
         */
268
        final public function getLogoutReason(): ?int
269
        {
270
                return $this->logoutReason;
1✔
271
        }
272

273

274
        /********************* Authorization ****************d*g**/
275

276

277
        /**
278
         * Returns effective roles derived from the login state, not from the (possibly retained) identity.
279
         * Logged in: the identity's roles, or authenticatedRole. Otherwise: the guest identity's roles, or guestRole.
280
         * @return list<string>
281
         */
282
        public function getRoles(): array
283
        {
284
                if (!$this->isLoggedIn()) {
1✔
285
                        return $this->resolveGuestIdentity()?->getRoles() ?? [$this->guestRole];
1✔
286
                }
287

288
                $identity = $this->getIdentity();
1✔
289
                return $identity?->getRoles() ?? [$this->authenticatedRole];
1✔
290
        }
291

292

293
        /**
294
         * Checks whether the user has the specified effective role.
295
         */
296
        final public function isInRole(string $role): bool
1✔
297
        {
298
                foreach ($this->getRoles() as $r) {
1✔
299
                        if ($role === ($r instanceof Role ? $r->getRoleId() : $r)) {
1✔
300
                                return true;
1✔
301
                        }
302
                }
303

304
                return false;
1✔
305
        }
306

307

308
        /**
309
         * Checks whether the user has access to the given resource and privilege.
310
         * Null means all resources or all privileges.
311
         */
312
        public function isAllowed(mixed $resource = Authorizator::All, mixed $privilege = Authorizator::All): bool
1✔
313
        {
314
                foreach ($this->getRoles() as $role) {
1✔
315
                        if ($this->getAuthorizator()->isAllowed($role, $resource, $privilege)) {
1✔
316
                                return true;
1✔
317
                        }
318
                }
319

320
                return false;
1✔
321
        }
322

323

324
        /**
325
         * Sets authorization handler.
326
         */
327
        public function setAuthorizator(Authorizator $handler): static
1✔
328
        {
329
                $this->authorizator = $handler;
1✔
330
                return $this;
1✔
331
        }
332

333

334
        /**
335
         * Returns current authorization handler.
336
         */
337
        final public function getAuthorizator(): Authorizator
338
        {
339
                if (!$this->authorizator) {
1✔
340
                        throw new Nette\InvalidStateException('Authorizator has not been set.');
1✔
341
                }
342

343
                return $this->authorizator;
1✔
344
        }
345

346

347
        /**
348
         * Returns authorization handler, or null if none is set.
349
         */
350
        final public function getAuthorizatorIfExists(): ?Authorizator
351
        {
UNCOV
352
                return $this->authorizator;
×
353
        }
354

355

356
        /** @deprecated */
357
        final public function hasAuthorizator(): bool
358
        {
UNCOV
359
                return (bool) $this->authorizator;
×
360
        }
361
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc