Test Failed
Push — 1.0.0-dev ( 6506b5...225896 )
by nguereza
05:07
created

DBSessionHandler::setModelInstanceFromConfig()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
eloc 9
c 1
b 1
f 0
nc 4
nop 0
dl 0
loc 17
rs 9.9666
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 name to use after load model
62
		 * @var string
63
		 */
64
		private $modelInstanceName = 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
		public function __construct(DBSessionHandlerModel $modelInstance = null, Log $logger = null, Loader $loader = null){
85
			/**
86
	         * instance of the Log class
87
	         */
88
	        if(is_object($logger)){
89
	          $this->setLogger($logger);
90
	        }
91
	        else{
92
	            $this->logger =& class_loader('Log', 'classes');
93
	            $this->logger->setLogger('Library::DBSessionHandler');
94
	        }
95
96
	        if(is_object($loader)){
97
	          $this->setLoader($loader);
98
	        }
99
		    $this->OBJ = & get_instance();
100
101
		    
102
			if(is_object($modelInstance)){
103
				$this->setModelInstance($modelInstance);
104
			}
105
		}
106
107
		/**
108
		 * Set the session secret used to encrypt the data in database 
109
		 * @param string $secret the base64 string secret
110
		 */
111
		public function setSessionSecret($secret){
112
			$this->sessionSecret = $secret;
113
			return $this;
114
		}
115
116
		/**
117
		 * Return the session secret
118
		 * @return string 
119
		 */
120
		public function getSessionSecret(){
121
			return $this->sessionSecret;
122
		}
123
124
125
		/**
126
		 * Set the initializer vector for openssl 
127
		 * @param string $key the session secret used
128
		 */
129
		public function setInitializerVector($key){
130
			$iv_length = openssl_cipher_iv_length(self::DB_SESSION_HASH_METHOD);
131
			$key = base64_decode($key);
132
			$this->iv = substr(hash('sha256', $key), 0, $iv_length);
133
			return $this;
134
		}
135
136
		/**
137
		 * Return the initializer vector
138
		 * @return string 
139
		 */
140
		public function getInitializerVector(){
141
			return $this->iv;
142
		}
143
144
		/**
145
		 * Open the database session handler, here nothing to do just return true
146
		 * @param  string $savePath    the session save path
147
		 * @param  string $sessionName the session name
148
		 * @return boolean 
149
		 */
150
		public function open($savePath, $sessionName){
151
			$this->logger->debug('Opening database session handler for [' . $sessionName . ']');
152
			//try to check if session secret is set before
153
			if(! $this->getSessionSecret()){
154
				$secret = get_config('session_secret', false);
155
				$this->setSessionSecret($secret);
0 ignored issues
show
Bug introduced by
It seems like $secret can also be of type false; however, parameter $secret of DBSessionHandler::setSessionSecret() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
				$this->setSessionSecret(/** @scrutinizer ignore-type */ $secret);
Loading history...
156
			}
157
			$this->logger->info('Session secret: ' . $this->getSessionSecret());
158
159
			if(! $this->getModelInstance()){
160
				$this->setModelInstanceFromConfig();
161
			}
162
			$this->setInitializerVector($this->getSessionSecret());
163
164
			//set session tables columns
165
			$this->sessionTableColumns = $this->getModelInstance()->getSessionTableColumns();
166
167
			if(empty($this->sessionTableColumns)){
168
				show_error('The session handler is "database" but the table columns not set');
169
			}
170
			$this->logger->info('Database session, the model columns are listed below: ' . stringfy_vars($this->sessionTableColumns));
171
			
172
			//delete the expired session
173
			$timeActivity = get_config('session_inactivity_time', 100);
174
			$this->gc($timeActivity);
0 ignored issues
show
Bug introduced by
It seems like $timeActivity can also be of type integer; however, parameter $maxLifetime of DBSessionHandler::gc() does only seem to accept ineteger, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

174
			$this->gc(/** @scrutinizer ignore-type */ $timeActivity);
Loading history...
175
			
176
			return true;
177
		}
178
179
		/**
180
		 * Close the session
181
		 * @return boolean
182
		 */
183
		public function close(){
184
			$this->logger->debug('Closing database session handler');
185
			return true;
186
		}
