Issues (7)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Session.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Sesshin;
3
4
use Sesshin\Id;
5
use League\Event\EmitterAwareTrait;
6
use Sesshin\FingerprintGenerator\FingerprintGeneratorInterface;
7
use Sesshin\Store\StoreInterface;
8
9
class Session implements \ArrayAccess
10
{
11
    use EmitterAwareTrait;
12
13
    const DEFAULT_NAMESPACE = 'default';
14
    const METADATA_NAMESPACE = '__metadata__';
15
16
    /** @var Id\Handler */
17
    private $idHandler;
18
19
    /** @var int Number of requests after which id is regeneratd */
20
    private $idRequestsLimit = null;
21
22
    /** @var int Time after id is regenerated */
23
    private $idTtl = 1440;
24
25
    /** @var bool */
26
    private $idRegenerated;
27
28
    /** @var int */
29
    private $regenerationTrace;
30
31
    /** @var StoreInterface */
32
    private $store;
33
34
    /** @var bool|mixed Session values */
35
    private $values = array();
36
37
    /** @var int Specifies the number of seconds after which session will be automatically expired */
38
    private $ttl = 1440;
39
40
    /** @var int First trace (timestamp), time when session was created */
41
    private $firstTrace;
42
43
    /** @var int Last trace (Unix timestamp) */
44
    private $lastTrace;
45
46
    /** @var int */
47
    private $requestsCount;
48
49
    /** @var FingerprintGeneratorInterface[] */
50
    private $fingerprintGenerators = array();
51
52
    /** @var string */
53
    private $fingerprint = '';
54
55
    /** @var bool Is session opened? */
56
    private $opened = false;
57
58
    /** @var SessionFlash */
59
    private $flash;
60
61
    /**
62
     * @param StoreInterface $store
63
     */
64
    public function __construct(StoreInterface $store)
65
    {
66
        $this->store = $store;
67
68
        // registering shutdown function, just in case someone forgets to close session
69
        register_shutdown_function(array($this, 'close'));
70
    }
71
72
    /**
73
     * Creates new session
74
     *
75
     * It should be called only once at the beginning. If called for existing
76
     * session it ovewrites it (clears all values etc).
77
     * It can be replaced with {@link self::open()} (called with "true" argument)
78
     *
79
     * @return bool Session opened?
80
     */
81
    public function create()
82
    {
83
        $this->getIdHandler()->generateId();
84
85
        $this->values = array();
86
87
        $this->firstTrace = time();
88
        $this->updateLastTrace();
89
90
        $this->requestsCount = 1;
91
        $this->regenerationTrace = time();
92
93
        $this->fingerprint = $this->generateFingerprint();
94
95
        $this->opened = true;
96
97
        return $this->opened;
98
    }
99
100
    /**
101
     * Opens the session (for a given request)
102
     *
103
     * If session hasn't been created earlier with {@link self::create()} method then:
104
     * - if argument is set to true, session will be created implicitly (behaves
105
     *   like PHP's native session_start()),
106
     * - otherwise, session won't be created and apprporiate listeners will be notified.
107
     *
108
     * If called earlier, then second (and next ones) call does nothing
109
     *
110
     * @param bool $createNewIfNotExists Create new session if not exists earlier?
111
     * @return bool Session opened?
112
     */
113
    public function open($createNewIfNotExists = false)
114
    {
115
        if (!$this->isOpened()) {
116
            if ($this->getIdHandler()->issetId()) {
117
                $this->load();
118
119
                if (!$this->getFirstTrace()) {
120
                    $this->getEmitter()->emit(new Event\NoDataOrExpired($this));
121
                } elseif ($this->isExpired()) {
122
                    $this->getEmitter()->emit(new Event\Expired($this));
123
                } elseif ($this->generateFingerprint() != $this->getFingerprint()) {
124
                    $this->getEmitter()->emit(new Event\InvalidFingerprint($this));
125
                } else {
126
                    $this->opened = true;
127
                    $this->requestsCount += 1;
128
                }
129
            } elseif ($createNewIfNotExists) {
130
                $this->create();
131
            }
132
        }
133
134
        return $this->opened;
135
    }
136
137
    /**
138
     * Is session opened?
139
     *
140
     * @return bool
141
     */
142
    public function isOpened()
143
    {
144
        return $this->opened;
145
    }
146
147
    /**
148
     * Alias of {@link self::isOpened()}.
149
     *
150
     * @return bool
151
     */
152
    public function isOpen()
153
    {
154
        return $this->isOpened();
155
    }
156
157
    /**
158
     * Is session expired?
159
     *
160
     * @return bool
161
     */
162
    public function isExpired()
163
    {
164
        return ($this->getLastTrace() + $this->getTtl() < time());
165
    }
166
167
    /**
168
     * Close the session.
169
     */
170
    public function close()
171
    {
172
        if ($this->opened) {
173
            if ($this->shouldRegenerateId()) {
174
                $this->regenerateId();
175
            }
176
177
            $this->updateLastTrace();
178
            $this->save();
179
180
            $this->values = array();
181
            $this->opened = false;
182
        }
183
    }
184
185
    /**
186
     * Destroy the session.
187
     */
188
    public function destroy()
189
    {
190
        $this->values = array();
191
        $this->getStore()->delete($this->getId());
192
        $this->getIdHandler()->unsetId();
193
    }
194
195
    /**
196
     * Get session identifier
197
     *
198
     * @return string
199
     */
200
    public function getId()
201
    {
202
        return $this->getIdHandler()->getId();
203
    }
204
205
    /**
206
     * Regenerates session id.
207
     *
208
     * Destroys current session in store and generates new id, which will be saved
209
     * at the end of script execution (together with values).
210
     *
211
     * Id is regenerated at the most once per script execution (even if called a few times).
212
     *
213
     * Mitigates Session Fixation - use it whenever the user's privilege level changes.
214
     */
215
    public function regenerateId()
216
    {
217
        if (!$this->idRegenerated) {
218
            $this->getStore()->delete($this->getId());
219
            $this->getIdHandler()->generateId();
220
221
            $this->regenerationTrace = time();
222
            $this->idRegenerated = true;
223
224
            return true;
225
        }
226
227
        return false;
228
    }
229
230
    /**
231
     * @param Id\Handler $idHandler
232
     */
233
    public function setIdHandler(Id\Handler $idHandler)
234
    {
235
        $this->idHandler = $idHandler;
236
    }
237
238
    /**
239
     * @return Id\Handler
240
     */
241
    public function getIdHandler()
242
    {
243
        if (! $this->idHandler) {
244
            $this->idHandler = new Id\Handler();
245
        }
246
247
        return $this->idHandler;
248
    }
249
250
    /**
251
     * @param int $limit
252
     */
253
    public function setIdRequestsLimit($limit)
254
    {
255
        $this->idRequestsLimit = $limit;
256
    }
257
258
    /**
259
     * @param int $ttl
260
     */
261
    public function setIdTtl($ttl)
262
    {
263
        $this->idTtl = $ttl;
264
    }
265
266
    /**
267
     * Determine if session id should be regenerated? (based on request_counter or regenerationTrace)
268
     */
269
    protected function shouldRegenerateId()
270
    {
271
        if (($this->idRequestsLimit) && ($this->requestsCount >= $this->idRequestsLimit)) {
272
            return true;
273
        }
274
275
        if (($this->idTtl && $this->regenerationTrace) && ($this->regenerationTrace + $this->idTtl < time())) {
276
            return true;
277
        }
278
279
        return false;
280
    }
281
282
    /**
283
     * @return StoreInterface
284
     */
285
    protected function getStore()
286
    {
287
        return $this->store;
288
    }
289
290
    /**
291
     * @param FingerprintGeneratorInterface $fingerprintGenerator
292
     */
293
    public function addFingerprintGenerator(FingerprintGeneratorInterface $fingerprintGenerator)
294
    {
295
        $this->fingerprintGenerators[] = $fingerprintGenerator;
296
    }
297
298
    /**
299
     * @return string
300
     */
301
    protected function generateFingerprint()
302
    {
303
        $fingerprint = '';
304
305
        foreach ($this->fingerprintGenerators as $fingerprintGenerator) {
306
            $fingerprint .= $fingerprintGenerator->generate();
307
        }
308
309
        return $fingerprint;
310
    }
311
312
    /**
313
     * @return string
314
     */
315
    public function getFingerprint()
316
    {
317
        return $this->fingerprint;
318
    }
319
320
    /**
321
     * Gets first trace timestamp.
322
     *
323
     * @return int
324
     */
325
    public function getFirstTrace()
326
    {
327
        return $this->firstTrace;
328
    }
329
330
    /**
331
     * Updates last trace timestamp.
332
     */
333
    protected function updateLastTrace()
334
    {
335
        $this->lastTrace = time();
336
    }
337
338
    /**
339
     * Gets last trace timestamp.
340
     *
341
     * @return int
342
     */
343
    public function getLastTrace()
344
    {
345
        return $this->lastTrace;
346
    }
347
348
    /**
349
     * Gets last (id) regeneration timestamp.
350
     *
351
     * @return int
352
     */
353
    public function getRegenerationTrace()
354
    {
355
        return $this->regenerationTrace;
356
    }
357
358
    /**
359
     * It must be called before {@link self::open()}.
360
     *
361
     * @param int $ttl
362
     * @throws Exception
363
     */
364
    public function setTtl($ttl)
365
    {
366
        if ($this->isOpened()) {
367
            throw new Exception('Session is already opened, ttl cannot be set');
368
        }
369
370
        if ($ttl < 1) {
371
            throw new Exception('$ttl must be greather than 0');
372
        }
373
374
        $this->ttl = (int)$ttl;
375
    }
376
377
    /**
378
     * @return int
379
     */
380
    public function getTtl()
381
    {
382
        return $this->ttl;
383
    }
384
385
    /**
386
     * @return int
387
     */
388
    public function getRequestsCount()
389
    {
390
        return $this->requestsCount;
391
    }
392
393
    /**
394
     * Sets session value in given or default namespace
395
     *
396
     * @param string $name
397
     * @param mixed $value
398
     * @param string $namespace
399
     */
400
    public function setValue($name, $value, $namespace = self::DEFAULT_NAMESPACE)
401
    {
402
        $this->values[$namespace][$name] = $value;
403
    }
404
405
    /**
406
     * Gets session value from given or default namespace
407
     *
408
     * @param string $name
409
     * @param string $namespace
410
     * @return mixed
411
     */
412
    public function getValue($name, $namespace = self::DEFAULT_NAMESPACE)
413
    {
414
        return isset($this->values[$namespace][$name]) ? $this->values[$namespace][$name] : null;
415
    }
416
417
    /**
418
     * Gets and unsets value (flash value) for given or default namespace
419
     *
420
     * @param string $name
421
     * @param string $namespace
422
     * @return mixed
423
     */
424
    public function getUnsetValue($name, $namespace = self::DEFAULT_NAMESPACE)
425
    {
426
        $value = $this->getValue($name, $namespace);
427
        $this->unsetValue($name, $namespace);
428
429
        return $value;
430
    }
431
432
    /**
433
     * Get all values for given or default namespace
434
     *
435
     * @param string $namespace
436
     * @return array
437
     */
438
    public function getValues($namespace = self::DEFAULT_NAMESPACE)
439
    {
440
        return (isset($this->values[$namespace]) ? $this->values[$namespace] : array());
441
    }
442
443
    /**
444
     * @param string $name
445
     * @param string $namespace
446
     * @return bool
447
     */
448
    public function issetValue($name, $namespace = self::DEFAULT_NAMESPACE)
449
    {
450
        return isset($this->values[$namespace][$name]);
451
    }
452
453
    /**
454
     * @param string $name
455
     * @param string $namespace
456
     */
457
    public function unsetValue($name, $namespace = self::DEFAULT_NAMESPACE)
458
    {
459
        if (isset($this->values[$namespace][$name])) {
460
            unset($this->values[$namespace][$name]);
461
        }
462
    }
463
464
    /**
465
     * @param string $namespace
466
     */
467
    public function unsetValues($namespace = self::DEFAULT_NAMESPACE)
468
    {
469
        if (isset($this->values[$namespace])) {
470
            unset($this->values[$namespace]);
471
        }
472
    }
473
474
    /**
475
     * @param mixed $offset
476
     * @param mixed $value
477
     */
478
    public function offsetSet($offset, $value)
479
    {
480
        $this->setValue($offset, $value);
481
    }
482
483
    /**
484
     * @param mixed $offset
485
     * @return mixed
486
     */
487
    public function offsetGet($offset)
488
    {
489
        return $this->getValue($offset);
490
    }
491
492
    /**
493
     * @param mixed $offset
494
     * @return bool
495
     */
496
    public function offsetExists($offset)
497
    {
498
        return $this->issetValue($offset);
499
    }
500
501
    /**
502
     * @param mixed $offset
503
     */
504
    public function offsetUnset($offset)
505
    {
506
        $this->unsetValue($offset);
507
    }
508
509
    /**
510
     * Loads session data from defined store.
511
     *
512
     * @return bool
513
     */
514
    protected function load()
515
    {
516
        $id = $this->getId();
517
        $values = $this->getStore()->fetch($id);
518
519
        if ($values === false) {
520
            return false;
521
        }
522
523
        // metadata
524
        $metadata = $values[self::METADATA_NAMESPACE];
525
        $this->firstTrace = $metadata['firstTrace'];
526
        $this->lastTrace = $metadata['lastTrace'];
527
        $this->regenerationTrace = $metadata['regenerationTrace'];
528
        $this->requestsCount = $metadata['requestsCount'];
529
        $this->fingerprint = $metadata['fingerprint'];
530
531
        // values
532
        $this->values = $values;
533
534
        return true;
535
    }
536
537
    /**
538
     * Saves session data into defined store.
539
     *
540
     * @return bool
541
     */
542
    protected function save()
543
    {
544
        $this->flash()->ageFlashData();
545
546
        $values = $this->values;
547
548
        $values[self::METADATA_NAMESPACE] = [
549
            'firstTrace' => $this->getFirstTrace(),
550
            'lastTrace' => $this->getLastTrace(),
551
            'regenerationTrace' => $this->getRegenerationTrace(),
552
            'requestsCount' => $this->getRequestsCount(),
553
            'fingerprint' => $this->getFingerprint(),
554
        ];
555
556
        return $this->getStore()->save($this->getId(), $values, $this->ttl);
557
    }
558
559
    /**
560
     * Put a key / value pair or array of key / value pairs in the session.
561
     *
562
     * @param  string|array  $key
563
     * @param  mixed       $value
564
     * @return void
565
     */
566
    public function put($key, $value = null)
567
    {
568
        if (! is_array($key)) {
569
            $key = array($key => $value);
570
        }
571
572
        foreach ($key as $arrayKey => $arrayValue) {
573
            $this->setValue($arrayKey, $arrayValue);
574
        }
575
    }
576
577
578
    /**
579
     * Push a value onto a session array.
580
     *
581
     * @param  string  $key
582
     * @param  mixed   $value
583
     * @return void
584
     */
585
    public function push($key, $value)
586
    {
587
        $array      = $this->getValue($key);
588
        $array[]    = $value;
589
        $this->setValue($key, $array);
590
    }
591
592
    /**
593
     * Remove one or many items from the session.
594
     *
595
     * @param  string|array  $keys
596
     * @return void
597
     */
598
    public function forget($keys)
599
    {
600
        $remove  = $keys;
601
602
        foreach($remove as $key){
0 ignored issues
show
The expression $remove of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
603
            $this->unsetValue($key);
604
        }
605
606
    }
607
608
609
    /**
610
     * Call the flash session handler.
611
     *
612
     * @return SessionFlash
613
     */
614
    public function flash()
615
    {
616
        if (is_null($this->flash)) {
617
            $this->flash = new SessionFlash($this);
618
        }
619
620
        return $this->flash;
621
    }
622
}
623