├── config ├── autoload.php └── session.php ├── libraries ├── Session.php └── session │ ├── Session_cache.php │ ├── Session_cookie.php │ ├── Session_database.php │ └── Session_native.php ├── readme.md └── spark.info /config/autoload.php: -------------------------------------------------------------------------------- 1 | load->library('driver'); 31 | } 32 | 33 | define('SESS_DRIVER_PATH', realpath(dirname(__FILE__).'/session')); 34 | 35 | class Session extends CI_Driver_Library { 36 | 37 | public $userdata = array(); 38 | public $encryption_key = ''; 39 | public $time_reference = 'time'; 40 | public $gc_probability = 5; 41 | public $sess_expiration = 7200; 42 | public $sess_expire_on_close = FALSE; 43 | public $sess_match_ip = FALSE; 44 | public $sess_match_useragent = TRUE; 45 | public $sess_time_to_update = 300; 46 | 47 | protected $_has_written = FALSE; 48 | protected $_driver; 49 | protected $valid_drivers; 50 | 51 | protected $flashdata_key = 'flash'; 52 | 53 | protected $CI; 54 | 55 | // ------------------------------------------------------------------------ 56 | 57 | /** 58 | * Setup, Run session routines 59 | * 60 | * @access public 61 | */ 62 | public function __construct($params = array()) 63 | { 64 | log_message('debug', "Session Class Initialized"); 65 | 66 | $this->CI = get_instance(); 67 | 68 | $this->CI->load->config('session'); 69 | 70 | $this->_driver = (isset($params['driver']) ? $params['driver'] : $this->CI->config->item('sess_driver')); 71 | 72 | foreach (array('sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 73 | 'sess_time_to_update', 'time_reference', 'encryption_key') as $key) 74 | { 75 | $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key); 76 | } 77 | 78 | // No need to allow multiple drivers, everything should be going to the same place 79 | $this->valid_drivers = array(strtolower(get_class($this)).'_'.$this->_driver); 80 | 81 | if ($this->encryption_key == '') 82 | { 83 | show_error('In order to use the Session class you are required to set an encryption key in your config file.'); 84 | } 85 | 86 | // Set the "now" time. Can either be GMT or server time, based on the 87 | // config prefs. We use this to set the "last activity" time 88 | $this->now = $this->_get_time(); 89 | 90 | // Set the session length. If the session expiration is 91 | // set to zero we'll set the expiration two years from now. 92 | if ($this->sess_expiration == 0) 93 | { 94 | $this->sess_expiration = (60*60*24*365*2); 95 | } 96 | 97 | // Run the Session routine. If a session doesn't exist we'll 98 | // create a new one. If it does, we'll update it. 99 | if ( ! $this->sess_read()) 100 | { 101 | $this->sess_create(); 102 | } 103 | else 104 | { 105 | $this->sess_update(); 106 | } 107 | 108 | // Delete 'old' flashdata (from last request) 109 | $this->_flashdata_sweep(); 110 | 111 | // Mark all new flashdata as old (data will be deleted before next request) 112 | $this->_flashdata_mark(); 113 | 114 | // Delete expired sessions if necessary 115 | $this->_sess_gc(); 116 | 117 | log_message('debug', "Session routines successfully run"); 118 | } 119 | 120 | // -------------------------------------------------------------------- 121 | 122 | /** 123 | * If session wasn't written by _output, try writing it here 124 | */ 125 | public function __destruct() 126 | { 127 | if(method_exists($this, 'sess_write')) 128 | $this->sess_write(); 129 | } 130 | 131 | // -------------------------------------------------------------------- 132 | 133 | /** 134 | * Serialize an array 135 | * 136 | * This function first converts any slashes found in the array to a temporary 137 | * marker, so when it gets unserialized the slashes will be preserved 138 | * 139 | * @access public 140 | * @param array 141 | * @return string 142 | */ 143 | public function _serialize($data) 144 | { 145 | if (is_array($data)) 146 | { 147 | foreach ($data as $key => $val) 148 | { 149 | if (is_string($val)) 150 | { 151 | $data[$key] = str_replace('\\', '{{slash}}', $val); 152 | } 153 | } 154 | } 155 | else 156 | { 157 | if (is_string($data)) 158 | { 159 | $data = str_replace('\\', '{{slash}}', $data); 160 | } 161 | } 162 | 163 | return serialize($data); 164 | } 165 | 166 | // -------------------------------------------------------------------- 167 | 168 | /** 169 | * Unserialize 170 | * 171 | * This function unserializes a data string, then converts any 172 | * temporary slash markers back to actual slashes 173 | * 174 | * @access public 175 | * @param array 176 | * @return string 177 | */ 178 | public function _unserialize($data) 179 | { 180 | $this->CI->load->helper('string'); 181 | 182 | $data = @unserialize(strip_slashes($data)); 183 | 184 | if (is_array($data)) 185 | { 186 | foreach ($data as $key => $val) 187 | { 188 | if (is_string($val)) 189 | { 190 | $data[$key] = str_replace('{{slash}}', '\\', $val); 191 | } 192 | } 193 | 194 | return $data; 195 | } 196 | 197 | return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data; 198 | } 199 | 200 | // -------------------------------------------------------------------- 201 | 202 | /** 203 | * Fetch a specific item from the session array 204 | * 205 | * @access public 206 | * @param string 207 | * @return string 208 | */ 209 | public function userdata($item) 210 | { 211 | return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item]; 212 | } 213 | 214 | // -------------------------------------------------------------------- 215 | 216 | /** 217 | * Fetch all session data 218 | * 219 | * @access public 220 | * @return array 221 | */ 222 | public function all_userdata() 223 | { 224 | return $this->userdata; 225 | } 226 | 227 | // -------------------------------------------------------------------- 228 | 229 | /** 230 | * Add or change data in the "userdata" array 231 | * 232 | * @access public 233 | * @param mixed 234 | * @param string 235 | * @return void 236 | */ 237 | public function set_userdata($newdata = array(), $newval = '') 238 | { 239 | if (is_string($newdata)) 240 | { 241 | $newdata = array($newdata => $newval); 242 | } 243 | 244 | if (count($newdata) > 0) 245 | { 246 | foreach ($newdata as $key => $val) 247 | { 248 | $this->userdata[$key] = $val; 249 | } 250 | } 251 | } 252 | 253 | // -------------------------------------------------------------------- 254 | 255 | /** 256 | * Delete a session variable from the "userdata" array 257 | * 258 | * @access public 259 | * @return void 260 | */ 261 | public function unset_userdata($newdata = array()) 262 | { 263 | if (is_string($newdata)) 264 | { 265 | $newdata = array($newdata => ''); 266 | } 267 | 268 | if (count($newdata) > 0) 269 | { 270 | foreach ($newdata as $key => $val) 271 | { 272 | unset($this->userdata[$key]); 273 | } 274 | } 275 | } 276 | 277 | // ------------------------------------------------------------------------ 278 | 279 | /** 280 | * Add or change flashdata, only available 281 | * until the next request 282 | * 283 | * @access public 284 | * @param mixed 285 | * @param string 286 | * @return void 287 | */ 288 | public function set_flashdata($newdata = array(), $newval = '') 289 | { 290 | if (is_string($newdata)) 291 | { 292 | $newdata = array($newdata => $newval); 293 | } 294 | 295 | if (count($newdata) > 0) 296 | { 297 | foreach ($newdata as $key => $val) 298 | { 299 | $flashdata_key = $this->flashdata_key.':new:'.$key; 300 | $this->set_userdata($flashdata_key, $val); 301 | } 302 | } 303 | } 304 | 305 | // ------------------------------------------------------------------------ 306 | 307 | /** 308 | * Keeps existing flashdata available to next request. 309 | * 310 | * @access public 311 | * @param string 312 | * @return void 313 | */ 314 | public function keep_flashdata($key) 315 | { 316 | // 'old' flashdata gets removed. Here we mark all 317 | // flashdata as 'new' to preserve it from _flashdata_sweep() 318 | // Note the function will return FALSE if the $key 319 | // provided cannot be found 320 | $old_flashdata_key = $this->flashdata_key.':old:'.$key; 321 | $value = $this->userdata($old_flashdata_key); 322 | 323 | $new_flashdata_key = $this->flashdata_key.':new:'.$key; 324 | $this->set_userdata($new_flashdata_key, $value); 325 | } 326 | 327 | // ------------------------------------------------------------------------ 328 | 329 | /** 330 | * Fetch a specific flashdata item from the session array 331 | * 332 | * @access public 333 | * @param string 334 | * @return string 335 | */ 336 | public function flashdata($key) 337 | { 338 | $flashdata_key = $this->flashdata_key.':old:'.$key; 339 | return $this->userdata($flashdata_key); 340 | } 341 | 342 | // ------------------------------------------------------------------------ 343 | 344 | /** 345 | * Identifies flashdata as 'old' for removal 346 | * when _flashdata_sweep() runs. 347 | * 348 | * @access private 349 | * @return void 350 | */ 351 | protected function _flashdata_mark() 352 | { 353 | $userdata = $this->all_userdata(); 354 | foreach ($userdata as $name => $value) 355 | { 356 | $parts = explode(':new:', $name); 357 | if (is_array($parts) && count($parts) === 2) 358 | { 359 | $new_name = $this->flashdata_key.':old:'.$parts[1]; 360 | $this->set_userdata($new_name, $value); 361 | $this->unset_userdata($name); 362 | } 363 | } 364 | } 365 | 366 | // ------------------------------------------------------------------------ 367 | 368 | /** 369 | * Removes all flashdata marked as 'old' 370 | * 371 | * @access private 372 | * @return void 373 | */ 374 | 375 | protected function _flashdata_sweep() 376 | { 377 | $userdata = $this->all_userdata(); 378 | foreach ($userdata as $key => $value) 379 | { 380 | if (strpos($key, ':old:')) 381 | { 382 | $this->unset_userdata($key); 383 | } 384 | } 385 | 386 | } 387 | 388 | // ------------------------------------------------------------------------ 389 | 390 | /** 391 | * Get the "now" time 392 | * 393 | * @access public 394 | * @return string 395 | */ 396 | public function _get_time() 397 | { 398 | $now = time(); 399 | if (strtolower($this->time_reference) == 'gmt') 400 | { 401 | return mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now)); 402 | } 403 | 404 | return $now; 405 | } 406 | 407 | // ------------------------------------------------------------------------ 408 | 409 | /** 410 | * This session has already been written, don't try to write again 411 | * 412 | * @access public 413 | * @return null 414 | */ 415 | public function track_write() 416 | { 417 | $this->_has_written = TRUE; 418 | } 419 | 420 | // ------------------------------------------------------------------------ 421 | 422 | /** 423 | * Check if session was written (presumably by _output, or manually) 424 | * 425 | * @access public 426 | * @return bool 427 | */ 428 | public function check_write() 429 | { 430 | return ( ! $this->_has_written ); 431 | } 432 | 433 | // ------------------------------------------------------------------------ 434 | 435 | /** 436 | * Call child methods when they don't exist here 437 | * 438 | * @access public 439 | * @return mixed 440 | */ 441 | public function __call($method, $arguments) 442 | { 443 | return call_user_func_array(array($this->{$this->_driver}, $method), $arguments); 444 | } 445 | 446 | // ------------------------------------------------------------------------ 447 | 448 | /** 449 | * Sparks has poor/no driver support (autoload, too), so include the driver file here if it's not found 450 | * 451 | * @access public 452 | * @return mixed 453 | */ 454 | public function __get($child) 455 | { 456 | $file = SESS_DRIVER_PATH.'/Session_'.strtolower($child).'.php'; 457 | 458 | if (file_exists($file)) 459 | { 460 | include_once($file); 461 | } 462 | 463 | return parent::__get($child); 464 | } 465 | 466 | } -------------------------------------------------------------------------------- /libraries/session/Session_cache.php: -------------------------------------------------------------------------------- 1 | CI = get_instance(); 19 | 20 | foreach (array('sess_cache_driver','sess_encrypt_cookie','sess_cookie_name','cookie_prefix','cookie_path','cookie_domain','cookie_secure') as $key) 21 | { 22 | $this->$key = $this->CI->config->item($key); 23 | } 24 | 25 | // Load cache library, assign it to the object 26 | $this->CI->load->driver('cache', array('adapter' => $this->sess_cache_driver)); 27 | } 28 | 29 | // -------------------------------------------------------------------- 30 | 31 | /** 32 | * Fetch the current session data if it exists 33 | * 34 | * @access public 35 | * @return bool 36 | */ 37 | public function sess_read() 38 | { 39 | // Fetch the cookie 40 | $session = $this->CI->input->cookie($this->sess_cookie_name); 41 | 42 | // No cookie? Goodbye cruel world!... 43 | if ($session === FALSE) 44 | { 45 | log_message('debug', 'A session cookie was not found.'); 46 | return FALSE; 47 | } 48 | 49 | // Decrypt the cookie data 50 | if ($this->sess_encrypt_cookie == TRUE) 51 | { 52 | $session = $this->CI->encrypt->decode($session); 53 | } 54 | else 55 | { 56 | // encryption was not used, so we need to check the md5 hash 57 | $hash = substr($session, strlen($session)-32); // get last 32 chars 58 | $session = substr($session, 0, strlen($session)-32); 59 | 60 | // Does the md5 hash match? This is to prevent manipulation of session data in userspace 61 | if ($hash !== md5($session.$this->parent->encryption_key)) 62 | { 63 | log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.'); 64 | $this->sess_destroy(); 65 | return FALSE; 66 | } 67 | } 68 | 69 | // Unserialize the session array 70 | $session = $this->parent->_unserialize($session); 71 | 72 | if( ! is_array($session) OR ! isset($session['session_id'])) 73 | { 74 | $this->sess_destroy(); 75 | return FALSE; 76 | } 77 | 78 | $this->sess_id = $session['session_id']; 79 | 80 | // Is there a corresponding session in the cache? 81 | $cache = $this->CI->cache->get($this->sess_id); 82 | 83 | if($cache === FALSE) 84 | { 85 | log_message('debug', 'Session not found in cache.'); 86 | return FALSE; 87 | } 88 | 89 | $cache = $this->parent->_unserialize($cache); 90 | 91 | // Is the session data we unserialized an array with the correct format? 92 | if ( ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']) OR ! is_array($cache) OR ! isset($cache['session_id']) OR ! isset($cache['ip_address']) OR ! isset($cache['user_agent']) OR ! isset($cache['last_activity'])) 93 | { 94 | $this->sess_destroy(); 95 | return FALSE; 96 | } 97 | 98 | // Is the session current? 99 | if (($session['last_activity'] + $this->parent->sess_expiration) < $this->parent->now) 100 | { 101 | $this->sess_destroy(); 102 | return FALSE; 103 | } 104 | 105 | // Does the IP Match? 106 | if ($this->parent->sess_match_ip == TRUE AND ($session['ip_address'] != $this->CI->input->ip_address() OR $cache['ip_address'] != $this->CI->input->ip_address())) 107 | { 108 | $this->sess_destroy(); 109 | return FALSE; 110 | } 111 | 112 | // Does the User Agent Match? 113 | if ($this->parent->sess_match_useragent == TRUE AND (trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)) OR trim($cache['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)))) 114 | { 115 | $this->sess_destroy(); 116 | return FALSE; 117 | } 118 | 119 | // Session is valid! 120 | $this->parent->userdata = $cache; 121 | unset($session, $cache); 122 | 123 | return TRUE; 124 | } 125 | 126 | // -------------------------------------------------------------------- 127 | 128 | /** 129 | * Write the session data 130 | * 131 | * @access public 132 | * @return void 133 | */ 134 | public function sess_write() 135 | { 136 | if( ! $this->parent->check_write()) 137 | { 138 | $this->CI->cache->save($this->sess_id, $this->parent->_serialize($this->parent->userdata), $this->parent->sess_expiration); 139 | 140 | $this->parent->track_write(); 141 | 142 | $this->_set_cookie(); 143 | } 144 | } 145 | 146 | // -------------------------------------------------------------------- 147 | 148 | /** 149 | * Create a new session 150 | * 151 | * @access public 152 | * @return void 153 | */ 154 | public function sess_create() 155 | { 156 | $sessid = ''; 157 | while (strlen($sessid) < 32) 158 | { 159 | $sessid .= mt_rand(0, mt_getrandmax()); 160 | } 161 | 162 | $this->sess_id = md5(uniqid($sessid.$this->CI->input->ip_address(), TRUE)); 163 | 164 | $this->parent->userdata = array( 165 | 'session_id' => $this->sess_id, 166 | 'ip_address' => $this->CI->input->ip_address(), 167 | 'user_agent' => substr($this->CI->input->user_agent(), 0, 120), 168 | 'last_activity' => $this->parent->now 169 | ); 170 | 171 | $custom_userdata = array('user_data' => ''); 172 | 173 | // Save session to cache 174 | $this->CI->cache->save($this->sess_id, $this->parent->userdata+$custom_userdata); 175 | 176 | // Write the cookie 177 | $this->_set_cookie(); 178 | } 179 | 180 | // -------------------------------------------------------------------- 181 | 182 | /** 183 | * Update an existing session 184 | * 185 | * @access public 186 | * @return void 187 | */ 188 | public function sess_update() 189 | { 190 | // We only update the session every five minutes by default 191 | if (($this->parent->userdata['last_activity'] + $this->parent->sess_time_to_update) >= $this->parent->now) 192 | { 193 | return; 194 | } 195 | 196 | // Save the old session id so we know which record to 197 | // update in the database if we need it 198 | $old_sessid = $this->sess_id; 199 | $new_sessid = ''; 200 | while (strlen($new_sessid) < 32) 201 | { 202 | $new_sessid .= mt_rand(0, mt_getrandmax()); 203 | } 204 | 205 | // To make the session ID even more secure we'll combine it with the user's IP, then hash 206 | $this->sess_id = md5(uniqid($new_sessid.$this->CI->input->ip_address(), TRUE)); 207 | 208 | // Update the session data in the session data array 209 | $this->parent->userdata['session_id'] = $this->sess_id; 210 | $this->parent->userdata['last_activity'] = $this->now; 211 | 212 | // Save new cache, delete old cache 213 | $this->CI->cache->save($this->sess_id, $this->parent->_serialize($this->parent->userdata), $this->parent->sess_expiration); 214 | $this->CI->cache->delete($old_sessid); 215 | 216 | // Write the cookie 217 | $this->_set_cookie(); 218 | } 219 | 220 | // -------------------------------------------------------------------- 221 | 222 | /** 223 | * Destroy the current session 224 | * 225 | * @access public 226 | * @return void 227 | */ 228 | public function sess_destroy() 229 | { 230 | if( ! empty($this->sess_id)) 231 | { 232 | $this->CI->cache->delete($this->sess_id); 233 | } 234 | 235 | $this->parent->userdata = array(); 236 | $this->sess_id = ''; 237 | 238 | // Kill the cookie 239 | setcookie( 240 | $this->sess_cookie_name, 241 | addslashes(serialize(array())), 242 | ($this->parent->now - 31500000), 243 | $this->cookie_path, 244 | $this->cookie_domain, 245 | 0 246 | ); 247 | } 248 | 249 | // -------------------------------------------------------------------- 250 | 251 | /** 252 | * Does nothing for cache sessions 253 | * 254 | * @access private 255 | * @return void 256 | */ 257 | public function _sess_gc(){} 258 | 259 | // -------------------------------------------------------------------- 260 | 261 | /** 262 | * Write the session cookie 263 | * 264 | * @access private 265 | * @return void 266 | */ 267 | protected function _set_cookie() 268 | { 269 | $cookie_data = array( 270 | 'session_id' => $this->parent->userdata['session_id'], 271 | 'ip_address' => $this->parent->userdata['ip_address'], 272 | 'user_agent' => $this->parent->userdata['user_agent'], 273 | 'last_activity' => $this->parent->userdata['last_activity'] 274 | ); 275 | 276 | // Serialize the userdata for the cookie 277 | $cookie_data = $this->parent->_serialize($cookie_data); 278 | 279 | if ($this->sess_encrypt_cookie == TRUE) 280 | { 281 | $cookie_data = $this->CI->encrypt->encode($cookie_data); 282 | } 283 | else 284 | { 285 | // if encryption is not used, we provide an md5 hash to prevent userside tampering 286 | $cookie_data = $cookie_data.md5($cookie_data.$this->parent->encryption_key); 287 | } 288 | 289 | $expire = $this->parent->sess_expiration + time(); 290 | 291 | // Set the cookie 292 | setcookie( 293 | $this->sess_cookie_name, 294 | $cookie_data, 295 | $expire, 296 | $this->cookie_path, 297 | $this->cookie_domain, 298 | $this->cookie_secure 299 | ); 300 | } 301 | 302 | } -------------------------------------------------------------------------------- /libraries/session/Session_cookie.php: -------------------------------------------------------------------------------- 1 | CI = get_instance(); 22 | 23 | foreach (array('sess_encrypt_cookie', 'sess_cookie_name', 'cookie_prefix', 'cookie_path', 'cookie_domain', 'cookie_secure') as $key) 24 | { 25 | $this->$key = $this->CI->config->item($key); 26 | } 27 | 28 | // Do we need encryption? If so, load the encryption class 29 | if ($this->sess_encrypt_cookie == TRUE) 30 | { 31 | $this->CI->load->library('encrypt'); 32 | } 33 | } 34 | 35 | // -------------------------------------------------------------------- 36 | 37 | /** 38 | * Fetch the current session data if it exists 39 | * 40 | * @access public 41 | * @return bool 42 | */ 43 | public function sess_read() 44 | { 45 | // Fetch the cookie 46 | $session = $this->CI->input->cookie($this->sess_cookie_name); 47 | 48 | // No cookie? Goodbye cruel world!... 49 | if ($session === FALSE) 50 | { 51 | log_message('debug', 'A session cookie was not found.'); 52 | return FALSE; 53 | } 54 | 55 | // Decrypt the cookie data 56 | if ($this->sess_encrypt_cookie == TRUE) 57 | { 58 | $session = $this->CI->encrypt->decode($session); 59 | } 60 | else 61 | { 62 | // encryption was not used, so we need to check the md5 hash 63 | $hash = substr($session, strlen($session)-32); // get last 32 chars 64 | $session = substr($session, 0, strlen($session)-32); 65 | 66 | // Does the md5 hash match? This is to prevent manipulation of session data in userspace 67 | if ($hash !== md5($session.$this->encryption_key)) 68 | { 69 | log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.'); 70 | $this->sess_destroy(); 71 | return FALSE; 72 | } 73 | } 74 | 75 | // Unserialize the session array 76 | $session = $this->parent->_unserialize($session); 77 | 78 | // Is the session data we unserialized an array with the correct format? 79 | if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity'])) 80 | { 81 | $this->sess_destroy(); 82 | return FALSE; 83 | } 84 | 85 | // Is the session current? 86 | if (($session['last_activity'] + $this->parent->sess_expiration) < $this->parent->now) 87 | { 88 | $this->sess_destroy(); 89 | return FALSE; 90 | } 91 | 92 | // Does the IP Match? 93 | if ($this->parent->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address()) 94 | { 95 | $this->sess_destroy(); 96 | return FALSE; 97 | } 98 | 99 | // Does the User Agent Match? 100 | if ($this->parent->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120))) 101 | { 102 | $this->sess_destroy(); 103 | return FALSE; 104 | } 105 | 106 | // Session is valid! 107 | $this->parent->userdata = $session; 108 | unset($session); 109 | 110 | return TRUE; 111 | } 112 | 113 | // -------------------------------------------------------------------- 114 | 115 | /** 116 | * Write the session data 117 | * 118 | * @access public 119 | * @return void 120 | */ 121 | public function sess_write() 122 | { 123 | if( ! $this->parent->check_write()) 124 | { 125 | $this->parent->track_write(); 126 | 127 | $this->_set_cookie(); 128 | } 129 | } 130 | 131 | // -------------------------------------------------------------------- 132 | 133 | /** 134 | * Create a new session 135 | * 136 | * @access public 137 | * @return void 138 | */ 139 | public function sess_create() 140 | { 141 | $sessid = ''; 142 | while (strlen($sessid) < 32) 143 | { 144 | $sessid .= mt_rand(0, mt_getrandmax()); 145 | } 146 | 147 | // To make the session ID even more secure we'll combine it with the user's IP 148 | $sessid .= $this->CI->input->ip_address(); 149 | 150 | $this->parent->userdata = array( 151 | 'session_id' => md5(uniqid($sessid, TRUE)), 152 | 'ip_address' => $this->CI->input->ip_address(), 153 | 'user_agent' => substr($this->CI->input->user_agent(), 0, 120), 154 | 'last_activity' => $this->parent->now 155 | ); 156 | 157 | 158 | // Write the cookie 159 | $this->_set_cookie(); 160 | } 161 | 162 | // -------------------------------------------------------------------- 163 | 164 | /** 165 | * Update an existing session 166 | * 167 | * @access public 168 | * @return void 169 | */ 170 | public function sess_update() 171 | { 172 | // We only update the session every five minutes by default 173 | if (($this->parent->userdata['last_activity'] + $this->parent->sess_time_to_update) >= $this->parent->now) 174 | { 175 | return; 176 | } 177 | 178 | // Save the old session id so we know which record to 179 | // update in the database if we need it 180 | $old_sessid = $this->parent->userdata['session_id']; 181 | $new_sessid = ''; 182 | while (strlen($new_sessid) < 32) 183 | { 184 | $new_sessid .= mt_rand(0, mt_getrandmax()); 185 | } 186 | 187 | // To make the session ID even more secure we'll combine it with the user's IP 188 | $new_sessid .= $this->CI->input->ip_address(); 189 | 190 | // Turn it into a hash 191 | $new_sessid = md5(uniqid($new_sessid, TRUE)); 192 | 193 | // Update the session data in the session data array 194 | $this->parent->userdata['session_id'] = $new_sessid; 195 | $this->parent->userdata['last_activity'] = $this->parent->now; 196 | 197 | // Write the cookie 198 | $this->_set_cookie(); 199 | } 200 | 201 | // -------------------------------------------------------------------- 202 | 203 | /** 204 | * Destroy the current session 205 | * 206 | * @access public 207 | * @return void 208 | */ 209 | public function sess_destroy() 210 | { 211 | $this->parent->userdata = array(); 212 | 213 | // Kill the cookie 214 | setcookie( 215 | $this->sess_cookie_name, 216 | addslashes(serialize(array())), 217 | ($this->parent->now - 31500000), 218 | $this->cookie_path, 219 | $this->cookie_domain, 220 | 0 221 | ); 222 | } 223 | 224 | // -------------------------------------------------------------------- 225 | 226 | /** 227 | * Write the session cookie 228 | * 229 | * @access private 230 | * @return void 231 | */ 232 | protected function _set_cookie() 233 | { 234 | $cookie_data = $this->parent->userdata; 235 | 236 | // Serialize the userdata for the cookie 237 | $cookie_data = $this->parent->_serialize($cookie_data); 238 | 239 | if ($this->sess_encrypt_cookie == TRUE) 240 | { 241 | $cookie_data = $this->CI->encrypt->encode($cookie_data); 242 | } 243 | else 244 | { 245 | // if encryption is not used, we provide an md5 hash to prevent userside tampering 246 | $cookie_data = $cookie_data.md5($cookie_data.$this->parent->encryption_key); 247 | } 248 | 249 | $expire = ($this->parent->sess_expire_on_close === TRUE) ? 0 : $this->parent->sess_expiration + time(); 250 | 251 | // Set the cookie 252 | setcookie( 253 | $this->sess_cookie_name, 254 | $cookie_data, 255 | $expire, 256 | $this->cookie_path, 257 | $this->cookie_domain, 258 | $this->cookie_secure 259 | ); 260 | } 261 | 262 | // -------------------------------------------------------------------- 263 | 264 | /** 265 | * Does nothing for cookie sessions 266 | * 267 | * @access private 268 | * @return void 269 | */ 270 | public function _sess_gc(){} 271 | 272 | // -------------------------------------------------------------------- 273 | 274 | } -------------------------------------------------------------------------------- /libraries/session/Session_database.php: -------------------------------------------------------------------------------- 1 | CI = get_instance(); 24 | 25 | foreach (array('sess_encrypt_cookie','sess_table_name','sess_cookie_name','cookie_prefix','cookie_path','cookie_domain','cookie_secure') as $key) 26 | { 27 | $this->$key = $this->CI->config->item($key); 28 | } 29 | 30 | // Do we need encryption? If so, load the encryption class 31 | if ($this->sess_encrypt_cookie == TRUE) 32 | { 33 | $this->CI->load->library('encrypt'); 34 | } 35 | 36 | $this->CI->load->database(); 37 | } 38 | 39 | // -------------------------------------------------------------------- 40 | 41 | /** 42 | * Fetch the current session data if it exists 43 | * 44 | * @access public 45 | * @return bool 46 | */ 47 | public function sess_read() 48 | { 49 | // Fetch the cookie 50 | $session = $this->CI->input->cookie($this->sess_cookie_name); 51 | 52 | // No cookie? Goodbye cruel world!... 53 | if ($session === FALSE) 54 | { 55 | log_message('debug', 'A session cookie was not found.'); 56 | return FALSE; 57 | } 58 | 59 | // Decrypt the cookie data 60 | if ($this->sess_encrypt_cookie == TRUE) 61 | { 62 | $session = $this->CI->encrypt->decode($session); 63 | } 64 | else 65 | { 66 | // encryption was not used, so we need to check the md5 hash 67 | $hash = substr($session, strlen($session)-32); // get last 32 chars 68 | $session = substr($session, 0, strlen($session)-32); 69 | 70 | // Does the md5 hash match? This is to prevent manipulation of session data in userspace 71 | if ($hash !== md5($session.$this->parent->encryption_key)) 72 | { 73 | log_message('error', 'The session cookie data did not match what was expected. This could be a possible hacking attempt.'); 74 | $this->sess_destroy(); 75 | return FALSE; 76 | } 77 | } 78 | 79 | // Unserialize the session array 80 | $session = $this->parent->_unserialize($session); 81 | 82 | // Is the session data we unserialized an array with the correct format? 83 | if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity'])) 84 | { 85 | $this->sess_destroy(); 86 | return FALSE; 87 | } 88 | 89 | // Is the session current? 90 | if (($session['last_activity'] + $this->parent->sess_expiration) < $this->parent->now) 91 | { 92 | $this->sess_destroy(); 93 | return FALSE; 94 | } 95 | 96 | // Does the IP Match? 97 | if ($this->parent->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address()) 98 | { 99 | $this->sess_destroy(); 100 | return FALSE; 101 | } 102 | 103 | // Does the User Agent Match? 104 | if ($this->parent->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120))) 105 | { 106 | $this->sess_destroy(); 107 | return FALSE; 108 | } 109 | 110 | // Is there a corresponding session in the DB? 111 | $this->CI->db->where('session_id', $session['session_id']); 112 | 113 | if ($this->parent->sess_match_ip == TRUE) 114 | { 115 | $this->CI->db->where('ip_address', $session['ip_address']); 116 | } 117 | 118 | if ($this->parent->sess_match_useragent == TRUE) 119 | { 120 | $this->CI->db->where('user_agent', $session['user_agent']); 121 | } 122 | 123 | $query = $this->CI->db->get($this->sess_table_name); 124 | 125 | // No result? Kill it! 126 | if ($query->num_rows() == 0) 127 | { 128 | $this->sess_destroy(); 129 | return FALSE; 130 | } 131 | 132 | // Is there custom data? If so, add it to the main session array 133 | $row = $query->row(); 134 | if (isset($row->user_data) AND $row->user_data != '') 135 | { 136 | $custom_data = $this->parent->_unserialize($row->user_data); 137 | 138 | if (is_array($custom_data)) 139 | { 140 | foreach ($custom_data as $key => $val) 141 | { 142 | $session[$key] = $val; 143 | } 144 | } 145 | } 146 | 147 | // Session is valid! 148 | $this->parent->userdata = $session; 149 | unset($session); 150 | 151 | return TRUE; 152 | } 153 | 154 | // -------------------------------------------------------------------- 155 | 156 | /** 157 | * Write the session data 158 | * 159 | * @access public 160 | * @return void 161 | */ 162 | public function sess_write() 163 | { 164 | if( ! $this->parent->check_write()) 165 | { 166 | // set the custom userdata, the session data we will set in a second 167 | $custom_userdata = $this->parent->userdata; 168 | $cookie_userdata = array(); 169 | 170 | // Before continuing, we need to determine if there is any custom data to deal with. 171 | // Let's determine this by removing the default indexes to see if there's anything left in the array 172 | // and set the session data while we're at it 173 | foreach (array('session_id','ip_address','user_agent','last_activity') as $val) 174 | { 175 | unset($custom_userdata[$val]); 176 | $cookie_userdata[$val] = $this->parent->userdata[$val]; 177 | } 178 | 179 | // Did we find any custom data? If not, we turn the empty array into a string 180 | // since there's no reason to serialize and store an empty array in the DB 181 | if (count($custom_userdata) === 0) 182 | { 183 | $custom_userdata = ''; 184 | } 185 | else 186 | { 187 | // Serialize the custom data array so we can store it 188 | $custom_userdata = $this->parent->_serialize($custom_userdata); 189 | } 190 | 191 | // Run the update query 192 | $this->CI->db->where('session_id', $this->parent->userdata['session_id']); 193 | $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->parent->userdata['last_activity'], 'user_data' => $custom_userdata)); 194 | 195 | $this->parent->track_write(); 196 | 197 | // Write the cookie. Notice that we manually pass the cookie data array to the 198 | // _set_cookie() function. Normally that function will store $this->userdata, but 199 | // in this case that array contains custom data, which we do not want in the cookie. 200 | $this->_set_cookie($cookie_userdata); 201 | } 202 | } 203 | 204 | // -------------------------------------------------------------------- 205 | 206 | /** 207 | * Create a new session 208 | * 209 | * @access public 210 | * @return void 211 | */ 212 | public function sess_create() 213 | { 214 | $sessid = ''; 215 | while (strlen($sessid) < 32) 216 | { 217 | $sessid .= mt_rand(0, mt_getrandmax()); 218 | } 219 | 220 | // To make the session ID even more secure we'll combine it with the user's IP 221 | $sessid .= $this->CI->input->ip_address(); 222 | 223 | $this->parent->userdata = array( 224 | 'session_id' => md5(uniqid($sessid, TRUE)), 225 | 'ip_address' => $this->CI->input->ip_address(), 226 | 'user_agent' => substr($this->CI->input->user_agent(), 0, 120), 227 | 'last_activity' => $this->parent->now 228 | ); 229 | 230 | $custom_userdata = array('user_data' => ''); 231 | 232 | 233 | // Save the data to the DB 234 | $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->parent->userdata+$custom_userdata)); 235 | 236 | // Write the cookie 237 | $this->_set_cookie($this->parent->userdata); 238 | } 239 | 240 | // -------------------------------------------------------------------- 241 | 242 | /** 243 | * Update an existing session 244 | * 245 | * @access public 246 | * @return void 247 | */ 248 | public function sess_update() 249 | { 250 | // We only update the session every five minutes by default 251 | if (($this->parent->userdata['last_activity'] + $this->parent->sess_time_to_update) >= $this->parent->now) 252 | { 253 | return; 254 | } 255 | 256 | // Save the old session id so we know which record to 257 | // update in the database if we need it 258 | $old_sessid = $this->parent->userdata['session_id']; 259 | $new_sessid = ''; 260 | while (strlen($new_sessid) < 32) 261 | { 262 | $new_sessid .= mt_rand(0, mt_getrandmax()); 263 | } 264 | 265 | // To make the session ID even more secure we'll combine it with the user's IP 266 | $new_sessid .= $this->CI->input->ip_address(); 267 | 268 | // Turn it into a hash 269 | $new_sessid = md5(uniqid($new_sessid, TRUE)); 270 | 271 | // Update the session data in the session data array 272 | $this->parent->userdata['session_id'] = $new_sessid; 273 | $this->parent->userdata['last_activity'] = $this->now; 274 | 275 | // set cookie explicitly to only have our session data 276 | $cookie_data = array(); 277 | foreach (array('session_id','ip_address','user_agent','last_activity') as $val) 278 | { 279 | $cookie_data[$val] = $this->parent->userdata[$val]; 280 | } 281 | 282 | $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->parent->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid))); 283 | 284 | // Write the cookie 285 | $this->_set_cookie($cookie_data); 286 | } 287 | 288 | // -------------------------------------------------------------------- 289 | 290 | /** 291 | * Destroy the current session 292 | * 293 | * @access public 294 | * @return void 295 | */ 296 | public function sess_destroy() 297 | { 298 | // Kill the session DB row 299 | if (isset($this->parent->userdata['session_id'])) 300 | { 301 | $this->CI->db->where('session_id', $this->parent->userdata['session_id']); 302 | $this->CI->db->delete($this->sess_table_name); 303 | } 304 | 305 | // Kill the cookie 306 | setcookie( 307 | $this->sess_cookie_name, 308 | addslashes(serialize(array())), 309 | ($this->parent->now - 31500000), 310 | $this->cookie_path, 311 | $this->cookie_domain, 312 | 0 313 | ); 314 | } 315 | 316 | // -------------------------------------------------------------------- 317 | 318 | /** 319 | * Clean up old session records 320 | * 321 | * @access private 322 | * @return void 323 | */ 324 | public function _sess_gc() 325 | { 326 | srand(time()); 327 | if ((rand() % 100) < $this->gc_probability) 328 | { 329 | $expire = $this->parent->now - $this->parent->sess_expiration; 330 | 331 | $this->CI->db->where("last_activity < {$expire}"); 332 | $this->CI->db->delete($this->sess_table_name); 333 | 334 | log_message('debug', 'Session garbage collection performed.'); 335 | } 336 | } 337 | 338 | // -------------------------------------------------------------------- 339 | 340 | /** 341 | * Write the session cookie 342 | * 343 | * @access private 344 | * @return void 345 | */ 346 | protected function _set_cookie($cookie_data) 347 | { 348 | // Serialize the userdata for the cookie 349 | $cookie_data = $this->parent->_serialize($cookie_data); 350 | 351 | if ($this->sess_encrypt_cookie == TRUE) 352 | { 353 | $cookie_data = $this->CI->encrypt->encode($cookie_data); 354 | } 355 | else 356 | { 357 | // if encryption is not used, we provide an md5 hash to prevent userside tampering 358 | $cookie_data = $cookie_data.md5($cookie_data.$this->parent->encryption_key); 359 | } 360 | 361 | $expire = ($this->parent->sess_expire_on_close === TRUE) ? 0 : $this->parent->sess_expiration + time(); 362 | 363 | // Set the cookie 364 | setcookie( 365 | $this->sess_cookie_name, 366 | $cookie_data, 367 | $expire, 368 | $this->cookie_path, 369 | $this->cookie_domain, 370 | $this->cookie_secure 371 | ); 372 | } 373 | 374 | } -------------------------------------------------------------------------------- /libraries/session/Session_native.php: -------------------------------------------------------------------------------- 1 | CI = get_instance(); 12 | } 13 | 14 | // -------------------------------------------------------------------- 15 | 16 | /** 17 | * Fetch the current session data if it exists 18 | * 19 | * @access public 20 | * @return bool 21 | */ 22 | public function sess_read() 23 | { 24 | $session = $_SESSION; 25 | 26 | // Is the session data we unserialized an array with the correct format? 27 | if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity'])) 28 | { 29 | log_message('debug', 'A session was not found.'); 30 | $this->sess_destroy(FALSE); 31 | return FALSE; 32 | } 33 | 34 | // Is the session current? 35 | if (($session['last_activity'] + $this->parent->sess_expiration) < $this->parent->now) 36 | { 37 | $this->sess_destroy(FALSE); 38 | return FALSE; 39 | } 40 | 41 | // Does the IP Match? 42 | if ($this->parent->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address()) 43 | { 44 | $this->sess_destroy(FALSE); 45 | return FALSE; 46 | } 47 | 48 | // Does the User Agent Match? 49 | if ($this->parent->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120))) 50 | { 51 | $this->sess_destroy(FALSE); 52 | return FALSE; 53 | } 54 | 55 | // Session is valid! 56 | $this->parent->userdata = $session; 57 | unset($session); 58 | 59 | return TRUE; 60 | } 61 | 62 | // -------------------------------------------------------------------- 63 | 64 | /** 65 | * Write the session data 66 | * 67 | * @access public 68 | * @return void 69 | */ 70 | public function sess_write() 71 | { 72 | if( ! $this->parent->check_write()) 73 | { 74 | $_SESSION = array(); 75 | foreach($this->parent->userdata as $key => $val) 76 | { 77 | $_SESSION[$key] = $val; 78 | } 79 | 80 | $this->parent->track_write(); 81 | } 82 | } 83 | 84 | // -------------------------------------------------------------------- 85 | 86 | /** 87 | * Create a new session 88 | * 89 | * @access public 90 | * @return void 91 | */ 92 | public function sess_create() 93 | { 94 | if(session_id() == '') { 95 | session_start(); 96 | } 97 | 98 | $_SESSION['session_id'] = session_id(); 99 | $_SESSION['ip_address'] = $this->CI->input->ip_address(); 100 | $_SESSION['user_agent'] = substr($this->CI->input->user_agent(), 0, 120); 101 | $_SESSION['last_activity'] = $this->parent->now; 102 | 103 | $this->parent->userdata = $_SESSION; 104 | } 105 | 106 | // -------------------------------------------------------------------- 107 | 108 | /** 109 | * Update an existing session 110 | * 111 | * @access public 112 | * @return void 113 | */ 114 | public function sess_update() 115 | { 116 | // We only update the session every five minutes by default 117 | if (($this->parent->userdata['last_activity'] + $this->parent->sess_time_to_update) >= $this->parent->now) 118 | { 119 | return; 120 | } 121 | 122 | // Regenerate session id 123 | session_regenerate_id(); 124 | 125 | // Update the session data in the session data array 126 | $this->parent->userdata['session_id'] = session_id(); 127 | $this->parent->userdata['last_activity'] = $this->parent->now; 128 | } 129 | 130 | // -------------------------------------------------------------------- 131 | 132 | /** 133 | * Destroy the current session 134 | * 135 | * @access public 136 | * @return void 137 | */ 138 | public function sess_destroy($destroy = TRUE) 139 | { 140 | session_unset(); 141 | session_regenerate_id(); 142 | 143 | if($destroy) 144 | session_destroy(); 145 | } 146 | 147 | // -------------------------------------------------------------------- 148 | 149 | /** 150 | * Does nothing for native sessions 151 | * 152 | * @access private 153 | * @return void 154 | */ 155 | public function _sess_gc(){} 156 | 157 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #CodeIgniter Session Driver 2 | 3 | ##Requirements 4 | * CodeIgniter 2.0+ 5 | 6 | This is a replacement for CodeIgniter's session library which utilizes drivers. Currently, supported drivers are: 7 | 8 | * Cookie (default) 9 | * Database 10 | * Native 11 | * Cache 12 | 13 | There are a couple of new config options in config/session.php, and the 'sess_use_database' option is no longer used. 14 | 15 | $this->load->spark('session-driver/X.X.X'); 16 | 17 | (Replace X.X.X with the appropriate version) 18 | 19 | ###NOTE 20 | I suggest adding a call to sess_write() in the _output function of the controller, which will write the session before output and avoid issues with setting the cookie. If you don't do this, the library will try to write in __destruct(), which may or may not cause you issues. 21 | 22 | class MY_Controller extends CI_Controller { 23 | public function _output($output) 24 | { 25 | if(isset($this->session)) 26 | { 27 | $this->session->sess_write(); 28 | } 29 | 30 | echo $output; 31 | } 32 | } -------------------------------------------------------------------------------- /spark.info: -------------------------------------------------------------------------------- 1 | # This is the spark-sdk specification. It's in a magical format called YAML. 2 | # Use this format while developing your own sparks! 3 | 4 | # This is the spark name. This should be the registered name of the spark. 5 | # It is here for informational purposes only. 6 | name: session-driver 7 | 8 | # This is the current version of this spark. All sparks should be in 9 | # x.x.x format. Validation will fail otherwise. 10 | version: 0.0.7 11 | 12 | # This is the version of CodeIgniter this spark is compatible up to. It should 13 | # be in x.x.x format 14 | compatibility: 2.0.2 15 | 16 | # There are no dependencies now, but when there are, uncomment below. 17 | #dependencies: 18 | # some-spark-1: 1.0.0 19 | # some-other-spark-2: 1.0.0 --------------------------------------------------------------------------------