-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSession.php
272 lines (231 loc) · 9.75 KB
/
Session.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
<?php if (!defined('BASE_PATH')) { exit('No direct script access allowed'); }
/*
What i added:
-session_hash field md5 that gets optional user agent and/or ip to cross check against user.
-some php.ini variables
Potential further additions for this class:
-get_lock / release_lock .... apparently handy, unsure
-add flash messages functionality like codeigniter
Database Sessions
Requirements:
Create the following mysql table in your database if you are
implementing this Session Model.
CREATE TABLE compat_sessions (
session_id CHAR(32) NOT NULL,
session_hash CHAR(32) NOT NULL,
session_data TEXT NOT NULL,
session_lastaccesstime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (session_id)
);
*/
class Session extends Model
{
private $db;
public function __construct()
{
// Instantiate new Database object
$this->db = new Model();
// increase cookie security
ini_set('session.cookie_httponly', 1);
// make sure that PHP only uses cookies for sessions and disallow session ID passing as a GET parameter
ini_set('session.use_only_cookies', 1);
// Additional help in preventing spoofing
$this->securityCode = 'nSA2Cr3QgPrL';
// Allows ::1 IP address to work
$this->usingLocalhost = true;
// Checks user agent / true-false
$this->lockUserAgent = true;
// Make sure session from same ip (bad if dynamic ip)
$this->lockIp = false;
#$this->lock_timeout = 60; /////////////////////////////// research... maybe add GET_LOCK, RELEASE_LOCK
// Set handler to override SESSION
session_set_save_handler(
array($this, 'open'),
array($this, 'close'),
array($this, 'read'),
array($this, 'write'),
array($this, 'destroy'),
array($this, 'gc')
);
// The following prevents unexpected effects when using objects as save handlers
register_shutdown_function('session_write_close');
// Set the current session id from the users browser $_COOKIE["PHPSESSID"]
if (array_key_exists('PHPSESSID', $_COOKIE)) {
session_id($_COOKIE['PHPSESSID']);
} else {
session_id($this->makeSessionId());
}
// This sets a persistent cookie that lasts a day.
session_start([
'cookie_lifetime' => 86400,
]);
// Proceed to set and retrieve values by key from $_SESSION
// $_SESSION['my_key'] = 'some value';
// $my_value = $_SESSION['my_key'];
}
/*
* Opening the Session - The first stage the session goes through
* is the opening of the session file. Here you can perform any
* is the opening of the session file. Here you can perform any
* action you like; the PHP documentation indicates that this function
* should be treated as a constructor, so you could use it to initialize
* class variables if you’re using an OOP approach.
*/
function open($path, $name)
{
$sql = 'INSERT INTO compat_sessions SET session_id = :session_id' .
', session_data = :session_data, session_hash = :session_hash ON DUPLICATE KEY UPDATE session_lastaccesstime = NOW()';
$bind = [
':session_id' => session_id(),
':session_hash' => md5(($this->lockUserAgent && isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') . ($this->lockIp && isset($_SERVER['REMOTE_ADDR']) ? $this->getIP() : '') . $this->securityCode),
':session_data' => '',
];
$this->db->run($sql, $bind);
return true;
}
/*
* Immediately after the session is opened, the contents of the session
* are read from whatever store you have nominated and placed into the $_SESSION array.
* It is important to understand that this data is not pulled every time you access a
* session variable. It is only pulled at the beginning of the session life cycle when
* PHP calls the open callback and then the read callback.
*/
function read($session_id)
{
$hash = '';
// if user agent checking true then add it to hash
if ($this->lockUserAgent && isset($_SERVER['HTTP_USER_AGENT'])) {
$hash .= $_SERVER['HTTP_USER_AGENT'];
}
// if we need to identify sessions by also checking the host
if ($this->lockIp && isset($_SERVER['REMOTE_ADDR'])) {
$hash .= $_SERVER['REMOTE_ADDR'];
}
// append this to the end
$hash .= $this->securityCode;
$sql = 'SELECT session_data FROM compat_sessions WHERE session_id = :session_id AND session_hash = :session_hash';
$bind = [
':session_id' => $session_id,
':session_hash' => md5($hash),
];
$data = $this->db->run($sql, $bind);
// php 7.1 and above strictly requires the session read to return a string and not even a null value.
if (empty($data[0]['session_data'])) {
return '';
} else {
return $data[0]['session_data'];
}
}
/*
* Writing the data back to whatever store you’re using occurs either at the end of
* the script’s execution or when you call session_write_close().
*/
function write($session_id, $session_data)
{
$sql = 'INSERT INTO compat_sessions SET session_id = :session_id' .
', session_hash = :session_hash' .
', session_data = :session_data' .
', session_lastaccesstime = NOW()' .
' ON DUPLICATE KEY UPDATE session_data = :session_data';
$bind = [
':session_id' => $session_id,
':session_hash' => md5(($this->lockUserAgent && isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '') . ($this->lockIp && isset($_SERVER['REMOTE_ADDR']) ? $this->getIP() : '') . $this->securityCode),
':session_data' => $session_data,
];
$this->db->run($sql, $bind);
return true;
}
/*
* Closing the session occurs at the end of the session life cycle,
* just after the session data has been written. No parameters are
* passed to this callback so if you need to process something here
* specific to the session, you can call session_id() to obtain the ID.
*/
function close()
{
$this->session_id = session_id();
return true;
}
/*
* Destroying the session manually is essential especially when using sessions
* as a way to secure sections of your application. The callback is called when
* the session_destroy() function is called.
*
* In its default session handling capability, the session_destroy() function will
* clear the $_SESSION array of all data. The documentation on php.net states that
* any global variables or cookies (if they are used) will not cleared, so if you
* are using a custom session handler like this one you can perform these tasks in
* this callback also.
*/
function destroy($session_id)
{
$sql = 'DELETE FROM compat_sessions WHERE session_id = :session_id';
$bind = [
':session_id' => $session_id,
];
$this->db->run($sql, $bind);
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 3600, $params['path'], $params['domain'], $params['secure'], $params['httponly']);
return true;
}
/*
* Garbage Collection - The session handler needs to cater to the fact that the
* programmer won’t always have a chance to manually destroy session data.
* For example, you may destroy session data when a user logs out and it is no
* longer needed, but there’s no guarantee a user will use the logout functionality
* to trigger the deletion. The garbage collection callback will occasionally be
* invoked by PHP to clean out stale session data. The parameter that is passed
* here is the max lifetime of the session which is an integer detailing the
* number of seconds that the lifetime spans.
*/
function gc($lifetime)
{
$sql = "DELETE FROM compat_sessions WHERE session_lastaccesstime < DATE_SUB(NOW(), INTERVAL " . $lifetime . " SECOND)";
$this->db->run($sql);
return true;
}
/*
* Generate a genesis Session ID.
* Called by session->open if there is no session cookie found.
*/
function makeSessionId()
{
$seed = str_split('abcdefghijklmnopqrstuvwxyz0123456789');
$rand_id = '';
shuffle($seed);
foreach (array_rand($seed, 32) as $k) { // sessions ids are 32 chars in length.
$rand_id .= $seed[$k];
}
return $rand_id;
}
/*
* @return bool
* Get correct user IP address.
*/
public function getIP()
{
foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key)
{
if (array_key_exists($key, $_SERVER) === true)
{
foreach (array_map('trim', explode(',', $_SERVER[$key])) as $ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
if ($this->usingLocalhost && $ip === '::1') {
return $ip;
}
}
}
}
return false;
}
/*
* Simple debugger for dumping all the Session data from the database.
*/
public function getSessionData()
{
return $this->db->select('compat_sessions');
}
}