|
1
|
|
|
<?php |
|
2
|
|
|
defined('ROOT_PATH') || exit('Access denied'); |
|
3
|
|
|
/** |
|
4
|
|
|
* TNH Framework |
|
5
|
|
|
* |
|
6
|
|
|
* A simple PHP framework using HMVC architecture |
|
7
|
|
|
* |
|
8
|
|
|
* This content is released under the GNU GPL License (GPL) |
|
9
|
|
|
* |
|
10
|
|
|
* Copyright (C) 2017 Tony NGUEREZA |
|
11
|
|
|
* |
|
12
|
|
|
* This program is free software; you can redistribute it and/or |
|
13
|
|
|
* modify it under the terms of the GNU General Public License |
|
14
|
|
|
* as published by the Free Software Foundation; either version 3 |
|
15
|
|
|
* of the License, or (at your option) any later version. |
|
16
|
|
|
* |
|
17
|
|
|
* This program is distributed in the hope that it will be useful, |
|
18
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20
|
|
|
* GNU General Public License for more details. |
|
21
|
|
|
* |
|
22
|
|
|
* You should have received a copy of the GNU General Public License |
|
23
|
|
|
* along with this program; if not, write to the Free Software |
|
24
|
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
|
25
|
|
|
*/ |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* check if the interface "SessionHandlerInterface" exists (normally in PHP 5.4 this already exists) |
|
29
|
|
|
*/ |
|
30
|
|
|
if ( !interface_exists('SessionHandlerInterface')){ |
|
31
|
|
|
show_error('"SessionHandlerInterface" interface does not exists or is disabled can not use it to handler database session.'); |
|
32
|
|
|
} |
|
33
|
|
|
|
|
34
|
|
|
class DBSessionHandler implements SessionHandlerInterface{ |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* The encryption method to use to encrypt session data in database |
|
38
|
|
|
* @const string |
|
39
|
|
|
*/ |
|
40
|
|
|
const DB_SESSION_HASH_METHOD = 'AES-256-CBC'; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* Super global instance |
|
44
|
|
|
* @var object |
|
45
|
|
|
*/ |
|
46
|
|
|
protected $OBJ = null; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Session secret to use |
|
50
|
|
|
* @var string |
|
51
|
|
|
*/ |
|
52
|
|
|
private $sessionSecret = null; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* The initialisation vector to use for openssl |
|
56
|
|
|
* @var string |
|
57
|
|
|
*/ |
|
58
|
|
|
private $iv = null; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* The model instance to use |
|
62
|
|
|
* @var object |
|
63
|
|
|
*/ |
|
64
|
|
|
private $modelInstance = null; |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* The columns of the table to use to store session data |
|
68
|
|
|
* @var array |
|
69
|
|
|
*/ |
|
70
|
|
|
private $sessionTableColumns = array(); |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* The instance of the Log |
|
74
|
|
|
* @var Log |
|
75
|
|
|
*/ |
|
76
|
|
|
private $logger; |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* Instance of the Loader class |
|
80
|
|
|
* @var Loader |
|
81
|
|
|
*/ |
|
82
|
|
|
protected $loader = null; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Create new instance of Database session handler |
|
86
|
|
|
* @param object $modelInstance the model instance |
|
87
|
|
|
*/ |
|
88
|
|
|
public function __construct(DBSessionHandlerModel $modelInstance = null){ |
|
89
|
|
|
//Set Log instance to use |
|
90
|
|
|
$this->setLoggerFromParamOrCreate(null); |
|
91
|
|
|
|
|
92
|
|
|
//Set Loader instance to use |
|
93
|
|
|
$this->setDependencyInstanceFromParamOrCreate('loader', null, 'Loader', 'classes'); |
|
94
|
|
|
|
|
95
|
|
|
$this->OBJ = & get_instance(); |
|
96
|
|
|
|
|
97
|
|
|
if (is_object($modelInstance)){ |
|
98
|
|
|
$this->setModelInstance($modelInstance); |
|
99
|
|
|
} |
|
100
|
|
|
} |
|
101
|
|
|
|
|
102
|
|
|
/** |
|
103
|
|
|
* Set the session secret used to encrypt the data in database |
|
104
|
|
|
* @param string $secret the base64 string secret |
|
105
|
|
|
*/ |
|
106
|
|
|
public function setSessionSecret($secret){ |
|
107
|
|
|
$this->sessionSecret = $secret; |
|
108
|
|
|
return $this; |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
/** |
|
112
|
|
|
* Return the session secret |
|
113
|
|
|
* @return string |
|
114
|
|
|
*/ |
|
115
|
|
|
public function getSessionSecret(){ |
|
116
|
|
|
return $this->sessionSecret; |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* Set the initializer vector for openssl |
|
122
|
|
|
* @param string $key the session secret used in base64 format |
|
123
|
|
|
*/ |
|
124
|
|
|
public function setInitializerVector($key){ |
|
125
|
|
|
$ivLength = openssl_cipher_iv_length(self::DB_SESSION_HASH_METHOD); |
|
126
|
|
|
$key = base64_decode($key); |
|
127
|
|
|
$this->iv = substr(hash('sha256', $key), 0, $ivLength); |
|
128
|
|
|
return $this; |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* Return the initializer vector |
|
133
|
|
|
* @return string |
|
134
|
|
|
*/ |
|
135
|
|
|
public function getInitializerVector(){ |
|
136
|
|
|
return $this->iv; |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
/** |
|
140
|
|
|
* Open the database session handler, here nothing to do just return true |
|
141
|
|
|
* @param string $savePath the session save path |
|
142
|
|
|
* @param string $sessionName the session name |
|
143
|
|
|
* @return boolean |
|
144
|
|
|
*/ |
|
145
|
|
|
public function open($savePath, $sessionName){ |
|
146
|
|
|
$this->logger->debug('Opening database session handler for [' . $sessionName . ']'); |
|
147
|
|
|
//try to check if session secret is set before |
|
148
|
|
|
$secret = $this->getSessionSecret(); |
|
149
|
|
|
if (empty($secret)){ |
|
150
|
|
|
$secret = get_config('session_secret', null); |
|
151
|
|
|
$this->setSessionSecret($secret); |
|
152
|
|
|
} |
|
153
|
|
|
$this->logger->info('Session secret: ' . $secret); |
|
154
|
|
|
|
|
155
|
|
|
if (! is_object($this->modelInstance)){ |
|
156
|
|
|
$this->setModelInstanceFromConfig(); |
|
157
|
|
|
} |
|
158
|
|
|
$this->setInitializerVector($secret); |
|
159
|
|
|
|
|
160
|
|
|
//set session tables columns |
|
161
|
|
|
$this->sessionTableColumns = $this->modelInstance->getSessionTableColumns(); |
|
162
|
|
|
|
|
163
|
|
|
if (empty($this->sessionTableColumns)){ |
|
164
|
|
|
show_error('The session handler is "database" but the table columns not set'); |
|
165
|
|
|
} |
|
166
|
|
|
$this->logger->info('Database session, the model columns are listed below: ' . stringfy_vars($this->sessionTableColumns)); |
|
167
|
|
|
|
|
168
|
|
|
//delete the expired session |
|
169
|
|
|
$timeActivity = get_config('session_inactivity_time', 100); |
|
170
|
|
|
$this->gc($timeActivity); |
|
171
|
|
|
|
|
172
|
|
|
return true; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Close the session |
|
177
|
|
|
* @return boolean |
|
178
|
|
|
*/ |
|
179
|
|
|
public function close(){ |
|
180
|
|
|
$this->logger->debug('Closing database session handler'); |
|
181
|
|
|
return true; |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* Get the session value for the given session id |
|
186
|
|
|
* @param string $sid the session id to use |
|
187
|
|
|
* @return string the session data in serialiaze format |
|
188
|
|
|
*/ |
|
189
|
|
|
public function read($sid){ |
|
190
|
|
|
$this->logger->debug('Reading database session data for SID: ' . $sid); |
|
191
|
|
|
$instance = $this->getModelInstance(); |
|
192
|
|
|
$columns = $this->sessionTableColumns; |
|
193
|
|
|
$this->loader->functions('user_agent'); |
|
194
|
|
|
$this->loader->library('Browser'); |
|
195
|
|
|
|
|
196
|
|
|
$ip = get_ip(); |
|
197
|
|
|
$host = @gethostbyaddr($ip) or null; |
|
198
|
|
|
$browser = $this->OBJ->browser->getPlatform().', '.$this->OBJ->browser->getBrowser().' '.$this->OBJ->browser->getVersion(); |
|
199
|
|
|
|
|
200
|
|
|
$data = $instance->get_by(array($columns['sid'] => $sid, $columns['shost'] => $host, $columns['sbrowser'] => $browser)); |
|
201
|
|
|
if ($data && isset($data->{$columns['sdata']})){ |
|
202
|
|
|
//checking inactivity |
|
203
|
|
|
$timeInactivity = time() - get_config('session_inactivity_time', 100); |
|
204
|
|
|
if ($data->{$columns['stime']} < $timeInactivity){ |
|
205
|
|
|
$this->logger->info('Database session data for SID: ' . $sid . ' already expired, destroy it'); |
|
206
|
|
|
$this->destroy($sid); |
|
207
|
|
|
return null; |
|
208
|
|
|
} |
|
209
|
|
|
return $this->decode($data->{$columns['sdata']}); |
|
210
|
|
|
} |
|
211
|
|
|
$this->logger->info('Database session data for SID: ' . $sid . ' is not valid return false, may be the session ID is wrong'); |
|
212
|
|
|
return null; |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
/** |
|
216
|
|
|
* Save the session data |
|
217
|
|
|
* @param string $sid the session ID |
|
218
|
|
|
* @param mixed $data the session data to save in serialize format |
|
219
|
|
|
* @return boolean |
|
220
|
|
|
*/ |
|
221
|
|
|
public function write($sid, $data){ |
|
222
|
|
|
$this->logger->debug('Saving database session data for SID: ' . $sid . ', data: ' . stringfy_vars($data)); |
|
223
|
|
|
$instance = $this->getModelInstance(); |
|
224
|
|
|
$columns = $this->sessionTableColumns; |
|
225
|
|
|
|
|
226
|
|
|
$this->loader->functions('user_agent'); |
|
227
|
|
|
$this->loader->library('Browser'); |
|
228
|
|
|
|
|
229
|
|
|
$ip = get_ip(); |
|
230
|
|
|
$keyValue = $instance->getKeyValue(); |
|
231
|
|
|
$host = @gethostbyaddr($ip) or null; |
|
232
|
|
|
$browser = $this->OBJ->browser->getPlatform().', '.$this->OBJ->browser->getBrowser().' '.$this->OBJ->browser->getVersion(); |
|
233
|
|
|
$data = $this->encode($data); |
|
234
|
|
|
$params = array( |
|
235
|
|
|
$columns['sid'] => $sid, |
|
236
|
|
|
$columns['sdata'] => $data, |
|
237
|
|
|
$columns['stime'] => time(), |
|
238
|
|
|
$columns['shost'] => $host, |
|
239
|
|
|
$columns['sbrowser'] => $browser, |
|
240
|
|
|
$columns['sip'] => $ip, |
|
241
|
|
|
$columns['skey'] => $keyValue |
|
242
|
|
|
); |
|
243
|
|
|
$this->logger->info('Database session data to save are listed below :' . stringfy_vars($params)); |
|
244
|
|
|
$exists = $instance->get($sid); |
|
245
|
|
|
if ($exists){ |
|
246
|
|
|
$this->logger->info('Session data for SID: ' . $sid . ' already exists, just update it'); |
|
247
|
|
|
//update |
|
248
|
|
|
unset($params[$columns['sid']]); |
|
249
|
|
|
return $instance->update($sid, $params); |
|
250
|
|
|
} |
|
251
|
|
|
$this->logger->info('Session data for SID: ' . $sid . ' not yet exists, insert it now'); |
|
252
|
|
|
return $instance->insert($params); |
|
253
|
|
|
return true; |
|
|
|
|
|
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
|
|
257
|
|
|
/** |
|
258
|
|
|
* Destroy the session data for the given session id |
|
259
|
|
|
* @param string $sid the session id value |
|
260
|
|
|
* @return boolean |
|
261
|
|
|
*/ |
|
262
|
|
|
public function destroy($sid){ |
|
263
|
|
|
$this->logger->debug('Destroy of session data for SID: ' . $sid); |
|
264
|
|
|
$instance = $this->modelInstance; |
|
265
|
|
|
$instance->delete($sid); |
|
266
|
|
|
return true; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
/** |
|
270
|
|
|
* Clean the expire session data to save espace |
|
271
|
|
|
* @param integer $maxLifetime the max lifetime |
|
272
|
|
|
* @return boolean |
|
273
|
|
|
*/ |
|
274
|
|
|
public function gc($maxLifetime){ |
|
275
|
|
|
$instance = $this->modelInstance; |
|
276
|
|
|
$time = time() - $maxLifetime; |
|
277
|
|
|
$this->logger->debug('Garbage collector of expired session. maxLifetime [' . $maxLifetime . '] sec, expired time [' . $time . ']'); |
|
278
|
|
|
$instance->deleteByTime($time); |
|
279
|
|
|
return true; |
|
280
|
|
|
} |
|
281
|
|
|
|
|
282
|
|
|
/** |
|
283
|
|
|
* Encode the session data using the openssl |
|
284
|
|
|
* @param mixed $data the session data to encode |
|
285
|
|
|
* @return mixed the encoded session data |
|
286
|
|
|
*/ |
|
287
|
|
|
public function encode($data){ |
|
288
|
|
|
$key = base64_decode($this->sessionSecret); |
|
289
|
|
|
$dataEncrypted = openssl_encrypt($data , self::DB_SESSION_HASH_METHOD, $key, OPENSSL_RAW_DATA, $this->getInitializerVector()); |
|
290
|
|
|
$output = base64_encode($dataEncrypted); |
|
291
|
|
|
return $output; |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* Decode the session data using the openssl |
|
297
|
|
|
* @param mixed $data the data to decode |
|
298
|
|
|
* @return mixed the decoded data |
|
299
|
|
|
*/ |
|
300
|
|
|
public function decode($data){ |
|
301
|
|
|
$key = base64_decode($this->sessionSecret); |
|
302
|
|
|
$data = base64_decode($data); |
|
303
|
|
|
$data = openssl_decrypt($data, self::DB_SESSION_HASH_METHOD, $key, OPENSSL_RAW_DATA, $this->getInitializerVector()); |
|
304
|
|
|
return $data; |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
|
|
308
|
|
|
/** |
|
309
|
|
|
* Return the loader instance |
|
310
|
|
|
* @return object Loader the loader instance |
|
311
|
|
|
*/ |
|
312
|
|
|
public function getLoader(){ |
|
313
|
|
|
return $this->loader; |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* set the loader instance for future use |
|
318
|
|
|
* @param object Loader $loader the loader object |
|
319
|
|
|
*/ |
|
320
|
|
|
public function setLoader($loader){ |
|
321
|
|
|
$this->loader = $loader; |
|
322
|
|
|
return $this; |
|
323
|
|
|
} |
|
324
|
|
|
|
|
325
|
|
|
/** |
|
326
|
|
|
* Return the model instance |
|
327
|
|
|
* @return object DBSessionHandlerModel the model instance |
|
328
|
|
|
*/ |
|
329
|
|
|
public function getModelInstance(){ |
|
330
|
|
|
return $this->modelInstance; |
|
331
|
|
|
} |
|
332
|
|
|
|
|
333
|
|
|
/** |
|
334
|
|
|
* set the model instance for future use |
|
335
|
|
|
* @param DBSessionHandlerModel $modelInstance the model object |
|
336
|
|
|
*/ |
|
337
|
|
|
public function setModelInstance(DBSessionHandlerModel $modelInstance){ |
|
338
|
|
|
$this->modelInstance = $modelInstance; |
|
339
|
|
|
return $this; |
|
340
|
|
|
} |
|
341
|
|
|
|
|
342
|
|
|
/** |
|
343
|
|
|
* Return the Log instance |
|
344
|
|
|
* @return Log |
|
345
|
|
|
*/ |
|
346
|
|
|
public function getLogger(){ |
|
347
|
|
|
return $this->logger; |
|
348
|
|
|
} |
|
349
|
|
|
|
|
350
|
|
|
/** |
|
351
|
|
|
* Set the log instance |
|
352
|
|
|
* @param Log $logger the log object |
|
353
|
|
|
*/ |
|
354
|
|
|
public function setLogger(Log $logger){ |
|
355
|
|
|
$this->logger = $logger; |
|
356
|
|
|
return $this; |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
/** |
|
360
|
|
|
* Set the dependencies instance using argument or create new instance if is null |
|
361
|
|
|
* @param string $name this class property name. |
|
362
|
|
|
* @param object $instance the instance. If is not null will use it |
|
363
|
|
|
* otherwise will create new instance. |
|
364
|
|
|
* @param string $loadClassName the name of class to load using class_loader function. |
|
365
|
|
|
* @param string $loadClassPath the path of class to load using class_loader function. |
|
366
|
|
|
* |
|
367
|
|
|
* @return object this current instance |
|
368
|
|
|
*/ |
|
369
|
|
|
protected function setDependencyInstanceFromParamOrCreate($name, $instance = null, $loadClassName = null, $loadClassePath = 'classes'){ |
|
370
|
|
|
if ($instance !== null){ |
|
371
|
|
|
$this->{$name} = $instance; |
|
372
|
|
|
return $this; |
|
373
|
|
|
} |
|
374
|
|
|
$this->{$name} =& class_loader($loadClassName, $loadClassePath); |
|
375
|
|
|
return $this; |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
/** |
|
379
|
|
|
* Set the Log instance using argument or create new instance |
|
380
|
|
|
* @param object $logger the Log instance if not null |
|
381
|
|
|
* |
|
382
|
|
|
* @return object this current instance |
|
383
|
|
|
*/ |
|
384
|
|
|
protected function setLoggerFromParamOrCreate(Log $logger = null){ |
|
385
|
|
|
$this->setDependencyInstanceFromParamOrCreate('logger', $logger, 'Log', 'classes'); |
|
386
|
|
|
if ($logger === null){ |
|
387
|
|
|
$this->logger->setLogger('Library::DBSessionHandler'); |
|
388
|
|
|
} |
|
389
|
|
|
return $this; |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
/** |
|
393
|
|
|
* Set the model instance using the configuration for session |
|
394
|
|
|
*/ |
|
395
|
|
|
protected function setModelInstanceFromConfig(){ |
|
396
|
|
|
$modelName = get_config('session_save_path'); |
|
397
|
|
|
$this->logger->info('The database session model: ' . $modelName); |
|
398
|
|
|
$this->loader->model($modelName, 'dbsessionhandlerinstance'); |
|
399
|
|
|
//@codeCoverageIgnoreStart |
|
400
|
|
|
if (isset($this->OBJ->dbsessionhandlerinstance) |
|
401
|
|
|
&& ! ($this->OBJ->dbsessionhandlerinstance instanceof DBSessionHandlerModel) |
|
402
|
|
|
) { |
|
403
|
|
|
show_error('To use database session handler, your class model "' . get_class($this->OBJ->dbsessionhandlerinstance) . '" need extends "DBSessionHandlerModel"'); |
|
404
|
|
|
} |
|
405
|
|
|
//@codeCoverageIgnoreEnd |
|
406
|
|
|
|
|
407
|
|
|
//set model instance |
|
408
|
|
|
$this->modelInstance = $this->OBJ->dbsessionhandlerinstance; |
|
409
|
|
|
} |
|
410
|
|
|
} |
|
411
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.