Skip to content

Commit 454f4e2

Browse files
committed
added SessionStorage & CookieStorage (replaced Nette\Http\UserStorage)
1 parent 81634d8 commit 454f4e2

4 files changed

Lines changed: 325 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
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+
declare(strict_types=1);
9+
10+
namespace Nette\Bridges\SecurityHttp;
11+
12+
use Nette;
13+
use Nette\Http;
14+
use Nette\Security\IIdentity;
15+
16+
17+
/**
18+
* Cookie storage for Nette\Security\User object.
19+
*/
20+
final class CookieStorage implements Nette\Security\UserStorage
21+
{
22+
use Nette\SmartObject;
23+
24+
/** @var Http\IRequest */
25+
private $request;
26+
27+
/** @var Http\IResponse */
28+
private $response;
29+
30+
/** @var string */
31+
private $cookieName = 'userid';
32+
33+
/** @var ?string */
34+
private $cookieDomain;
35+
36+
/** @var string */
37+
private $cookieSameSite = 'Lax';
38+
39+
/** @var ?string */
40+
private $cookieExpiration;
41+
42+
43+
public function __construct(Http\IRequest $request, Http\IResponse $response)
44+
{
45+
$this->response = $response;
46+
$this->request = $request;
47+
}
48+
49+
50+
public function saveAuthentication(IIdentity $identity): void
51+
{
52+
$this->response->setCookie(
53+
$this->cookieName,
54+
$identity->getId(),
55+
$this->cookieExpiration,
56+
null,
57+
$this->cookieDomain
58+
);
59+
}
60+
61+
62+
public function clearAuthentication(bool $clearIdentity): void
63+
{
64+
$this->response->deleteCookie(
65+
$this->cookieName,
66+
null,
67+
$this->cookieDomain
68+
);
69+
}
70+
71+
72+
public function getState(): array
73+
{
74+
$uid = $this->request->getCookie($this->cookieName);
75+
$identity = is_string($uid)
76+
? new Nette\Security\SimpleIdentity($uid)
77+
: null;
78+
return [(bool) $identity, $identity, null];
79+
}
80+
81+
82+
public function setExpiration(?string $expire, bool $clearIdentity): void
83+
{
84+
$this->cookieExpiration = $expire;
85+
}
86+
87+
88+
public function setCookieParameters(
89+
string $name = null,
90+
string $domain = null,
91+
string $sameSite = null
92+
) {
93+
$this->cookieName = $name ?? $this->cookieName;
94+
$this->cookieDomain = $domain ?? $this->cookieDomain;
95+
$this->cookieSameSite = $sameSite ?? $this->cookieSameSite;
96+
}
97+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
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+
declare(strict_types=1);
9+
10+
namespace Nette\Bridges\SecurityHttp;
11+
12+
use Nette;
13+
use Nette\Http\Session;
14+
use Nette\Http\SessionSection;
15+
use Nette\Security\IIdentity;
16+
17+
18+
/**
19+
* Session storage for Nette\Security\User object.
20+
*/
21+
final class SessionStorage implements Nette\Security\UserStorage
22+
{
23+
use Nette\SmartObject;
24+
25+
/** @var string */
26+
private $namespace = '';
27+
28+
/** @var Session */
29+
private $sessionHandler;
30+
31+
/** @var SessionSection */
32+
private $sessionSection;
33+
34+
35+
public function __construct(Session $sessionHandler)
36+
{
37+
$this->sessionHandler = $sessionHandler;
38+
}
39+
40+
41+
public function saveAuthentication(IIdentity $identity): void
42+
{
43+
$section = $this->getSessionSection(true);
44+
$section->authenticated = true;
45+
$section->reason = null;
46+
$section->authTime = time(); // informative value
47+
$section->identity = $identity;
48+
49+
// Session Fixation defence
50+
$this->sessionHandler->regenerateId();
51+
}
52+
53+
54+
public function clearAuthentication(bool $clearIdentity): void
55+
{
56+
$section = $this->getSessionSection(true);
57+
$section->authenticated = false;
58+
$section->reason = self::LOGOUT_MANUAL;
59+
$section->authTime = null;
60+
61+
// Session Fixation defence
62+
$this->sessionHandler->regenerateId();
63+
}
64+
65+
66+
public function getState(): array
67+
{
68+
$session = $this->getSessionSection(false);
69+
return $session
70+
? [(bool) $session->authenticated, $session->identity, $session->reason]
71+
: [false, null, null];
72+
}
73+
74+
75+
public function setExpiration(?string $time, bool $clearIdentity = false): void
76+
{
77+
$section = $this->getSessionSection(true);
78+
if ($time) {
79+
$time = Nette\Utils\DateTime::from($time)->format('U');
80+
$section->expireTime = $time;
81+
$section->expireDelta = $time - time();
82+
83+
} else {
84+
unset($section->expireTime, $section->expireDelta);
85+
}
86+
87+
$section->expireIdentity = (bool) $clearIdentity;
88+
$section->setExpiration($time, 'foo'); // time check
89+
}
90+
91+
92+
/**
93+
* Changes namespace; allows more users to share a session.
94+
* @return static
95+
*/
96+
public function setNamespace(string $namespace)
97+
{
98+
if ($this->namespace !== $namespace) {
99+
$this->namespace = $namespace;
100+
$this->sessionSection = null;
101+
}
102+
return $this;
103+
}
104+
105+
106+
/**
107+
* Returns current namespace.
108+
*/
109+
public function getNamespace(): string
110+
{
111+
return $this->namespace;
112+
}
113+
114+
115+
/**
116+
* Returns and initializes $this->sessionSection.
117+
*/
118+
protected function getSessionSection(bool $need): ?SessionSection
119+
{
120+
if ($this->sessionSection !== null) {
121+
return $this->sessionSection;
122+
}
123+
124+
if (!$need && !$this->sessionHandler->exists()) {
125+
return null;
126+
}
127+
128+
$this->sessionSection = $section = $this->sessionHandler->getSection('Nette.Http.UserStorage/' . $this->namespace);
129+
130+
if (!$section->identity instanceof IIdentity || !is_bool($section->authenticated)) {
131+
$section->remove();
132+
}
133+
134+
if ($section->authenticated && $section->expireDelta > 0) { // check time expiration
135+
if ($section->expireTime < time()) {
136+
$section->reason = self::LOGOUT_INACTIVITY;
137+
$section->authenticated = false;
138+
if ($section->expireIdentity) {
139+
unset($section->identity);
140+
}
141+
}
142+
$section->expireTime = time() + $section->expireDelta; // sliding expiration
143+
}
144+
145+
if (!$section->authenticated) {
146+
unset($section->expireTime, $section->expireDelta, $section->expireIdentity, $section->authTime);
147+
}
148+
149+
return $this->sessionSection;
150+
}
151+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/**
4+
* Test: SecurityExtension
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
use Nette\Bridges\HttpDI\HttpExtension;
10+
use Nette\Bridges\HttpDI\SessionExtension;
11+
use Nette\Bridges\SecurityDI\SecurityExtension;
12+
use Nette\DI;
13+
use Tester\Assert;
14+
15+
16+
require __DIR__ . '/../bootstrap.php';
17+
18+
19+
$compiler = new DI\Compiler;
20+
$compiler->addExtension('foo', new HttpExtension);
21+
$compiler->addExtension('session', new SessionExtension);
22+
$compiler->addExtension('security', new SecurityExtension);
23+
24+
$loader = new Nette\DI\Config\Loader;
25+
$config = $loader->load(Tester\FileMock::create('
26+
security:
27+
authentication:
28+
storage: cookie
29+
expiration: 1 week
30+
cookieName: abc
31+
cookieDomain: xyz
32+
cookieSamesite: Strict
33+
', 'neon'));
34+
35+
eval($compiler->addConfig($config)->compile());
36+
$container = new Container;
37+
38+
$storage = $container->getService('security.userStorage');
39+
Assert::type(Nette\Bridges\SecurityHttp\CookieStorage::class, $storage);
40+
41+
Assert::with($storage, function () {
42+
Assert::same('1 week', $this->cookieExpiration);
43+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nette\Bridges\HttpDI\HttpExtension;
6+
use Nette\Bridges\HttpDI\SessionExtension;
7+
use Nette\Bridges\SecurityDI\SecurityExtension;
8+
use Nette\DI;
9+
use Tester\Assert;
10+
11+
12+
require __DIR__ . '/../bootstrap.php';
13+
14+
15+
$compiler = new DI\Compiler;
16+
$compiler->addExtension('foo', new HttpExtension);
17+
$compiler->addExtension('session', new SessionExtension);
18+
$compiler->addExtension('security', new SecurityExtension);
19+
20+
$loader = new Nette\DI\Config\Loader;
21+
$config = $loader->load(Tester\FileMock::create('
22+
session:
23+
expiration: 1 year
24+
25+
security:
26+
authentication:
27+
storage: session
28+
expiration: 1 week
29+
', 'neon'));
30+
31+
eval($compiler->addConfig($config)->compile());
32+
$container = new Container;
33+
34+
Assert::type(Nette\Bridges\SecurityHttp\SessionStorage::class, $container->getService('security.userStorage'));

0 commit comments

Comments
 (0)