187
188
		/**
189
		 * Get the session value for the given session id
190
		 * @param  string $sid the session id to use
191
		 * @return mixed      the session data in serialiaze format
192
		 */
193
		public function read($sid){
194
			$this->logger->debug('Reading database session data for SID: ' . $sid);
195
			$instance = $this->getModelInstance();
196
			$columns = $this->sessionTableColumns;
197
			if($this->getLoader()){
198
				$this->getLoader()->functions('user_agent'); 
199
				$this->getLoader()->library('Browser'); 
200
			}
201
			else{
202
            	Loader::functions('user_agent');
203
            	Loader::library('Browser');
204
            }
205
			
206
			$ip = get_ip();
207
			$keyValue = $instance->getKeyValue();
0 ignored issues
show
Unused Code introduced by
The assignment to $keyValue is dead and can be removed.
Loading history...
208
			$host = @gethostbyaddr($ip) or null;
209
			$browser = $this->OBJ->browser->getPlatform().', '.$this->OBJ->browser->getBrowser().' '.$this->OBJ->browser->getVersion();
210
			
211
			$data = $instance->get_by(array($columns['sid'] => $sid, $columns['shost'] => $host, $columns['sbrowser'] => $browser));
212
			if($data && isset($data->{$columns['sdata']})){
213
				//checking inactivity 
214
				$timeInactivity = time() - get_config('session_inactivity_time', 100);
215
				if($data->{$columns['stime']} < $timeInactivity){
216
					$this->logger->info('Database session data for SID: ' . $sid . ' already expired, destroy it');
217
					$this->destroy($sid);
218
					return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by SessionHandlerInterface::read() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
219
				}
220
				return $this->decode($data->{$columns['sdata']});
221
			}
222
			$this->logger->info('Database session data for SID: ' . $sid . ' is not valid return false, may be the session ID is wrong');
223
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by SessionHandlerInterface::read() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
224
		}
225
226
		/**
227
		 * Save the session data
228
		 * @param  string $sid  the session ID
229
		 * @param  mixed $data the session data to save in serialize format
230
		 * @return boolean 
231
		 */
232
		public function write($sid, $data){
233
			$this->logger->debug('Saving database session data for SID: ' . $sid . ', data: ' . stringfy_vars($data));
234
			$instance = $this->getModelInstance();
235
			$columns = $this->sessionTableColumns;
236
237
			if($this->getLoader()){
238
				$this->getLoader()->functions('user_agent'); 
239
				$this->getLoader()->library('Browser'); 
240
			}
241
			else{
242
            	Loader::functions('user_agent');
243
            	Loader::library('Browser');
244
            }
245
246
			$ip = get_ip();
247
			$keyValue = $instance->getKeyValue();
248
			$host = @gethostbyaddr($ip) or null;
249
			$browser = $this->OBJ->browser->getPlatform().', '.$this->OBJ->browser->getBrowser().' '.$this->OBJ->browser->getVersion();
250
			$data = $this->encode($data);
251
			$params = array(
252
							$columns['sid'] => $sid,
253
							$columns['sdata'] => $data,
254
							$columns['stime'] => time(),
255
							$columns['shost'] => $host,
256
							$columns['sbrowser'] => $browser,
257
							$columns['sip'] => $ip,
258
							$columns['skey'] => $keyValue
259
						);
260
			$this->logger->info('Database session data to save are listed below :' . stringfy_vars($params));
261
			$exists = $instance->get($sid);
262
			if($exists){
263
				$this->logger->info('Session data for SID: ' . $sid . ' already exists, just update it');
264
				//update
265
				unset($params[$columns['sid']]);
266
				$instance->update($sid, $params);
267
			}
268
			else{
269
				$this->logger->info('Session data for SID: ' . $sid . ' not yet exists, insert it now');
270
				$instance->insert($params);
271
			}
272
			return true;
273
		}
274
275
276
		/**
277
		 * Destroy the session data for the given session id
278
		 * @param  string $sid the session id value
279
		 * @return boolean
280
		 */
281
		public function destroy($sid){
282
			$this->logger->debug('Destroy of session data for SID: ' . $sid);
283
			$instance = $this->modelInstanceName;
284
			$instance->delete($sid);
285
			return true;
286
		}
