Completed
Push — master ( bb6061...aa646e )
by Derek Stephen
01:15
created

SessionManager::unset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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