Completed
Push — master ( fbc89b...961d9b )
by Derek Stephen
01:41
created

SessionManager::getIpAddress()   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 0
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
96
        if (!isset($_SESSION['ipAddress']) || !isset($_SESSION['userAgent'])) {
97
            return false;
98
        }
99
100
        if ($_SESSION['ipAddress'] != $ipAddress) {
101
            return false;
102
        }
103
104
        if ($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT']) {
105
            return false;
106
        }
107
108
        return true;
109
    }
110
111
    /**
112
     * If a site goes through the likes of Cloudflare, the last part of the IP might change
113
     * So we replace it with an x.
114
     *
115
     * @return string
116
     */
117
    private function getIpAddress(): string
118
    {
119
        return preg_replace(SessionManager::IP_REGEX, '$1x', $_SERVER['REMOTE_ADDR']);
120
    }
121
122
    /**
123
     *  Creates a fresh session Id to make it harder to hack
124
     *  If the site is very slow in parts increase the expiry time
125
     *  10 seconds is a good default which allows ajax calls to work
126
     *  without losing the session
127
     */
128
    private function regenerateSession()
129
    {
130
        // If this session is obsolete it means there already is a new id
131
        if (isset($_SESSION['OBSOLETE']) && $_SESSION['OBSOLETE'] == true) {
132
            return;
133
        }
134
135
        // Set current session to expire in 10 seconds
136
        $_SESSION['OBSOLETE'] = true;
137
        $_SESSION['EXPIRES'] = time() + 10;
138
139
        // Create new session without destroying the old one
140
        session_regenerate_id(false);
141
142
        // Grab current session ID and close both sessions to allow other scripts to use them
143
        $newSession = session_id();
144
        session_write_close();
145
146
        // Set session ID to the new one, and start it back up again
147
        session_id($newSession);
148
        session_start();
149
150
        // Now we unset the obsolete and expiration values for the session we want to keep
151
        unset($_SESSION['OBSOLETE']);
152
        unset($_SESSION['EXPIRES']);
153
    }
154
155
    /**
156
     * Checks whether the session has expired or not
157
     * @return bool
158
     */
159
    private function validateSession()
160
    {
161
        if (isset($_SESSION['OBSOLETE']) && !isset($_SESSION['EXPIRES'])) {
162
            return false;
163
        }
164
165
        if (isset($_SESSION['EXPIRES']) && $_SESSION['EXPIRES'] < time()) {
166
            return false;
167
        }
168
169
        return true;
170
    }
171
172
    /**
173
     *  Resets the session
174
     */
175
    public static function destroySession()
176
    {
177
        $id = session_id();
178
179
        if(!empty($id)) {
180
            $_SESSION = array();
181
            session_destroy();
182
            session_start();
183
        }
184
    }
185
186
    /**
187
     * @param string $key
188
     * @param mixed $val
189
     */
190
    public static function set(string $key, $val):  void
191
    {
192
        $_SESSION[$key] = $val;
193
    }
194
195
    /**
196
     * @param $key
197
     * @return null
198
     */
199
    public static function get(string $key)
200
    {
201
        return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
202
    }
203
204
    /**
205
     * @param $key
206
     * @param $val
207
     */
208
    public static function destroy(string $key): void
209
    {
210
        unset($_SESSION[$key]);
211
    }
212
}
213