Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
DatabaseSessionHandler
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
7 / 7
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 close
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 destroy
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 gc
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 open
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 read
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 write
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace App\Infrastructure\Session;
6
7use SessionHandlerInterface;
8use Yiisoft\Db\Connection\ConnectionInterface;
9use Yiisoft\Db\Query\Query;
10
11/**
12 * Handles session storage in the database.
13 * OWASP Compliant: stores IP address and User Agent to detect session hijacking.
14 */
15 class DatabaseSessionHandler implements SessionHandlerInterface
16{
17    public function __construct(
18        private ConnectionInterface $db,
19        private string $tableName = '{{%users_sessions}}'
20    ) {}
21
22    public function close(): bool
23    {
24        return true;
25    }
26
27    public function destroy(string $id): bool
28    {
29        $this->db->createCommand()
30            ->delete($this->tableName, ['id' => $id])
31            ->execute();
32
33        return true;
34    }
35
36    public function gc(int $max_lifetime): int|false
37    {
38        $this->db->createCommand()
39            ->delete($this->tableName, ['<', 'last_write', time() - $max_lifetime])
40            ->execute();
41
42        return 1;
43    }
44
45    public function open(string $path, string $name): bool
46    {
47        return true;
48    }
49
50    public function read(string $id): string|false
51    {
52        $query = new Query($this->db);
53        $data = $query->select(['data'])
54            ->from($this->tableName)
55            ->where(['id' => $id])
56            ->scalar();
57
58        return $data === false ? '' : (string) $data;
59    }
60
61    public function write(string $id, string $data): bool
62    {
63        $query = new Query($this->db);
64        $exists = $query->from($this->tableName)
65            ->where(['id' => $id])
66            ->exists();
67
68        // Retrieve IP and User Agent for security auditing
69        $ip = $_SERVER['REMOTE_ADDR'] ?? null;
70        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
71
72        if ($exists) {
73            $this->db->createCommand()
74                ->update($this->tableName, [
75                    'data' => $data,
76                    'last_write' => time(),
77                    'ip_address' => $ip,
78                    'user_agent' => $userAgent,
79                ], ['id' => $id])
80                ->execute();
81        } else {
82            $this->db->createCommand()
83                ->insert($this->tableName, [
84                    'id' => $id,
85                    'data' => $data,
86                    'last_write' => time(),
87                    'ip_address' => $ip,
88                    'user_agent' => $userAgent,
89                ])
90                ->execute();
91        }
92
93        return true;
94    }
95}