287
288
		/**
289
		 * Clean the expire session data to save espace
290
		 * @param  ineteger $maxLifetime the max lifetime
0 ignored issues
show
Bug introduced by
The type ineteger was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
291
		 * @return boolean
292
		 */
293
		public function gc($maxLifetime){
294
			$instance = $this->modelInstanceName;
295
			$time = time() - $maxLifetime;
296
			$this->logger->debug('Garbage collector of expired session. maxLifetime [' . $maxLifetime . '] sec, expired time [' . $time . ']');
297
			$instance->deleteByTime($time);
298
			return true;
299
		}
300
301
		/**
302
		 * Encode the session data using the openssl
303
		 * @param  mixed $data the session data to encode
304
		 * @return mixed the encoded session data
305
		 */
306
		public function encode($data){
307
			$key = base64_decode($this->sessionSecret);
308
			$dataEncrypted = openssl_encrypt($data , self::DB_SESSION_HASH_METHOD, $key, OPENSSL_RAW_DATA, $this->getInitializerVector());
309
			$output = base64_encode($dataEncrypted);
310
			return $output;
311
		}
312
313
314
		/**
315
		 * Decode the session data using the openssl
316
		 * @param  mixed $data the data to decode
317
		 * @return mixed       the decoded data
318
		 */
319
		public function decode($data){
320
			$key = base64_decode($this->sessionSecret);
321
			$data = base64_decode($data);
322
			$data = openssl_decrypt($data, self::DB_SESSION_HASH_METHOD, $key, OPENSSL_RAW_DATA, $this->getInitializerVector());
323
			return $data;
324
		}
325
326
		
327
		/**
328
         * Return the loader instance
329
         * @return Loader the loader instance
330
         */
331
        public function getLoader(){
332
            return $this->loader;
333
        }
334
335
        /**
336
         * set the loader instance for future use
337
         * @param Loader $loader the loader object
338
         */
339
         public function setLoader($loader){
340
            $this->loader = $loader;
341
            return $this;
342
        }
343
344
        /**
345
         * Return the model instance
346
         * @return DBSessionHandlerModel the model instance
347
         */
348
        public function getModelInstance(){
349
            return $this->modelInstanceName;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->modelInstanceName returns the type string which is incompatible with the documented return type DBSessionHandlerModel.
Loading history...
350
        }
351
352
        /**
353
         * set the model instance for future use
354
         * @param DBSessionHandlerModel $modelInstance the model object
355
         */
356
         public function setModelInstance(DBSessionHandlerModel $modelInstance){
357
            $this->modelInstanceName = $modelInstance;
0 ignored issues
show
Documentation Bug introduced by
It seems like $modelInstance of type DBSessionHandlerModel is incompatible with the declared type string of property $modelInstanceName.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
358
            return $this;
359
        }
360
361
        /**
362
	     * Return the Log instance
363
	     * @return Log
364
	     */
365
	    public function getLogger(){
366
	      return $this->logger;
367
	    }
368
369
	    /**
370
	     * Set the log instance
371
	     * @param Log $logger the log object
372
	     */
373
	    public function setLogger(Log $logger){
374
	      $this->logger = $logger;
375
	      return $this;
376
	    }
377
378
	    /**
379
	     * Set the model instance using the configuration for session
380
	     */
381
	    private function setModelInstanceFromConfig(){
382
	    	$modelName = get_config('session_save_path');
383
			$this->logger->info('The database session model: ' . $modelName);
384
			if($this->getLoader()){
385
				$this->getLoader()->model($modelName, 'dbsessionhandlerinstance'); 
386
			}
387
			//@codeCoverageIgnoreStart
388
			else{
389
            	Loader::model($modelName, 'dbsessionhandlerinstance'); 
390
            }
391
            if(isset($this->OBJ->dbsessionhandlerinstance) && ! $this->OBJ->dbsessionhandlerinstance instanceof DBSessionHandlerModel){
392
				show_error('To use database session handler, your class model "'.get_class($this->OBJ->dbsessionhandlerinstance).'" need extends "DBSessionHandlerModel"');
393
			}  
394
			//@codeCoverageIgnoreEnd
395
			
396
			//set model instance
397
			$this->setModelInstance($this->OBJ->dbsessionhandlerinstance);
398
	    }
399
	}
400