-
Notifications
You must be signed in to change notification settings - Fork 7.6k
EckoSession
Code Igniter bundles a session class, working with cookies. Unfortunately, this class stores session data directly inside the cookie, thus allowing the client to see and edit those data. Here is a replacement class that stores data in the database. (note: the original Code Igniter Session class can use a database, but only for validation purposes. The actual data is stored in the cookie itself)
[h3]1.) Using the class[/h3]
This class works with CI >= 1.71 (maby also with lower versions)
The usage is the same as the bundled Code Igniter session class. So you use it like described in the user guide:
[code]http://codeigniter.com/user_guide/libraries/sessions.html[/code]
Features:
- Supports native PHP-Session-Handling
- Supports CI-flashdata-Handling
- Using a "fingerprint" instead of Browser or IP to identify the user
[h3]2.) Configuration[/h3]
This class uses the same configuration directives than the original session class. So don't forget to set inside your 'config.php' :
[code]$config['sess_cookie_name'] = 'mysite'; $config['sess_expiration'] = 7200; $config['sess_use_database'] = TRUE; $config['sess_table_name'] = 'ci_sessions'; $config['sess_match_ip'] = TRUE; $config['sess_match_useragent'] = FALSE; $config['cookie_prefix'] = ""; $config['cookie_domain'] = ""; $config['cookie_path'] = "/";[/code]
Additionally there are the folowing directives: [code]$config['sess_gc_probability'] = 0; $config['sess_gc_divisor'] = 0;[/code] Set to 0 the defaults (php.ini) are used for Garbage Collection.
[h3]3.) Database[/h3]
Here is the table schema needed by the new session class :
[code] CREATE TABLE ci_sessions
(
session_id
varchar(32) NOT NULL default '',
fingerprint
varchar(32) NOT NULL default '',
session_data
blob NOT NULL,
session_expire
int(11) NOT NULL default '0',
PRIMARY KEY (session_id
)
) ENGINE=MyISAM DEFAULT CHARSET=latin1; [/code]
[h3]4.) Installing the package[/h3]
Just get the zip File:EckoSession_1.0.1.zip.
Move the file [b]Session.php[/b] in your [b]application/libraries[/b] directory. Then use the autoload feature of Code Igniter : open your "autoload.php" configuration file and add "session" in the core autoload array :
[code]$autoload['libraries'] = array('database', 'session');[/code]
[h3]5.) Inside working[/h3]
Using this class, the cookie only stores a unique session identifier. Everything else is matched from the database.
[h3]6.) Contents of /application/libraries/Session.php:[/h3]
[code] <?php /** The EckoTools Session Library
@package The EckoTools Session Library @category Libraries @author Hartmut König ([email protected]) @link http://www.okidoe.de @version 1.0.2 @copyright Hartmut König 2009
A class to handle sessions by using a mySQL database for session related data storage providing better security then the default session handler used by PHP with added protection against Session Hijacking & Fixation including the flashdata-Feature of CI. It don't use Browser or IP to identify the user. Instead I generate a fingerprint of different seldom changing data (@link _generate_fingerprint)
To prevent session hijacking, don't forget to use the {@link regenerate_id} method whenever you do a privilege change in your application
CREATE TABLE ci_sessions
(
session_id
varchar(32) NOT NULL default '',
fingerprint
varchar(32) NOT NULL default '',
session_data
blob NOT NULL,
session_expire
int(11) NOT NULL default '0',
PRIMARY KEY (session_id
)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
This class is an adaptation between the original CI Sessions, Native Sessions and my own coding
*/
error_reporting(E_ALL);
class CI_Session {
/**
-
Constructor of class
-
Initializes the class and starts a new session
-
There is no need to call start_session() after instantiating this class
-
$gc_maxlifetime (optional) the number of seconds after which data will be seen as 'garbage' and
-
cleaned up on the next run of the gc (garbage collection) routine
-
Default is specified in php.ini file
-
$gc_probability (optional) used in conjunction with gc_divisor, is used to manage probability that
-
the gc routine is started. the probability is expressed by the formula
-
probability = $gc_probability / $gc_divisor
-
So if $gc_probability is 1 and $gc_divisor is 100 means that there is
-
a 1% chance the the gc routine will be called on each request
-
Default is specified in php.ini file
-
$gc_divisor (optional) used in conjunction with gc_probability, is used to manage probability
-
that the gc routine is started. the probability is expressed by the formula
-
probability = $gc_probability / $gc_divisor
-
So if $gc_probability is 1 and $gc_divisor is 100 means that there is
-
a 1% chance the the gc routine will be called on each request
-
Default is specified in php.ini file
-
$security_code (optional) the value of this argument is appended to the fingerprint before
-
creating the md5 hash out of it. this way we'll try to prevent fingerprint
-
spoofing
-
Default is 'LeouOeEkKpvSnD-YCHd5ogt3y'
-
$table_name (optional) You can change the name of that table by setting this property
-
Default is 'ci_sessions'
-
@return void */ function CI_Session( $security_code="LeouOeEkKpvSnD-YCHd5ogt3y",$table_name="ci_sessions" ) { //-- CI Config $this->CI = & get_instance(); $this->flashdata_key = 'flash'; // prefix for "flash" variables (eg. flash:new:message)
$table_name = $this->CI->config->item('sess_table_name'); $gc_maxlifetime = $this->CI->config->item('sess_expiration'); $gc_probability = $this->CI->config->item('sess_gc_probability'); $gc_divisor = $this->CI->config->item('sess_gc_divisor'); $sess_name = $this->CI->config->item('sess_cookie_name');// if $gc_maxlifetime is specified and is an integer number (!empty($gc_maxlifetime) && is_integer($gc_maxlifetime)) ? @ini_set('session.gc_maxlifetime', $gc_maxlifetime) : false;
// if $gc_probability is specified and is an integer number (!empty($gc_probability) && is_integer($gc_probability))
? @ini_set('session.gc_probability', $gc_probability) : false;// if $gc_divisor is specified and is an integer number (!empty($gc_divisor) && is_integer($gc_divisor))
? @ini_set('session.gc_divisor', $gc_divisor) : false;(!empty($sess_name))
? @ini_set('session.name', $sess_name) : false;// get session lifetime $this->sessionLifetime = ini_get("session.gc_maxlifetime");
// we'll use this later in order to prevent fingerprint spoofing $this->securityCode = $security_code; $this->tableName = $table_name;
// register the new handler session_set_save_handler( array(&$this, '_open'), array(&$this, '_close'), array(&$this, '_read'), array(&$this, '_write'), array(&$this, '_destroy'), array(&$this, '_gc') ); register_shutdown_function('session_write_close');
// start the session session_start();
// Delete 'old' flashdata (from last request) $this->_flashdata_sweep(); // Mark all new flashdata as old (data will be deleted before next request) $this->_flashdata_mark();
}
/**
* Reads given session attribute value
*
* @return integer sessionvalue
*/
function userdata($item)
{
//added for backward-compatibility
if($item == 'session_id')
{
return session_id();
}
if(isset($_SESSION[$item]))
{
return($_SESSION[$item]);
}
return(false);
}
/**
* Fetch all session data
*
* @access public
* @return mixed
*/
function all_userdata()
{
return ( ! isset($_SESSION)) ? FALSE : $_SESSION;
}
/**
* Sets session attributes to the given values
*
* @return void
*/
function set_userdata($newdata = array(), $newval = '')
{
(is_string($newdata))
? $newdata = array($newdata => $newval)
: false;
if(count($newdata) > 0)
{
foreach($newdata as $key => $val)
{
$_SESSION[$key] = $val;
}
}
}
/**
* Erases given session attributes
*
* @return void
*/
function unset_userdata($newdata = array())
{
(is_string($newdata))
? $newdata = array($newdata => '')
: false;
if(count($newdata) > 0)
{
foreach ($newdata as $key => $val)
{
unset($_SESSION[$key]);
}
}
}
/**
- Deletes all data related to the session
- @return void */ function sess_destroy() { $this->regenerate_id(); session_unset(); session_destroy(); }
/**
-
Regenerates the session id.
-
Call this method whenever you do a privilege change!
-
@return void */ function regenerate_id() { // saves the old session's id $oldSessionID = session_id();
// regenerates the id // this function will create a new session, with a new id and containing the data from the old session // but will not delete the old session session_regenerate_id();
// because the session_regenerate_id() function does not delete the old session, // we have to delete it manually $this->_destroy($oldSessionID); } /** * Add or change flashdata, only available * until the next request * * @access public * @param mixed * @param string * @return void */ function set_flashdata($newdata = array(), $newval = '') { if (is_string($newdata)) { $newdata = array($newdata => $newval); }
if (count($newdata) > 0) { foreach ($newdata as $key => $val) { $flashdata_key = $this->flashdata_key.':new:'.$key; $this->set_userdata($flashdata_key, $val); } } }
// ------------------------------------------------------------------------
/**
* Keeps existing flashdata available to next request.
*
* @access public
* @param string
* @return void
*/
function keep_flashdata($key)
{
// 'old' flashdata gets removed. Here we mark all
// flashdata as 'new' to preserve it from _flashdata_sweep()
// Note the function will return FALSE if the $key
// provided cannot be found
$old_flashdata_key = $this->flashdata_key.':old:'.$key;
$value = $this->userdata($old_flashdata_key);
$new_flashdata_key = $this->flashdata_key.':new:'.$key;
$this->set_userdata($new_flashdata_key, $value);
}
// ------------------------------------------------------------------------
/**
* Fetch a specific flashdata item from the session array
*
* @access public
* @param string
* @return string
*/
function flashdata($key)
{
$flashdata_key = $this->flashdata_key.':old:'.$key;
return $this->userdata($flashdata_key);
}
// ------------------------------------------------------------------------
/**
* Identifies flashdata as 'old' for removal
* when _flashdata_sweep() runs.
*
* @access private
* @return void
*/
function _flashdata_mark()
{
$userdata = $this->all_userdata();
foreach ($userdata as $name => $value)
{
$parts = explode(':new:', $name);
if (is_array($parts) && count($parts) === 2)
{
$new_name = $this->flashdata_key.':old:'.$parts[1];
$this->set_userdata($new_name, $value);
$this->unset_userdata($name);
}
}
}
// ------------------------------------------------------------------------
/**
* Removes all flashdata marked as 'old'
*
* @access private
* @return void
*/
function _flashdata_sweep()
{
$userdata = $this->all_userdata();
foreach($userdata as $key => $value)
{
if (strpos($key, ':old:'))
{
$this->unset_userdata($key);
}
}
}
/**
-
Get the number of online users
-
@return integer number of users currently online */ function get_users_online() { // counts the rows from the database $query = $this->CI->db->query("SELECT COUNT(session_id) as count FROM ".$this->tableName); $result = $query->row();
// return the number of found rows return $result->count; }
/**
* Generates key as protection against Session Hijacking & Fixation.
* @access private
* @return string
*/
function _generate_fingerprint()
{
//-- We don't use the ip-adress, because this is a subject to change in most cases (proxies etc.)
$list = array('HTTP_ACCEPT_CHARSET',
'HTTP_ACCEPT_ENCODING',
'HTTP_ACCEPT_LANGUAGE',
'HTTP_USER_AGENT');
$key = array($this->securityCode);
foreach($list as $item)
{
$key[] = $this->CI->input->server($item);
}
return md5(implode("\0", $key));
}
/**
- Custom open() function
- @access private */ function _open($save_path, $session_name) { return(true); }
/**
- Custom close() function
- @access private */ function _close() { return(true); }
/**
-
Custom read() function
-
@access private */ function _read($session_id) { // reads session data associated with the session id // but only // - if the fingerprint is the same as the one who had previously written to this session AND // - if session has not expired $result = $this->CI->db->query("SELECT session_data ". "FROM ".$this->tableName." ". "WHERE session_id = ".$this->CI->db->escape($session_id)." ". "AND fingerprint = ".$this->CI->db->escape($this->_generate_fingerprint())." ". "AND session_expire > '".time()."' LIMIT 1");
// if anything was found if($result->num_rows() > 0) { // return found data $fields = $result->row();
// Unserialization - PHP handles this automatically return $fields->session_data; }
// if there was an error return an empty string - this HAS to be an empty string return("");
}
/**
-
Custom write() function
-
@access private */ function _write($session_id, $session_data) { // insert OR update session's data - this is how it works: // first it tries to insert a new row in the database BUT if session_id is already in the database then just // update session_data and session_expire for that specific session_id // read more here http://dev.mysql.com/doc/refman/4.1/en/insert-on-duplicate.html $result = $this->CI->db->query( "INSERT INTO ".$this->tableName." (". "session_id,". "fingerprint,". "session_data,". "session_expire". ") VALUES (". $this->CI->db->escape($session_id).",". $this->CI->db->escape($this->_generate_fingerprint()).",". $this->CI->db->escape($session_data).",". $this->CI->db->escape(time() + $this->sessionLifetime). ")". "ON DUPLICATE KEY UPDATE ". "session_data = ".$this->CI->db->escape($session_data).",". "session_expire = ".$this->CI->db->escape(time() + $this->sessionLifetime));
// note that after this type of queries, mysql_affected_rows() returns // - 1 if the row was inserted // - 2 if the row was updated switch($this->CI->db->affected_rows()) { // if the row was inserted case 1: return("");
break; // if the row was updated case 2: return(true); break; // if something went wrong, return false default: return(false); break; } }
/**
-
Custom destroy() function
-
@access private */ function _destroy($session_id) { // deletes the current session id from the database $result = $this->CI->db->query("DELETE FROM ".$this->tableName." ". "WHERE session_id = ".$this->CI->db->escape($session_id));
// if anything happened if($this->CI->db->affected_rows()) { return(true); }
// if something went wrong, return false return(false); }
/**
- Custom gc() function (garbage collector)
- @access private */ function _gc($maxlifetime) { // it deletes expired sessions from database $result = $this->CI->db->query("DELETE FROM ".$this->tableName." ". "WHERE session_expire < ".$this->CI->db->escape(time() - $maxlifetime)); }
} ?>