Session.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794
  1. <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
  2. /**
  3. * CodeIgniter
  4. *
  5. * An open source application development framework for PHP 5.1.6 or newer
  6. *
  7. * @package CodeIgniter
  8. * @author EllisLab Dev Team
  9. * @copyright Copyright (c) 2008 - 2014, EllisLab, Inc.
  10. * @copyright Copyright (c) 2014 - 2015, British Columbia Institute of Technology (http://bcit.ca/)
  11. * @license http://codeigniter.com/user_guide/license.html
  12. * @link http://codeigniter.com
  13. * @since Version 1.0
  14. * @filesource
  15. */
  16. // ------------------------------------------------------------------------
  17. /**
  18. * Session Class
  19. *
  20. * @package CodeIgniter
  21. * @subpackage Libraries
  22. * @category Sessions
  23. * @author EllisLab Dev Team
  24. * @link http://codeigniter.com/user_guide/libraries/sessions.html
  25. */
  26. class CI_Session {
  27. var $sess_encrypt_cookie = FALSE;
  28. var $sess_use_database = FALSE;
  29. var $sess_table_name = '';
  30. var $sess_expiration = 7200;
  31. var $sess_expire_on_close = FALSE;
  32. var $sess_match_ip = FALSE;
  33. var $sess_match_useragent = TRUE;
  34. var $sess_cookie_name = 'ci_session';
  35. var $cookie_prefix = '';
  36. var $cookie_path = '';
  37. var $cookie_domain = '';
  38. var $cookie_secure = FALSE;
  39. var $sess_time_to_update = 300;
  40. var $encryption_key = '';
  41. var $flashdata_key = 'flash';
  42. var $time_reference = 'time';
  43. var $gc_probability = 5;
  44. var $userdata = array();
  45. var $CI;
  46. var $now;
  47. /**
  48. * Session Constructor
  49. *
  50. * The constructor runs the session routines automatically
  51. * whenever the class is instantiated.
  52. */
  53. public function __construct($params = array())
  54. {
  55. log_message('debug', "Session Class Initialized");
  56. // Set the super object to a local variable for use throughout the class
  57. $this->CI =& get_instance();
  58. // Set all the session preferences, which can either be set
  59. // manually via the $params array above or via the config file
  60. foreach (array('sess_encrypt_cookie', 'sess_use_database', 'sess_table_name', 'sess_expiration', 'sess_expire_on_close', 'sess_match_ip', 'sess_match_useragent', 'sess_cookie_name', 'cookie_path', 'cookie_domain', 'cookie_secure', 'sess_time_to_update', 'time_reference', 'cookie_prefix', 'encryption_key') as $key)
  61. {
  62. $this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
  63. }
  64. if ($this->encryption_key == '')
  65. {
  66. show_error('In order to use the Session class you are required to set an encryption key in your config file.');
  67. }
  68. // Load the string helper so we can use the strip_slashes() function
  69. $this->CI->load->helper('string');
  70. // Do we need encryption? If so, load the encryption class
  71. if ($this->sess_encrypt_cookie == TRUE)
  72. {
  73. $this->CI->load->library('encrypt');
  74. }
  75. // Are we using a database? If so, load it
  76. if ($this->sess_use_database === TRUE AND $this->sess_table_name != '')
  77. {
  78. $this->CI->load->database();
  79. }
  80. // Set the "now" time. Can either be GMT or server time, based on the
  81. // config prefs. We use this to set the "last activity" time
  82. $this->now = $this->_get_time();
  83. // Set the session length. If the session expiration is
  84. // set to zero we'll set the expiration two years from now.
  85. if ($this->sess_expiration == 0)
  86. {
  87. $this->sess_expiration = (60*60*24*365*2);
  88. }
  89. // Set the cookie name
  90. $this->sess_cookie_name = $this->cookie_prefix.$this->sess_cookie_name;
  91. // Run the Session routine. If a session doesn't exist we'll
  92. // create a new one. If it does, we'll update it.
  93. if ( ! $this->sess_read())
  94. {
  95. $this->sess_create();
  96. }
  97. else
  98. {
  99. $this->sess_update();
  100. }
  101. // Delete 'old' flashdata (from last request)
  102. $this->_flashdata_sweep();
  103. // Mark all new flashdata as old (data will be deleted before next request)
  104. $this->_flashdata_mark();
  105. // Delete expired sessions if necessary
  106. $this->_sess_gc();
  107. log_message('debug', "Session routines successfully run");
  108. }
  109. // --------------------------------------------------------------------
  110. /**
  111. * Fetch the current session data if it exists
  112. *
  113. * @access public
  114. * @return bool
  115. */
  116. function sess_read()
  117. {
  118. // Fetch the cookie
  119. $session = $this->CI->input->cookie($this->sess_cookie_name);
  120. // No cookie? Goodbye cruel world!...
  121. if ($session === FALSE)
  122. {
  123. log_message('debug', 'A session cookie was not found.');
  124. return FALSE;
  125. }
  126. // HMAC authentication
  127. $len = strlen($session) - 40;
  128. if ($len <= 0)
  129. {
  130. log_message('error', 'Session: The session cookie was not signed.');
  131. return FALSE;
  132. }
  133. // Check cookie authentication
  134. $hmac = substr($session, $len);
  135. $session = substr($session, 0, $len);
  136. // Time-attack-safe comparison
  137. $hmac_check = hash_hmac('sha1', $session, $this->encryption_key);
  138. $diff = 0;
  139. for ($i = 0; $i < 40; $i++)
  140. {
  141. $xor = ord($hmac[$i]) ^ ord($hmac_check[$i]);
  142. $diff |= $xor;
  143. }
  144. if ($diff !== 0)
  145. {
  146. log_message('error', 'Session: HMAC mismatch. The session cookie data did not match what was expected.');
  147. $this->sess_destroy();
  148. return FALSE;
  149. }
  150. // Decrypt the cookie data
  151. if ($this->sess_encrypt_cookie == TRUE)
  152. {
  153. $session = $this->CI->encrypt->decode($session);
  154. }
  155. // Unserialize the session array
  156. $session = $this->_unserialize($session);
  157. // Is the session data we unserialized an array with the correct format?
  158. if ( ! is_array($session) OR ! isset($session['session_id']) OR ! isset($session['ip_address']) OR ! isset($session['user_agent']) OR ! isset($session['last_activity']))
  159. {
  160. $this->sess_destroy();
  161. return FALSE;
  162. }
  163. // Is the session current?
  164. if (($session['last_activity'] + $this->sess_expiration) < $this->now)
  165. {
  166. $this->sess_destroy();
  167. return FALSE;
  168. }
  169. // Does the IP Match?
  170. if ($this->sess_match_ip == TRUE AND $session['ip_address'] != $this->CI->input->ip_address())
  171. {
  172. $this->sess_destroy();
  173. return FALSE;
  174. }
  175. // Does the User Agent Match?
  176. if ($this->sess_match_useragent == TRUE AND trim($session['user_agent']) != trim(substr($this->CI->input->user_agent(), 0, 120)))
  177. {
  178. $this->sess_destroy();
  179. return FALSE;
  180. }
  181. // Is there a corresponding session in the DB?
  182. if ($this->sess_use_database === TRUE)
  183. {
  184. $this->CI->db->where('session_id', $session['session_id']);
  185. if ($this->sess_match_ip == TRUE)
  186. {
  187. $this->CI->db->where('ip_address', $session['ip_address']);
  188. }
  189. if ($this->sess_match_useragent == TRUE)
  190. {
  191. $this->CI->db->where('user_agent', $session['user_agent']);
  192. }
  193. $query = $this->CI->db->get($this->sess_table_name);
  194. // No result? Kill it!
  195. if ($query->num_rows() == 0)
  196. {
  197. $this->sess_destroy();
  198. return FALSE;
  199. }
  200. // Is there custom data? If so, add it to the main session array
  201. $row = $query->row();
  202. if (isset($row->user_data) AND $row->user_data != '')
  203. {
  204. $custom_data = $this->_unserialize($row->user_data);
  205. if (is_array($custom_data))
  206. {
  207. foreach ($custom_data as $key => $val)
  208. {
  209. $session[$key] = $val;
  210. }
  211. }
  212. }
  213. }
  214. // Session is valid!
  215. $this->userdata = $session;
  216. unset($session);
  217. return TRUE;
  218. }
  219. // --------------------------------------------------------------------
  220. /**
  221. * Write the session data
  222. *
  223. * @access public
  224. * @return void
  225. */
  226. function sess_write()
  227. {
  228. // Are we saving custom data to the DB? If not, all we do is update the cookie
  229. if ($this->sess_use_database === FALSE)
  230. {
  231. $this->_set_cookie();
  232. return;
  233. }
  234. // set the custom userdata, the session data we will set in a second
  235. $custom_userdata = $this->userdata;
  236. $cookie_userdata = array();
  237. // Before continuing, we need to determine if there is any custom data to deal with.
  238. // Let's determine this by removing the default indexes to see if there's anything left in the array
  239. // and set the session data while we're at it
  240. foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
  241. {
  242. unset($custom_userdata[$val]);
  243. $cookie_userdata[$val] = $this->userdata[$val];
  244. }
  245. // Did we find any custom data? If not, we turn the empty array into a string
  246. // since there's no reason to serialize and store an empty array in the DB
  247. if (count($custom_userdata) === 0)
  248. {
  249. $custom_userdata = '';
  250. }
  251. else
  252. {
  253. // Serialize the custom data array so we can store it
  254. $custom_userdata = $this->_serialize($custom_userdata);
  255. }
  256. // Run the update query
  257. $this->CI->db->where('session_id', $this->userdata['session_id']);
  258. $this->CI->db->update($this->sess_table_name, array('last_activity' => $this->userdata['last_activity'], 'user_data' => $custom_userdata));
  259. // Write the cookie. Notice that we manually pass the cookie data array to the
  260. // _set_cookie() function. Normally that function will store $this->userdata, but
  261. // in this case that array contains custom data, which we do not want in the cookie.
  262. $this->_set_cookie($cookie_userdata);
  263. }
  264. // --------------------------------------------------------------------
  265. /**
  266. * Create a new session
  267. *
  268. * @access public
  269. * @return void
  270. */
  271. function sess_create()
  272. {
  273. $sessid = '';
  274. while (strlen($sessid) < 32)
  275. {
  276. $sessid .= mt_rand(0, mt_getrandmax());
  277. }
  278. // To make the session ID even more secure we'll combine it with the user's IP
  279. $sessid .= $this->CI->input->ip_address();
  280. $this->userdata = array(
  281. 'session_id' => md5(uniqid($sessid, TRUE)),
  282. 'ip_address' => $this->CI->input->ip_address(),
  283. 'user_agent' => substr($this->CI->input->user_agent(), 0, 120),
  284. 'last_activity' => $this->now,
  285. 'user_data' => ''
  286. );
  287. // Save the data to the DB if needed
  288. if ($this->sess_use_database === TRUE)
  289. {
  290. $this->CI->db->query($this->CI->db->insert_string($this->sess_table_name, $this->userdata));
  291. }
  292. // Write the cookie
  293. $this->_set_cookie();
  294. }
  295. // --------------------------------------------------------------------
  296. /**
  297. * Update an existing session
  298. *
  299. * @access public
  300. * @return void
  301. */
  302. function sess_update()
  303. {
  304. // We only update the session every five minutes by default
  305. if ($this->CI->input->is_ajax_request() OR ($this->userdata['last_activity'] + $this->sess_time_to_update) >= $this->now)
  306. {
  307. return;
  308. }
  309. // Save the old session id so we know which record to
  310. // update in the database if we need it
  311. $old_sessid = $this->userdata['session_id'];
  312. $new_sessid = '';
  313. while (strlen($new_sessid) < 32)
  314. {
  315. $new_sessid .= mt_rand(0, mt_getrandmax());
  316. }
  317. // To make the session ID even more secure we'll combine it with the user's IP
  318. $new_sessid .= $this->CI->input->ip_address();
  319. // Turn it into a hash
  320. $new_sessid = md5(uniqid($new_sessid, TRUE));
  321. // Update the session data in the session data array
  322. $this->userdata['session_id'] = $new_sessid;
  323. $this->userdata['last_activity'] = $this->now;
  324. // _set_cookie() will handle this for us if we aren't using database sessions
  325. // by pushing all userdata to the cookie.
  326. $cookie_data = NULL;
  327. // Update the session ID and last_activity field in the DB if needed
  328. if ($this->sess_use_database === TRUE)
  329. {
  330. // set cookie explicitly to only have our session data
  331. $cookie_data = array();
  332. foreach (array('session_id','ip_address','user_agent','last_activity') as $val)
  333. {
  334. $cookie_data[$val] = $this->userdata[$val];
  335. }
  336. $this->CI->db->query($this->CI->db->update_string($this->sess_table_name, array('last_activity' => $this->now, 'session_id' => $new_sessid), array('session_id' => $old_sessid)));
  337. }
  338. // Write the cookie
  339. $this->_set_cookie($cookie_data);
  340. }
  341. // --------------------------------------------------------------------
  342. /**
  343. * Destroy the current session
  344. *
  345. * @access public
  346. * @return void
  347. */
  348. function sess_destroy()
  349. {
  350. // Kill the session DB row
  351. if ($this->sess_use_database === TRUE && isset($this->userdata['session_id']))
  352. {
  353. $this->CI->db->where('session_id', $this->userdata['session_id']);
  354. $this->CI->db->delete($this->sess_table_name);
  355. }
  356. // Kill the cookie
  357. setcookie(
  358. $this->sess_cookie_name,
  359. addslashes(serialize(array())),
  360. ($this->now - 31500000),
  361. $this->cookie_path,
  362. $this->cookie_domain,
  363. 0
  364. );
  365. // Kill session data
  366. $this->userdata = array();
  367. }
  368. // --------------------------------------------------------------------
  369. /**
  370. * Fetch a specific item from the session array
  371. *
  372. * @access public
  373. * @param string
  374. * @return string
  375. */
  376. function userdata($item)
  377. {
  378. return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
  379. }
  380. // --------------------------------------------------------------------
  381. /**
  382. * Fetch all session data
  383. *
  384. * @access public
  385. * @return array
  386. */
  387. function all_userdata()
  388. {
  389. return $this->userdata;
  390. }
  391. // --------------------------------------------------------------------
  392. /**
  393. * Add or change data in the "userdata" array
  394. *
  395. * @access public
  396. * @param mixed
  397. * @param string
  398. * @return void
  399. */
  400. function set_userdata($newdata = array(), $newval = '')
  401. {
  402. if (is_string($newdata))
  403. {
  404. $newdata = array($newdata => $newval);
  405. }
  406. if (count($newdata) > 0)
  407. {
  408. foreach ($newdata as $key => $val)
  409. {
  410. $this->userdata[$key] = $val;
  411. }
  412. }
  413. $this->sess_write();
  414. }
  415. // --------------------------------------------------------------------
  416. /**
  417. * Delete a session variable from the "userdata" array
  418. *
  419. * @access array
  420. * @return void
  421. */
  422. function unset_userdata($newdata = array())
  423. {
  424. if (is_string($newdata))
  425. {
  426. $newdata = array($newdata => '');
  427. }
  428. if (count($newdata) > 0)
  429. {
  430. foreach ($newdata as $key => $val)
  431. {
  432. unset($this->userdata[$key]);
  433. }
  434. }
  435. $this->sess_write();
  436. }
  437. // ------------------------------------------------------------------------
  438. /**
  439. * Add or change flashdata, only available
  440. * until the next request
  441. *
  442. * @access public
  443. * @param mixed
  444. * @param string
  445. * @return void
  446. */
  447. function set_flashdata($newdata = array(), $newval = '')
  448. {
  449. if (is_string($newdata))
  450. {
  451. $newdata = array($newdata => $newval);
  452. }
  453. if (count($newdata) > 0)
  454. {
  455. foreach ($newdata as $key => $val)
  456. {
  457. $flashdata_key = $this->flashdata_key.':new:'.$key;
  458. $this->set_userdata($flashdata_key, $val);
  459. }
  460. }
  461. }
  462. // ------------------------------------------------------------------------
  463. /**
  464. * Keeps existing flashdata available to next request.
  465. *
  466. * @access public
  467. * @param string
  468. * @return void
  469. */
  470. function keep_flashdata($key)
  471. {
  472. // 'old' flashdata gets removed. Here we mark all
  473. // flashdata as 'new' to preserve it from _flashdata_sweep()
  474. // Note the function will return FALSE if the $key
  475. // provided cannot be found
  476. $old_flashdata_key = $this->flashdata_key.':old:'.$key;
  477. $value = $this->userdata($old_flashdata_key);
  478. $new_flashdata_key = $this->flashdata_key.':new:'.$key;
  479. $this->set_userdata($new_flashdata_key, $value);
  480. }
  481. // ------------------------------------------------------------------------
  482. /**
  483. * Fetch a specific flashdata item from the session array
  484. *
  485. * @access public
  486. * @param string
  487. * @return string
  488. */
  489. function flashdata($key)
  490. {
  491. $flashdata_key = $this->flashdata_key.':old:'.$key;
  492. return $this->userdata($flashdata_key);
  493. }
  494. // ------------------------------------------------------------------------
  495. /**
  496. * Identifies flashdata as 'old' for removal
  497. * when _flashdata_sweep() runs.
  498. *
  499. * @access private
  500. * @return void
  501. */
  502. function _flashdata_mark()
  503. {
  504. $userdata = $this->all_userdata();
  505. foreach ($userdata as $name => $value)
  506. {
  507. $parts = explode(':new:', $name);
  508. if (is_array($parts) && count($parts) === 2)
  509. {
  510. $new_name = $this->flashdata_key.':old:'.$parts[1];
  511. $this->set_userdata($new_name, $value);
  512. $this->unset_userdata($name);
  513. }
  514. }
  515. }
  516. // ------------------------------------------------------------------------
  517. /**
  518. * Removes all flashdata marked as 'old'
  519. *
  520. * @access private
  521. * @return void
  522. */
  523. function _flashdata_sweep()
  524. {
  525. $userdata = $this->all_userdata();
  526. foreach ($userdata as $key => $value)
  527. {
  528. if (strpos($key, ':old:'))
  529. {
  530. $this->unset_userdata($key);
  531. }
  532. }
  533. }
  534. // --------------------------------------------------------------------
  535. /**
  536. * Get the "now" time
  537. *
  538. * @access private
  539. * @return string
  540. */
  541. function _get_time()
  542. {
  543. if (strtolower($this->time_reference) == 'gmt')
  544. {
  545. $now = time();
  546. $time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
  547. }
  548. else
  549. {
  550. $time = time();
  551. }
  552. return $time;
  553. }
  554. // --------------------------------------------------------------------
  555. /**
  556. * Write the session cookie
  557. *
  558. * @access public
  559. * @return void
  560. */
  561. function _set_cookie($cookie_data = NULL)
  562. {
  563. if (is_null($cookie_data))
  564. {
  565. $cookie_data = $this->userdata;
  566. }
  567. // Serialize the userdata for the cookie
  568. $cookie_data = $this->_serialize($cookie_data);
  569. if ($this->sess_encrypt_cookie == TRUE)
  570. {
  571. $cookie_data = $this->CI->encrypt->encode($cookie_data);
  572. }
  573. $cookie_data .= hash_hmac('sha1', $cookie_data, $this->encryption_key);
  574. $expire = ($this->sess_expire_on_close === TRUE) ? 0 : $this->sess_expiration + time();
  575. // Set the cookie
  576. setcookie(
  577. $this->sess_cookie_name,
  578. $cookie_data,
  579. $expire,
  580. $this->cookie_path,
  581. $this->cookie_domain,
  582. $this->cookie_secure
  583. );
  584. }
  585. // --------------------------------------------------------------------
  586. /**
  587. * Serialize an array
  588. *
  589. * This function first converts any slashes found in the array to a temporary
  590. * marker, so when it gets unserialized the slashes will be preserved
  591. *
  592. * @access private
  593. * @param array
  594. * @return string
  595. */
  596. function _serialize($data)
  597. {
  598. if (is_array($data))
  599. {
  600. foreach ($data as $key => $val)
  601. {
  602. if (is_string($val))
  603. {
  604. $data[$key] = str_replace('\\', '{{slash}}', $val);
  605. }
  606. }
  607. }
  608. else
  609. {
  610. if (is_string($data))
  611. {
  612. $data = str_replace('\\', '{{slash}}', $data);
  613. }
  614. }
  615. return serialize($data);
  616. }
  617. // --------------------------------------------------------------------
  618. /**
  619. * Unserialize
  620. *
  621. * This function unserializes a data string, then converts any
  622. * temporary slash markers back to actual slashes
  623. *
  624. * @access private
  625. * @param array
  626. * @return string
  627. */
  628. function _unserialize($data)
  629. {
  630. $data = @unserialize(strip_slashes($data));
  631. if (is_array($data))
  632. {
  633. foreach ($data as $key => $val)
  634. {
  635. if (is_string($val))
  636. {
  637. $data[$key] = str_replace('{{slash}}', '\\', $val);
  638. }
  639. }
  640. return $data;
  641. }
  642. return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
  643. }
  644. // --------------------------------------------------------------------
  645. /**
  646. * Garbage collection
  647. *
  648. * This deletes expired session rows from database
  649. * if the probability percentage is met
  650. *
  651. * @access public
  652. * @return void
  653. */
  654. function _sess_gc()
  655. {
  656. if ($this->sess_use_database != TRUE)
  657. {
  658. return;
  659. }
  660. srand(time());
  661. if ((rand() % 100) < $this->gc_probability)
  662. {
  663. $expire = $this->now - $this->sess_expiration;
  664. $this->CI->db->where("last_activity < {$expire}");
  665. $this->CI->db->delete($this->sess_table_name);
  666. log_message('debug', 'Session garbage collection performed.');
  667. }
  668. }
  669. }
  670. // END Session Class
  671. /* End of file Session.php */
  672. /* Location: ./system/libraries/Session.php */