1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
5
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
6
|
|
|
* |
7
|
|
|
* Licensed under The MIT License |
8
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
9
|
|
|
* Redistributions of files must retain the above copyright notice. |
10
|
|
|
* |
11
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
12
|
|
|
* @link https://cakephp.org CakePHP(tm) Project |
13
|
|
|
* @since 0.10.0 |
14
|
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License |
15
|
|
|
*/ |
16
|
|
|
|
17
|
|
|
declare(strict_types=1); |
18
|
|
|
|
19
|
|
|
namespace Phauthentic\Infrastructure\Http\Session; |
20
|
|
|
|
21
|
|
|
use Adbar\Dot; |
22
|
|
|
use SessionHandlerInterface; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* This class is a wrapper for the native PHP session functions. It provides |
26
|
|
|
* several defaults for the most common session configuration |
27
|
|
|
* via external handlers and helps with using session in cli without any warnings. |
28
|
|
|
* |
29
|
|
|
* Sessions can be created from the defaults using `Session::create()` or you can get |
30
|
|
|
* an instance of a new session by just instantiating this class and passing the complete |
31
|
|
|
* options you want to use. |
32
|
|
|
* |
33
|
|
|
* When specific options are omitted, this class will take its defaults from the configuration |
34
|
|
|
* values from the `session.*` directives in php.ini. This class will also alter such |
35
|
|
|
* directives when configuration values are provided. |
36
|
|
|
*/ |
37
|
|
|
class Session implements SessionInterface |
38
|
|
|
{ |
39
|
|
|
/** |
40
|
|
|
* The Session handler instance used as an engine for persisting the session data. |
41
|
|
|
* |
42
|
|
|
* @var \SessionHandlerInterface |
43
|
|
|
*/ |
44
|
|
|
protected SessionHandlerInterface $handler; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* The Session handler instance used as an engine for persisting the session data. |
48
|
|
|
* |
49
|
|
|
* @var \Phauthentic\Infrastructure\Http\Session\ConfigInterface |
50
|
|
|
*/ |
51
|
|
|
protected ConfigInterface $config; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Indicates whether the sessions has already started |
55
|
|
|
* |
56
|
|
|
* @var bool |
57
|
|
|
*/ |
58
|
|
|
protected bool $started = false; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* The time in seconds the session will be valid for |
62
|
|
|
* |
63
|
|
|
* @var int |
64
|
|
|
*/ |
65
|
|
|
protected int $lifetime = 0; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Whether this session is running under a CLI environment |
69
|
|
|
* |
70
|
|
|
* @var bool |
71
|
|
|
*/ |
72
|
|
|
protected bool $isCli = false; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Constructor. |
76
|
|
|
* ### Configuration: |
77
|
|
|
* - timeout: The time in minutes the session should be valid for. |
78
|
|
|
* - cookiePath: The url path for which session cookie is set. Maps to the |
79
|
|
|
* `session.cookie_path` php.ini config. Defaults to base path of app. |
80
|
|
|
* - ini: A list of php.ini directives to change before the session start. |
81
|
|
|
* - handler: An array containing at least the `class` key. To be used as the session |
82
|
|
|
* engine for persisting data. The rest of the keys in the array will be passed as |
83
|
|
|
* the configuration array for the engine. You can set the `class` key to an already |
84
|
|
|
* instantiated session handler object. |
85
|
|
|
* |
86
|
|
|
* @param \Phauthentic\Infrastructure\Http\Session\ConfigInterface|null $config The Configuration to apply to this session object |
87
|
|
|
* @param \SessionHandlerInterface|null $handler |
88
|
|
|
*/ |
89
|
1 |
|
public function __construct( |
90
|
|
|
?ConfigInterface $config = null, |
91
|
|
|
?SessionHandlerInterface $handler = null |
92
|
|
|
) { |
93
|
1 |
|
if ($config !== null) { |
94
|
|
|
$this->config = $config; |
95
|
|
|
} else { |
96
|
1 |
|
$this->config = new Config(); |
97
|
1 |
|
$this->config->setUseTransSid(false); |
98
|
|
|
} |
99
|
|
|
|
100
|
1 |
|
if ($handler !== null) { |
101
|
|
|
$this->setSaveHandler($handler); |
102
|
|
|
} |
103
|
|
|
|
104
|
1 |
|
$this->lifetime = (int)ini_get('session.gc_maxlifetime'); |
105
|
1 |
|
$this->isCli = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg'); |
106
|
|
|
|
107
|
1 |
|
session_register_shutdown(); |
108
|
1 |
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* @return \Phauthentic\Infrastructure\Http\Session\ConfigInterface |
112
|
|
|
*/ |
113
|
|
|
public function config(): ConfigInterface |
114
|
|
|
{ |
115
|
|
|
return $this->config; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Set the engine property and update the session handler in PHP. |
120
|
|
|
* |
121
|
|
|
* @param \SessionHandlerInterface $handler The handler to set |
122
|
|
|
* @return void |
123
|
|
|
*/ |
124
|
|
|
protected function setSaveHandler(SessionHandlerInterface $handler): void |
125
|
|
|
{ |
126
|
|
|
if (!headers_sent()) { |
127
|
|
|
session_set_save_handler($handler, false); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$this->handler = $handler; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Startup checks |
135
|
|
|
* |
136
|
|
|
* @return bool |
137
|
|
|
*/ |
138
|
|
|
protected function doStartupChecks(): bool |
139
|
|
|
{ |
140
|
|
|
if (session_status() === PHP_SESSION_ACTIVE) { |
141
|
|
|
throw SessionException::alreadyStarted(); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
if (ini_get('session.use_cookies') && headers_sent($file, $line)) { |
145
|
|
|
return false; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
if (!session_start()) { |
149
|
|
|
throw SessionException::couldNotStart(); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
return true; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Starts the Session. |
157
|
|
|
* |
158
|
|
|
* @return bool True if session was started |
159
|
|
|
* @throws \Phauthentic\Infrastructure\Http\Session\SessionException if the session was already started |
160
|
|
|
*/ |
161
|
1 |
|
public function start(): bool |
162
|
|
|
{ |
163
|
1 |
|
if ($this->started) { |
164
|
|
|
return true; |
165
|
|
|
} |
166
|
|
|
|
167
|
1 |
|
if ($this->isCli) { |
168
|
1 |
|
$_SESSION = []; |
169
|
1 |
|
$this->setId('cli'); |
170
|
|
|
|
171
|
1 |
|
return $this->started = true; |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
if (!$this->doStartupChecks()) { |
175
|
|
|
return false; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
$this->started = true; |
179
|
|
|
|
180
|
|
|
if ($this->hasExpired()) { |
181
|
|
|
$this->destroy(); |
182
|
|
|
|
183
|
|
|
return $this->start(); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return $this->started; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* @return array|string|null |
191
|
|
|
*/ |
192
|
|
|
protected function time() |
193
|
|
|
{ |
194
|
|
|
return $this->read('Config.time'); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Determine if Session has already been started. |
199
|
|
|
* |
200
|
|
|
* @return bool True if session has been started. |
201
|
|
|
*/ |
202
|
1 |
|
public function started(): bool |
203
|
|
|
{ |
204
|
1 |
|
return $this->started || session_status() === PHP_SESSION_ACTIVE; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Returns true if given variable name is set in session. |
209
|
|
|
* |
210
|
|
|
* @param string|null $name Variable name to check for |
211
|
|
|
* @return bool True if variable is there |
212
|
|
|
*/ |
213
|
1 |
|
public function check(?string $name = null): bool |
214
|
|
|
{ |
215
|
1 |
|
if ($this->exists() && !$this->started()) { |
216
|
|
|
$this->start(); |
217
|
|
|
} |
218
|
|
|
|
219
|
1 |
|
if (!isset($_SESSION)) { |
220
|
|
|
return false; |
221
|
|
|
} |
222
|
|
|
|
223
|
1 |
|
return (new Dot($_SESSION))->get($name) !== null; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Returns given session variable, or all of them, if no parameters given. |
228
|
|
|
* |
229
|
|
|
* @param string|null $name The name of the session variable (or a path as sent to Hash.extract) |
230
|
|
|
* @return string|array|null The value of the session variable, null if session not available, |
231
|
|
|
* session not started, or provided name not found in the session. |
232
|
|
|
*/ |
233
|
1 |
|
public function read(?string $name = null) |
234
|
|
|
{ |
235
|
1 |
|
if ($this->exists() && !$this->started()) { |
236
|
|
|
$this->start(); |
237
|
|
|
} |
238
|
|
|
|
239
|
1 |
|
if (!isset($_SESSION)) { |
240
|
|
|
return null; |
241
|
|
|
} |
242
|
|
|
|
243
|
1 |
|
if ($name === null) { |
244
|
|
|
return $_SESSION ?? []; |
245
|
|
|
} |
246
|
|
|
|
247
|
1 |
|
return (new Dot($_SESSION))->get($name); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
/** |
251
|
|
|
* Reads and deletes a variable from session. |
252
|
|
|
* |
253
|
|
|
* @param string $name The key to read and remove (or a path as sent to Hash.extract). |
254
|
|
|
* @return mixed The value of the session variable, null if session not available, |
255
|
|
|
* session not started, or provided name not found in the session. |
256
|
|
|
*/ |
257
|
1 |
|
public function consume(string $name) |
258
|
|
|
{ |
259
|
1 |
|
if (empty($name)) { |
260
|
|
|
return null; |
261
|
|
|
} |
262
|
|
|
|
263
|
1 |
|
$value = $this->read($name); |
264
|
1 |
|
if ($value !== null) { |
265
|
1 |
|
$dot = new Dot($_SESSION); |
266
|
1 |
|
$dot->delete($name); |
267
|
1 |
|
$this->overwrite($_SESSION, (array)$dot->get()); |
268
|
|
|
} |
269
|
|
|
|
270
|
1 |
|
return $value; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* Writes value to given session variable name. |
275
|
|
|
* |
276
|
|
|
* @param string|array $name Name of variable |
277
|
|
|
* @param mixed $value Value to write |
278
|
|
|
* @return void |
279
|
|
|
*/ |
280
|
1 |
|
public function write($name, $value = null): void |
281
|
|
|
{ |
282
|
1 |
|
if (!$this->started()) { |
283
|
|
|
$this->start(); |
284
|
|
|
} |
285
|
|
|
|
286
|
1 |
|
$write = $name; |
287
|
1 |
|
if (!is_array($name)) { |
288
|
1 |
|
$write = [$name => $value]; |
289
|
|
|
} |
290
|
|
|
|
291
|
1 |
|
$data = new Dot($_SESSION ?? []); |
292
|
1 |
|
foreach ($write as $key => $val) { |
293
|
1 |
|
$data->add($key, $val); |
294
|
|
|
} |
295
|
|
|
|
296
|
1 |
|
$this->overwrite($_SESSION, $data->get()); |
297
|
1 |
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Returns the current sessions id |
301
|
|
|
* |
302
|
|
|
* @return string |
303
|
|
|
*/ |
304
|
|
|
public function id(): string |
305
|
|
|
{ |
306
|
|
|
return (string)session_id(); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Sets the session id |
311
|
|
|
* |
312
|
|
|
* Calling this method will not auto start the session. You might have to manually |
313
|
|
|
* assert a started session. |
314
|
|
|
* |
315
|
|
|
* Passing an id into it, you can also replace the session id if the session |
316
|
|
|
* has not already been started. |
317
|
|
|
* |
318
|
|
|
* Note that depending on the session handler, not all characters are allowed |
319
|
|
|
* within the session id. For example, the file session handler only allows |
320
|
|
|
* characters in the range a-z A-Z 0-9 , (comma) and - (minus). |
321
|
|
|
* |
322
|
|
|
* @param string $id Session Id |
323
|
|
|
* @return $this |
324
|
|
|
*/ |
325
|
1 |
|
public function setId(string $id): self |
326
|
|
|
{ |
327
|
1 |
|
if (headers_sent()) { |
328
|
|
|
throw SessionException::headersAlreadySent(); |
329
|
|
|
} |
330
|
|
|
|
331
|
1 |
|
session_id($id); |
332
|
|
|
|
333
|
1 |
|
return $this; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Removes a variable from session. |
338
|
|
|
* |
339
|
|
|
* @param string $name Session variable to remove |
340
|
|
|
* @return void |
341
|
|
|
*/ |
342
|
1 |
|
public function delete(string $name): void |
343
|
|
|
{ |
344
|
1 |
|
if ($this->check($name)) { |
345
|
1 |
|
$this->overwrite($_SESSION, (array)(new Dot($_SESSION))->delete($name)); |
|
|
|
|
346
|
|
|
} |
347
|
1 |
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* Used to write new data to _SESSION, since PHP doesn't like us setting the _SESSION var itself. |
351
|
|
|
* |
352
|
|
|
* @param array $old Set of old variables => values |
353
|
|
|
* @param array $new New set of variable => value |
354
|
|
|
* @return void |
355
|
|
|
*/ |
356
|
1 |
|
protected function overwrite(&$old, $new) |
357
|
|
|
{ |
358
|
1 |
|
if (!empty($old)) { |
359
|
1 |
|
foreach ($old as $key => $var) { |
360
|
1 |
|
if (!isset($new[$key])) { |
361
|
1 |
|
unset($old[$key]); |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
} |
365
|
|
|
|
366
|
1 |
|
foreach ($new as $key => $var) { |
367
|
1 |
|
$old[$key] = $var; |
368
|
|
|
} |
369
|
1 |
|
} |
370
|
|
|
|
371
|
|
|
/** |
372
|
|
|
* Helper method to destroy invalid sessions. |
373
|
|
|
* |
374
|
|
|
* @return void |
375
|
|
|
*/ |
376
|
|
|
public function destroy() |
377
|
|
|
{ |
378
|
|
|
if ($this->exists() && !$this->started()) { |
379
|
|
|
$this->start(); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
if (!$this->isCli && session_status() === PHP_SESSION_ACTIVE) { |
383
|
|
|
session_destroy(); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$_SESSION = []; |
387
|
|
|
$this->started = false; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Clears the session. |
392
|
|
|
* |
393
|
|
|
* Optionally it also clears the session id and renews the session. |
394
|
|
|
* |
395
|
|
|
* @param bool $renew If session should be renewed, as well. Defaults to false. |
396
|
|
|
* @return void |
397
|
|
|
*/ |
398
|
|
|
public function clear(bool $renew = false) |
399
|
|
|
{ |
400
|
|
|
$_SESSION = []; |
401
|
|
|
if ($renew) { |
402
|
|
|
$this->renew(); |
403
|
|
|
} |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
/** |
407
|
|
|
* Returns whether a session exists |
408
|
|
|
* |
409
|
|
|
* @return bool |
410
|
|
|
*/ |
411
|
1 |
|
public function exists() |
412
|
|
|
{ |
413
|
1 |
|
return !ini_get('session.use_cookies') |
414
|
1 |
|
|| isset($_COOKIE[session_name()]) |
415
|
1 |
|
|| $this->isCli |
416
|
1 |
|
|| (ini_get('session.use_trans_sid') && isset($_GET[session_name()])); |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Restarts this session. |
421
|
|
|
* |
422
|
|
|
* @return void |
423
|
|
|
*/ |
424
|
|
|
public function renew(): void |
425
|
|
|
{ |
426
|
|
|
if (!$this->exists() || $this->isCli) { |
427
|
|
|
return; |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
$this->start(); |
431
|
|
|
$params = session_get_cookie_params(); |
432
|
|
|
setcookie( |
433
|
|
|
session_name(), |
434
|
|
|
'', |
435
|
|
|
time() - 42000, |
436
|
|
|
$params['path'], |
437
|
|
|
$params['domain'], |
438
|
|
|
$params['secure'], |
439
|
|
|
$params['httponly'] |
440
|
|
|
); |
441
|
|
|
|
442
|
|
|
if (session_id()) { |
443
|
|
|
session_regenerate_id(true); |
444
|
|
|
} |
445
|
|
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Returns true if the session is no longer valid because the last time it was |
449
|
|
|
* accessed was after the configured timeout. |
450
|
|
|
* |
451
|
|
|
* @return bool |
452
|
|
|
*/ |
453
|
|
|
public function hasExpired(): bool |
454
|
|
|
{ |
455
|
|
|
$time = $this->time(); |
456
|
|
|
$result = false; |
457
|
|
|
|
458
|
|
|
$checkTime = $time !== null && $this->lifetime > 0; |
459
|
|
|
if ($checkTime && (time() - (int)$time > $this->lifetime)) { |
460
|
|
|
$result = true; |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
$this->write('Config.time', time()); |
464
|
|
|
|
465
|
|
|
return $result; |
466
|
|
|
} |
467
|
|
|
} |
468
|
|
|
|
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.