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

nette / security / 30133545030

24 Jul 2026 11:19PM UTC coverage: 92.845% (+0.4%) from 92.487%
30133545030

push

github

dg
SimpleAuthenticator: added support for hashed passwords

Stored passwords may now be crypt-format hashes and can be freely
mixed with plain-text ones. Hashes are detected by the '$ident$'
prefix followed by at least 20 characters, the same way
password_verify() identifies them, so besides
bcrypt and argon2 the legacy crypt() formats work too, and a hash
with an algorithm unknown to the PHP build fails closed instead of
being compared as plain text. Plain-text comparison is timing-safe
via hash_equals().

Hashes work in the 'users' configuration section without any extra
option; argon2 hashes must be quoted in NEON because of '=' and ','.

4 of 4 new or added lines in 2 files covered. (100.0%)

19 existing lines in 4 files now uncovered.

558 of 601 relevant lines covered (92.85%)

0.93 hits per line

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

77.78
/src/Bridges/SecurityHttp/SessionStorage.php
1
<?php declare(strict_types=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\Bridges\SecurityHttp;
9

10
use Nette;
11
use Nette\Http\Session;
12
use Nette\Http\SessionSection;
13
use Nette\Security\IIdentity;
14
use Nette\Security\User;
15
use function is_bool, time;
16

17

18
/**
19
 * Session storage for Nette\Security\User object.
20
 */
21
final class SessionStorage implements Nette\Security\UserStorage
22
{
23
        private string $namespace = '';
24
        private ?SessionSection $sessionSection = null;
25
        private ?int $expireTime = null;
26
        private bool $expireIdentity = false;
27

28

29
        public function __construct(
1✔
30
                private readonly Session $sessionHandler,
31
        ) {
32
        }
1✔
33

34

35
        public function saveAuthentication(IIdentity $identity): void
1✔
36
        {
37
                $section = $this->getSessionSection();
1✔
38
                $section->set('authenticated', true);
1✔
39
                $section->set('reason', null);
1✔
40
                $section->set('authTime', time()); // informative value
1✔
41
                $section->set('identity', $identity);
1✔
42
                $this->setupExpiration();
1✔
43

44
                // Session Fixation defence
45
                $this->sessionHandler->regenerateId();
1✔
46
        }
1✔
47

48

49
        public function clearAuthentication(bool $clearIdentity): void
50
        {
51
                $section = $this->getSessionSection();
×
52
                $section->set('authenticated', false);
×
53
                $section->set('reason', User::LogoutManual);
×
54
                $section->set('authTime', null);
×
55
                if ($clearIdentity === true) {
×
56
                        $section->set('identity', null);
×
57
                }
58

59
                // Session Fixation defence
60
                $this->sessionHandler->regenerateId();
×
61
        }
62

63

64
        public function getState(): array
65
        {
66
                $section = $this->getSessionSection();
1✔
67
                return [(bool) $section->get('authenticated'), $section->get('identity'), $section->get('reason')];
1✔
68
        }
69

70

71
        public function setExpiration(?string $time, bool $clearIdentity = false): void
1✔
72
        {
73
                $this->expireTime = $time ? (int) Nette\Utils\DateTime::from($time)->format('U') : null;
1✔
74
                $this->expireIdentity = $clearIdentity;
1✔
75

76
                if ($this->sessionSection && $this->sessionSection->get('authenticated')) {
1✔
77
                        $this->setupExpiration();
1✔
78
                }
79
        }
1✔
80

81

82
        private function setupExpiration(): void
83
        {
84
                assert($this->sessionSection !== null);
85
                $section = $this->sessionSection;
1✔
86
                if ($this->expireTime) {
1✔
87
                        $section->set('expireTime', $this->expireTime);
1✔
88
                        $section->set('expireDelta', $this->expireTime - time());
1✔
89
                } else {
90
                        $section->remove(['expireTime', 'expireDelta']);
1✔
91
                }
92

93
                $section->set('expireIdentity', $this->expireIdentity);
1✔
94
                // expiration is bound to a dummy variable 'foo', so nette/http checks the time against session.gc_maxlifetime
95
                // but never wipes the section itself; expiration is handled manually in getSessionSection()
96
                $section->setExpiration($this->expireTime === null ? null : '@' . $this->expireTime, 'foo');
1✔
97
        }
1✔
98

99

100
        /**
101
         * Changes namespace; allows more users to share a session.
102
         */
103
        public function setNamespace(string $namespace): static
104
        {
105
                if ($this->namespace !== $namespace) {
×
UNCOV
106
                        $this->namespace = $namespace;
×
UNCOV
107
                        $this->sessionSection = null;
×
108
                }
109

UNCOV
110
                return $this;
×
111
        }
112

113

114
        /**
115
         * Returns current namespace.
116
         */
117
        public function getNamespace(): string
118
        {
UNCOV
119
                return $this->namespace;
×
120
        }
121

122

123
        /**
124
         * Returns and initializes $this->sessionSection.
125
         */
126
        private function getSessionSection(): SessionSection
127
        {
128
                if ($this->sessionSection !== null) {
1✔
129
                        return $this->sessionSection;
1✔
130
                }
131

132
                $this->sessionSection = $section = $this->sessionHandler->getSection('Nette.Http.UserStorage/' . $this->namespace);
1✔
133

134
                if (!$section->get('identity') instanceof IIdentity || !is_bool($section->get('authenticated'))) {
1✔
135
                        $section->remove();
1✔
136
                }
137

138
                if ($section->get('authenticated') && $section->get('expireDelta') > 0) { // check time expiration
1✔
139
                        if ($section->get('expireTime') < time()) {
1✔
140
                                $section->set('reason', User::LogoutInactivity);
1✔
141
                                $section->set('authenticated', false);
1✔
142
                                if ($section->get('expireIdentity')) {
1✔
143
                                        $section->remove('identity');
1✔
144
                                }
145
                        } else {
146
                                $section->set('expireTime', time() + $section->get('expireDelta')); // sliding expiration
1✔
147
                        }
148
                }
149

150
                if (!$section->get('authenticated')) {
1✔
151
                        $section->remove(['expireTime', 'expireDelta', 'expireIdentity', 'authTime']);
1✔
152
                }
153

154
                return $this->sessionSection;
1✔
155
        }
156
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc