Mercurial > hg > isophonics-drupal-site
comparison core/lib/Drupal/Core/Session/SessionManager.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 1fec387a4317 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\Core\Session; | |
4 | |
5 use Drupal\Component\Utility\Crypt; | |
6 use Drupal\Core\Database\Connection; | |
7 use Drupal\Core\DependencyInjection\DependencySerializationTrait; | |
8 use Symfony\Component\HttpFoundation\RequestStack; | |
9 use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; | |
10 | |
11 /** | |
12 * Manages user sessions. | |
13 * | |
14 * This class implements the custom session management code inherited from | |
15 * Drupal 7 on top of the corresponding Symfony component. Regrettably the name | |
16 * NativeSessionStorage is not quite accurate. In fact the responsibility for | |
17 * storing and retrieving session data has been extracted from it in Symfony 2.1 | |
18 * but the class name was not changed. | |
19 * | |
20 * @todo | |
21 * In fact the NativeSessionStorage class already implements all of the | |
22 * functionality required by a typical Symfony application. Normally it is not | |
23 * necessary to subclass it at all. In order to reach the point where Drupal | |
24 * can use the Symfony session management unmodified, the code implemented | |
25 * here needs to be extracted either into a dedicated session handler proxy | |
26 * (e.g. sid-hashing) or relocated to the authentication subsystem. | |
27 */ | |
28 class SessionManager extends NativeSessionStorage implements SessionManagerInterface { | |
29 | |
30 use DependencySerializationTrait; | |
31 | |
32 /** | |
33 * The request stack. | |
34 * | |
35 * @var \Symfony\Component\HttpFoundation\RequestStack | |
36 */ | |
37 protected $requestStack; | |
38 | |
39 /** | |
40 * The database connection to use. | |
41 * | |
42 * @var \Drupal\Core\Database\Connection | |
43 */ | |
44 protected $connection; | |
45 | |
46 /** | |
47 * The session configuration. | |
48 * | |
49 * @var \Drupal\Core\Session\SessionConfigurationInterface | |
50 */ | |
51 protected $sessionConfiguration; | |
52 | |
53 /** | |
54 * Whether a lazy session has been started. | |
55 * | |
56 * @var bool | |
57 */ | |
58 protected $startedLazy; | |
59 | |
60 /** | |
61 * The write safe session handler. | |
62 * | |
63 * @todo: This reference should be removed once all database queries | |
64 * are removed from the session manager class. | |
65 * | |
66 * @var \Drupal\Core\Session\WriteSafeSessionHandlerInterface | |
67 */ | |
68 protected $writeSafeHandler; | |
69 | |
70 /** | |
71 * Constructs a new session manager instance. | |
72 * | |
73 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack | |
74 * The request stack. | |
75 * @param \Drupal\Core\Database\Connection $connection | |
76 * The database connection. | |
77 * @param \Drupal\Core\Session\MetadataBag $metadata_bag | |
78 * The session metadata bag. | |
79 * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration | |
80 * The session configuration interface. | |
81 * @param \Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy|Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler|\SessionHandlerInterface|null $handler | |
82 * The object to register as a PHP session handler. | |
83 * @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::setSaveHandler() | |
84 */ | |
85 public function __construct(RequestStack $request_stack, Connection $connection, MetadataBag $metadata_bag, SessionConfigurationInterface $session_configuration, $handler = NULL) { | |
86 $options = []; | |
87 $this->sessionConfiguration = $session_configuration; | |
88 $this->requestStack = $request_stack; | |
89 $this->connection = $connection; | |
90 | |
91 parent::__construct($options, $handler, $metadata_bag); | |
92 | |
93 // @todo When not using the Symfony Session object, the list of bags in the | |
94 // NativeSessionStorage will remain uninitialized. This will lead to | |
95 // errors in NativeSessionHandler::loadSession. Remove this after | |
96 // https://www.drupal.org/node/2229145, when we will be using the Symfony | |
97 // session object (which registers an attribute bag with the | |
98 // manager upon instantiation). | |
99 $this->bags = []; | |
100 } | |
101 | |
102 /** | |
103 * {@inheritdoc} | |
104 */ | |
105 public function start() { | |
106 if (($this->started || $this->startedLazy) && !$this->closed) { | |
107 return $this->started; | |
108 } | |
109 | |
110 $request = $this->requestStack->getCurrentRequest(); | |
111 $this->setOptions($this->sessionConfiguration->getOptions($request)); | |
112 | |
113 if ($this->sessionConfiguration->hasSession($request)) { | |
114 // If a session cookie exists, initialize the session. Otherwise the | |
115 // session is only started on demand in save(), making | |
116 // anonymous users not use a session cookie unless something is stored in | |
117 // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. | |
118 $result = $this->startNow(); | |
119 } | |
120 | |
121 if (empty($result)) { | |
122 // Randomly generate a session identifier for this request. This is | |
123 // necessary because \Drupal\user\SharedTempStoreFactory::get() wants to | |
124 // know the future session ID of a lazily started session in advance. | |
125 // | |
126 // @todo: With current versions of PHP there is little reason to generate | |
127 // the session id from within application code. Consider using the | |
128 // default php session id instead of generating a custom one: | |
129 // https://www.drupal.org/node/2238561 | |
130 $this->setId(Crypt::randomBytesBase64()); | |
131 | |
132 // Initialize the session global and attach the Symfony session bags. | |
133 $_SESSION = []; | |
134 $this->loadSession(); | |
135 | |
136 // NativeSessionStorage::loadSession() sets started to TRUE, reset it to | |
137 // FALSE here. | |
138 $this->started = FALSE; | |
139 $this->startedLazy = TRUE; | |
140 | |
141 $result = FALSE; | |
142 } | |
143 | |
144 return $result; | |
145 } | |
146 | |
147 /** | |
148 * Forcibly start a PHP session. | |
149 * | |
150 * @return bool | |
151 * TRUE if the session is started. | |
152 */ | |
153 protected function startNow() { | |
154 if ($this->isCli()) { | |
155 return FALSE; | |
156 } | |
157 | |
158 if ($this->startedLazy) { | |
159 // Save current session data before starting it, as PHP will destroy it. | |
160 $session_data = $_SESSION; | |
161 } | |
162 | |
163 $result = parent::start(); | |
164 | |
165 // Restore session data. | |
166 if ($this->startedLazy) { | |
167 $_SESSION = $session_data; | |
168 $this->loadSession(); | |
169 } | |
170 | |
171 return $result; | |
172 } | |
173 | |
174 /** | |
175 * {@inheritdoc} | |
176 */ | |
177 public function save() { | |
178 if ($this->isCli()) { | |
179 // We don't have anything to do if we are not allowed to save the session. | |
180 return; | |
181 } | |
182 | |
183 if ($this->isSessionObsolete()) { | |
184 // There is no session data to store, destroy the session if it was | |
185 // previously started. | |
186 if ($this->getSaveHandler()->isActive()) { | |
187 $this->destroy(); | |
188 } | |
189 } | |
190 else { | |
191 // There is session data to store. Start the session if it is not already | |
192 // started. | |
193 if (!$this->getSaveHandler()->isActive()) { | |
194 $this->startNow(); | |
195 } | |
196 // Write the session data. | |
197 parent::save(); | |
198 } | |
199 | |
200 $this->startedLazy = FALSE; | |
201 } | |
202 | |
203 /** | |
204 * {@inheritdoc} | |
205 */ | |
206 public function regenerate($destroy = FALSE, $lifetime = NULL) { | |
207 // Nothing to do if we are not allowed to change the session. | |
208 if ($this->isCli()) { | |
209 return; | |
210 } | |
211 | |
212 // We do not support the optional $destroy and $lifetime parameters as long | |
213 // as #2238561 remains open. | |
214 if ($destroy || isset($lifetime)) { | |
215 throw new \InvalidArgumentException('The optional parameters $destroy and $lifetime of SessionManager::regenerate() are not supported currently'); | |
216 } | |
217 | |
218 if ($this->isStarted()) { | |
219 $old_session_id = $this->getId(); | |
220 } | |
221 session_id(Crypt::randomBytesBase64()); | |
222 | |
223 $this->getMetadataBag()->clearCsrfTokenSeed(); | |
224 | |
225 if (isset($old_session_id)) { | |
226 $params = session_get_cookie_params(); | |
227 $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; | |
228 setcookie($this->getName(), $this->getId(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); | |
229 $this->migrateStoredSession($old_session_id); | |
230 } | |
231 | |
232 if (!$this->isStarted()) { | |
233 // Start the session when it doesn't exist yet. | |
234 $this->startNow(); | |
235 } | |
236 } | |
237 | |
238 /** | |
239 * {@inheritdoc} | |
240 */ | |
241 public function delete($uid) { | |
242 // Nothing to do if we are not allowed to change the session. | |
243 if (!$this->writeSafeHandler->isSessionWritable() || $this->isCli()) { | |
244 return; | |
245 } | |
246 $this->connection->delete('sessions') | |
247 ->condition('uid', $uid) | |
248 ->execute(); | |
249 } | |
250 | |
251 /** | |
252 * {@inheritdoc} | |
253 */ | |
254 public function destroy() { | |
255 session_destroy(); | |
256 | |
257 // Unset the session cookies. | |
258 $session_name = $this->getName(); | |
259 $cookies = $this->requestStack->getCurrentRequest()->cookies; | |
260 // setcookie() can only be called when headers are not yet sent. | |
261 if ($cookies->has($session_name) && !headers_sent()) { | |
262 $params = session_get_cookie_params(); | |
263 setcookie($session_name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']); | |
264 $cookies->remove($session_name); | |
265 } | |
266 } | |
267 | |
268 /** | |
269 * {@inheritdoc} | |
270 */ | |
271 public function setWriteSafeHandler(WriteSafeSessionHandlerInterface $handler) { | |
272 $this->writeSafeHandler = $handler; | |
273 } | |
274 | |
275 /** | |
276 * Returns whether the current PHP process runs on CLI. | |
277 * | |
278 * Command line clients do not support cookies nor sessions. | |
279 * | |
280 * @return bool | |
281 */ | |
282 protected function isCli() { | |
283 return PHP_SAPI === 'cli'; | |
284 } | |
285 | |
286 /** | |
287 * Determines whether the session contains user data. | |
288 * | |
289 * @return bool | |
290 * TRUE when the session does not contain any values and therefore can be | |
291 * destroyed. | |
292 */ | |
293 protected function isSessionObsolete() { | |
294 $used_session_keys = array_filter($this->getSessionDataMask()); | |
295 return empty($used_session_keys); | |
296 } | |
297 | |
298 /** | |
299 * Returns a map specifying which session key is containing user data. | |
300 * | |
301 * @return array | |
302 * An array where keys correspond to the session keys and the values are | |
303 * booleans specifying whether the corresponding session key contains any | |
304 * user data. | |
305 */ | |
306 protected function getSessionDataMask() { | |
307 if (empty($_SESSION)) { | |
308 return []; | |
309 } | |
310 | |
311 // Start out with a completely filled mask. | |
312 $mask = array_fill_keys(array_keys($_SESSION), TRUE); | |
313 | |
314 // Ignore the metadata bag, it does not contain any user data. | |
315 $mask[$this->metadataBag->getStorageKey()] = FALSE; | |
316 | |
317 // Ignore attribute bags when they do not contain any data. | |
318 foreach ($this->bags as $bag) { | |
319 $key = $bag->getStorageKey(); | |
320 $mask[$key] = !empty($_SESSION[$key]); | |
321 } | |
322 | |
323 return array_intersect_key($mask, $_SESSION); | |
324 } | |
325 | |
326 /** | |
327 * Migrates the current session to a new session id. | |
328 * | |
329 * @param string $old_session_id | |
330 * The old session ID. The new session ID is $this->getId(). | |
331 */ | |
332 protected function migrateStoredSession($old_session_id) { | |
333 $fields = ['sid' => Crypt::hashBase64($this->getId())]; | |
334 $this->connection->update('sessions') | |
335 ->fields($fields) | |
336 ->condition('sid', Crypt::hashBase64($old_session_id)) | |
337 ->execute(); | |
338 } | |
339 | |
340 } |