forked from mezzio/mezzio-session-cache
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCacheSessionPersistence.php
193 lines (163 loc) · 7.12 KB
/
CacheSessionPersistence.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<?php
declare(strict_types=1);
namespace Mezzio\Session\Cache;
use Mezzio\Session\Persistence\CacheHeadersGeneratorTrait;
use Mezzio\Session\Persistence\Http;
use Mezzio\Session\Persistence\SessionCookieAwareTrait;
use Mezzio\Session\Session;
use Mezzio\Session\SessionIdentifierAwareInterface;
use Mezzio\Session\SessionInterface;
use Mezzio\Session\SessionPersistenceInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use function bin2hex;
use function gmdate;
use function random_bytes;
/**
* Session persistence using a PSR-16 cache adapter.
*
* Session identifiers are generated using random_bytes (and casting to hex).
* During persistence, if the session regeneration flag is true, a new session
* identifier is created, and the session re-started.
*/
class CacheSessionPersistence implements SessionPersistenceInterface
{
use CacheHeadersGeneratorTrait;
use SessionCookieAwareTrait;
private CacheItemPoolInterface $cache;
private bool $persistent;
/**
* Prepare session cache and default HTTP caching headers.
*
* @param CacheItemPoolInterface $cache The cache pool instance
* @param string $cookieName The name of the cookie
* @param string $cacheLimiter The cache limiter setting is used to
* determine how to send HTTP client-side caching headers. Those
* headers will be added programmatically to the response along with
* the session set-cookie header when the session data is persisted.
* @param int $cacheExpire Number of seconds until the session cookie
* should expire; defaults to 180 minutes (180m * 60s/m = 10800s),
* which is the default of the PHP session.cache_expire setting. This
* is also used to set the TTL for session data.
* @param null|int $lastModified Timestamp when the application was last
* modified. If not provided, this will look for each of
* public/index.php, index.php, and finally the current working
* directory, using the filemtime() of the first found.
* @param bool $persistent Whether or not to create a persistent cookie. If
* provided, this sets the Expires directive for the cookie based on
* the value of $cacheExpire. Developers can also set the expiry at
* runtime via the Session instance, using its persistSessionFor()
* method; that value will be honored even if global persistence
* is toggled true here.
* @param string|null $cookieDomain The domain for the cookie. If not set,
* the current domain is used.
* @param bool $cookieSecure Whether or not the cookie should be required
* to be set over an encrypted connection
* @param bool $cookieHttpOnly Whether or not the cookie may be accessed
* by client-side apis (e.g., Javascript). An http-only cookie cannot
* be accessed by client-side apis.
* @param string $cookieSameSite The same-site rule to apply to the persisted
* cookie. Options include "Lax", "Strict", and "None".
* @todo reorder the constructor arguments
*/
public function __construct(
CacheItemPoolInterface $cache,
string $cookieName,
string $cookiePath = '/',
string $cacheLimiter = 'nocache',
int $cacheExpire = 10800,
?int $lastModified = null,
bool $persistent = false,
?string $cookieDomain = null,
bool $cookieSecure = false,
bool $cookieHttpOnly = false,
string $cookieSameSite = 'Lax'
) {
$this->cache = $cache;
if (empty($cookieName)) {
throw new Exception\InvalidArgumentException('Session cookie name must not be empty');
}
$this->cookieName = $cookieName;
$this->cookieLifetime = $persistent ? $cacheExpire : 0;
$this->cookieDomain = $cookieDomain;
$this->cookiePath = $cookiePath;
$this->cookieSecure = $cookieSecure;
$this->cookieHttpOnly = $cookieHttpOnly;
$this->cookieSameSite = $cookieSameSite;
$this->cacheLimiter = isset(self::$supportedCacheLimiters[$cacheLimiter])
? $cacheLimiter
: 'nocache';
$this->cacheExpire = $cacheExpire;
$this->lastModified = $lastModified
? gmdate(Http::DATE_FORMAT, $lastModified)
: $this->getLastModified();
$this->persistent = $persistent;
}
public function initializeSessionFromRequest(ServerRequestInterface $request): SessionInterface
{
$id = $this->getSessionCookieValueFromRequest($request);
$sessionData = $id ? $this->getSessionDataFromCache($id) : [];
return new Session($sessionData, $id);
}
/**
* @param SessionInterface&SessionIdentifierAwareInterface $session
*/
public function persistSession(SessionInterface $session, ResponseInterface $response): ResponseInterface
{
$id = $session->getId();
// New session? No data? Nothing to do.
if (
'' === $id
&& ([] === $session->toArray() || ! $session->hasChanged())
) {
return $response;
}
// Regenerate the session if:
// - we have no session identifier
// - the session is marked as regenerated
if ('' === $id || $session->isRegenerated()) {
$id = $this->regenerateSession($id);
}
$this->persistSessionDataToCache($id, $session->toArray());
$response = $this->addSessionCookieHeaderToResponse($response, $id, $session);
$response = $this->addCacheHeadersToResponse($response);
return $response;
}
/**
* Regenerates the session.
*
* If the cache has an entry corresponding to `$id`, this deletes it.
*
* Regardless, it generates and returns a new session identifier.
*/
private function regenerateSession(string $id): string
{
if ('' !== $id && $this->cache->hasItem($id)) {
$this->cache->deleteItem($id);
}
return $this->generateSessionId();
}
/**
* Generate a session identifier.
*/
private function generateSessionId(): string
{
return bin2hex(random_bytes(16));
}
private function getSessionDataFromCache(string $id): array
{
$item = $this->cache->getItem($id);
if (! $item->isHit()) {
return [];
}
return $item->get() ?: [];
}
private function persistSessionDataToCache(string $id, array $data): void
{
$item = $this->cache->getItem($id);
$item->set($data);
$item->expiresAfter($this->cacheExpire);
$this->cache->save($item);
}
}