SessionManager::get()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 1
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 2
rs 10
1
<?php declare(strict_types=1);
2
3
namespace Del;
4
5
final class SessionManager
6
{
7
    const IP_REGEX = '/(\d{1,3}\.\d{1,3}\.\d{1,3}\.)(\d{1,3})/';
8
9
    /**
10
     *  As this is a singleton, construction and clone are disabled
11
     *  use SessionManager::getInstance() if you need the instance
12
     */
13
    private function __construct()
14
    {
15
    }
16
17
    private function __clone()
18
    {
19
    }
20
21
    /**
22
     * @return SessionManager
23
     */
24 9
    public static function getInstance(): SessionManager
25
    {
26
        static $inst = null;
27
28 9
        if ($inst === null) {
29 1
            $inst = new SessionManager();
30
        }
31
32 9
        return $inst;
33
    }
34
35
    /**
36
     * Creates a secure session
37
     *
38
     * @param string $name
39
     * @param int $lifetime
40
     * @param string $path
41
     * @param string $domain
42
     * @param bool|null $secure
43
     */
44 9
    public static function sessionStart(string $name, int $lifetime = 0, string $path = '/', string $domain = '', ?bool $secure = null): void
45
    {
46
        // get the instance of the session manager
47 9
        $inst = self::getInstance();
48
49
        // Set the domain to default to the current domain.
50 9
        $domain = isset($domain) ? $domain : $_SERVER['SERVER_NAME'];
51
52
        // Set the default secure value to whether the site is being accessed with SSL
53 9
        $secure = isset($secure) ? $secure : isset($_SERVER['HTTPS']);
54 9
        $id = session_id();
55
56 9
        if (empty($id)) {
57 9
            session_name($name . '_Session');
58 9
            session_set_cookie_params($lifetime, $path, $domain, $secure, true);
59 9
            session_start();
60
        }
61
62
        // Make sure the session hasn't expired, and destroy it if it has
63 9
        $inst->isValid() ? $inst->initialise() :  $inst->destroySession();
64
65
    }
66
67 9
    private function initialise(): void
68
    {
69
        // Check to see if the session is a hijacking attempt
70 9
        if ($this->isHijackAttempt()) {
71
72
            // Reset session data and regenerate id
73 9
            $_SESSION = [];
74 9
            $_SESSION['ipAddress'] = $this->getIpAddress();
75 9
            $_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'] ?? '';
76 9
            $this->regenerateSession();
77
78 9
            return;
79
        }
80
81
        // Give a 5% chance of the session id changing on any request
82 4
        if ($this->shouldRandomlyRegenerate()) {
83
            $this->regenerateSession();
84
        }
85
    }
86
87
    /**
88
     * @return bool
89
     */
90 4
    private function shouldRandomlyRegenerate(): bool
91
    {
92 4
        return rand(1, 100) <= 5;
93
    }
94
95
96
    /**
97
     * Checks session IP and user agent are still the same
98
     * @return bool
99
     */
100 9
    private function isHijackAttempt(): bool
101
    {
102 9
        $ipAddress = $this->getIpAddress();
103 9
        $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
104
105 9
        if (!isset($_SESSION['ipAddress']) || !isset($_SESSION['userAgent'])) {
106 9
            return true;
107
        }
108
109 5
        if ($_SESSION['ipAddress'] != $ipAddress) {
110 1
            return true;
111
        }
112
113 4
        if ($_SESSION['userAgent'] !== $userAgent) {
114 1
            return true;
115
        }
116
117 4
        return false;
118
    }
119
120
    /**
121
     * If a site goes through the likes of Cloudflare, the last part of the IP might change
122
     * So we replace it with an x.
123
     *
124
     * @return string
125
     */
126 9
    private function getIpAddress(): string
127
    {
128 9
        $remoteAddress = $_SERVER['REMOTE_ADDR'] ?? '';
129
130 9
        return preg_replace(self::IP_REGEX, '$1x', $remoteAddress);
131
    }
132
133
    /**
134
     *  Creates a fresh session Id to make it harder to hack
135
     *  If the site is very slow in parts increase the expiry time
136
     *  10 seconds is a good default which allows ajax calls to work
137
     *  without losing the session
138
     */
139 9
    private function regenerateSession()
140
    {
141
        // If this session is obsolete it means there already is a new id
142 9
        if (isset($_SESSION['OBSOLETE']) && $_SESSION['OBSOLETE'] == true) {
143
            return;
144
        }
145
146
        // Set current session to expire in 10 seconds
147 9
        $_SESSION['OBSOLETE'] = true;
148 9
        $_SESSION['EXPIRES'] = time() + 10;
149
150
        // Create new session without destroying the old one
151 9
        session_regenerate_id(false);
152
153
        // Grab current session ID and close both sessions to allow other scripts to use them
154 9
        $newSession = session_id();
155 9
        session_write_close();
156
157
        // Set session ID to the new one, and start it back up again
158 9
        session_id($newSession);
159 9
        session_start();
160
161
        // Now we unset the obsolete and expiration values for the session we want to keep
162 9
        unset($_SESSION['OBSOLETE']);
163 9
        unset($_SESSION['EXPIRES']);
164
    }
165
166
    /**
167
     * Checks whether the session has expired or not
168
     * @return bool
169
     */
170 9
    private function isValid(): bool
171
    {
172 9
        if (isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES'])) {
173 2
            return false;
174
        }
175
176 9
        if (isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time()) {
177 1
            return false;
178
        }
179
180 9
        return true;
181
    }
182
183
    /**
184
     *  Resets the session
185
     */
186 9
    public static function destroySession()
187
    {
188 9
        $id = session_id();
189
190 9
        if (!empty($id)) {
191 9
            $_SESSION = [];
192 9
            session_destroy();
193 9
            session_start();
194
        }
195
    }
196
197
    /**
198
     * @param string $key
199
     * @param mixed $val
200
     */
201 9
    public function set(string $key, $val): void
202
    {
203 9
        $_SESSION[$key] = $val;
204
    }
205
206
    /**
207
     * @param $key
208
     * @return null|mixed
209
     */
210 9
    public function get(string $key)
211
    {
212 9
        return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
213
    }
214
215
    /**
216
     * @param string $key
217
     * @return bool
218
     */
219
    public function has(string $key): bool
220
    {
221
        return isset($_SESSION[$key]);
222
    }
223
224
    /**
225
     * @param $key
226
     * @param $val
227
     */
228 1
    public function unset(string $key): void
229
    {
230 1
        unset($_SESSION[$key]);
231
    }
232
233
    /**
234
     * @param $key
235
     * @param $val
236
     * @deprecated use unset
237
     */
238
    public function destroy(string $key): void
239
    {
240
        unset($_SESSION[$key]);
241
    }
242
}
243