PageRenderTime 27ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/classes/session/database.php

https://gitlab.com/unofficial-mirrors/moodle
PHP | 303 lines | 144 code | 32 blank | 127 comment | 22 complexity | 17c9b339c37f82da298557cfeb7b2d44 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Database based session handler.
  18. *
  19. * @package core
  20. * @copyright 2013 Petr Skoda {@link http://skodak.org}
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. namespace core\session;
  24. defined('MOODLE_INTERNAL') || die();
  25. /**
  26. * Database based session handler.
  27. *
  28. * @package core
  29. * @copyright 2013 Petr Skoda {@link http://skodak.org}
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. */
  32. class database extends handler {
  33. /** @var \stdClass $record session record */
  34. protected $recordid = null;
  35. /** @var \moodle_database $database session database */
  36. protected $database = null;
  37. /** @var bool $failed session read/init failed, do not write back to DB */
  38. protected $failed = false;
  39. /** @var string $lasthash hash of the session data content */
  40. protected $lasthash = null;
  41. /** @var int $acquiretimeout how long to wait for session lock */
  42. protected $acquiretimeout = 120;
  43. /**
  44. * Create new instance of handler.
  45. */
  46. public function __construct() {
  47. global $DB, $CFG;
  48. // Note: we store the reference here because we need to modify database in shutdown handler.
  49. $this->database = $DB;
  50. if (!empty($CFG->session_database_acquire_lock_timeout)) {
  51. $this->acquiretimeout = (int)$CFG->session_database_acquire_lock_timeout;
  52. }
  53. }
  54. /**
  55. * Init session handler.
  56. */
  57. public function init() {
  58. if (!$this->database->session_lock_supported()) {
  59. throw new exception('sessionhandlerproblem', 'error', '', null, 'Database does not support session locking');
  60. }
  61. $result = session_set_save_handler(array($this, 'handler_open'),
  62. array($this, 'handler_close'),
  63. array($this, 'handler_read'),
  64. array($this, 'handler_write'),
  65. array($this, 'handler_destroy'),
  66. array($this, 'handler_gc'));
  67. if (!$result) {
  68. throw new exception('dbsessionhandlerproblem', 'error');
  69. }
  70. }
  71. /**
  72. * Check the backend contains data for this session id.
  73. *
  74. * Note: this is intended to be called from manager::session_exists() only.
  75. *
  76. * @param string $sid
  77. * @return bool true if session found.
  78. */
  79. public function session_exists($sid) {
  80. // It was already checked in the calling code that the record in sessions table exists.
  81. return true;
  82. }
  83. /**
  84. * Kill all active sessions, the core sessions table is
  85. * purged afterwards.
  86. */
  87. public function kill_all_sessions() {
  88. // Nothing to do, the sessions table is cleared from core.
  89. return;
  90. }
  91. /**
  92. * Kill one session, the session record is removed afterwards.
  93. * @param string $sid
  94. */
  95. public function kill_session($sid) {
  96. // Nothing to do, the sessions table is purged afterwards.
  97. return;
  98. }
  99. /**
  100. * Open session handler.
  101. *
  102. * {@see http://php.net/manual/en/function.session-set-save-handler.php}
  103. *
  104. * @param string $save_path
  105. * @param string $session_name
  106. * @return bool success
  107. */
  108. public function handler_open($save_path, $session_name) {
  109. // Note: we use the already open database.
  110. return true;
  111. }
  112. /**
  113. * Close session handler.
  114. *
  115. * {@see http://php.net/manual/en/function.session-set-save-handler.php}
  116. *
  117. * @return bool success
  118. */
  119. public function handler_close() {
  120. if ($this->recordid) {
  121. try {
  122. $this->database->release_session_lock($this->recordid);
  123. } catch (\Exception $ex) {
  124. // Ignore any problems.
  125. }
  126. }
  127. $this->recordid = null;
  128. $this->lasthash = null;
  129. return true;
  130. }
  131. /**
  132. * Read session handler.
  133. *
  134. * {@see http://php.net/manual/en/function.session-set-save-handler.php}
  135. *
  136. * @param string $sid
  137. * @return string
  138. */
  139. public function handler_read($sid) {
  140. try {
  141. if (!$record = $this->database->get_record('sessions', array('sid'=>$sid), 'id')) {
  142. // Let's cheat and skip locking if this is the first access,
  143. // do not create the record here, let the manager do it after session init.
  144. $this->failed = false;
  145. $this->recordid = null;
  146. $this->lasthash = sha1('');
  147. return '';
  148. }
  149. if ($this->recordid and $this->recordid != $record->id) {
  150. error_log('Second session read with different record id detected, cannot read session');
  151. $this->failed = true;
  152. $this->recordid = null;
  153. return '';
  154. }
  155. if (!$this->recordid) {
  156. // Lock session if exists and not already locked.
  157. $this->database->get_session_lock($record->id, $this->acquiretimeout);
  158. $this->recordid = $record->id;
  159. }
  160. } catch (\dml_sessionwait_exception $ex) {
  161. // This is a fatal error, better inform users.
  162. // It should not happen very often - all pages that need long time to execute
  163. // should close session immediately after access control checks.
  164. error_log('Cannot obtain session lock for sid: '.$sid);
  165. $this->failed = true;
  166. throw $ex;
  167. } catch (\Exception $ex) {
  168. // Do not rethrow exceptions here, this should not happen.
  169. error_log('Unknown exception when starting database session : '.$sid.' - '.$ex->getMessage());
  170. $this->failed = true;
  171. $this->recordid = null;
  172. return '';
  173. }
  174. // Finally read the full session data because we know we have the lock now.
  175. if (!$record = $this->database->get_record('sessions', array('id'=>$record->id), 'id, sessdata')) {
  176. // Ignore - something else just deleted the session record.
  177. $this->failed = true;
  178. $this->recordid = null;
  179. return '';
  180. }
  181. $this->failed = false;
  182. if (is_null($record->sessdata)) {
  183. $data = '';
  184. $this->lasthash = sha1('');
  185. } else {
  186. $data = base64_decode($record->sessdata);
  187. $this->lasthash = sha1($record->sessdata);
  188. }
  189. return $data;
  190. }
  191. /**
  192. * Write session handler.
  193. *
  194. * {@see http://php.net/manual/en/function.session-set-save-handler.php}
  195. *
  196. * NOTE: Do not write to output or throw any exceptions!
  197. * Hopefully the next page is going to display nice error or it recovers...
  198. *
  199. * @param string $sid
  200. * @param string $session_data
  201. * @return bool success
  202. */
  203. public function handler_write($sid, $session_data) {
  204. if ($this->failed) {
  205. // Do not write anything back - we failed to start the session properly.
  206. return false;
  207. }
  208. $sessdata = base64_encode($session_data); // There might be some binary mess :-(
  209. $hash = sha1($sessdata);
  210. if ($hash === $this->lasthash) {
  211. return true;
  212. }
  213. try {
  214. if ($this->recordid) {
  215. $this->database->set_field('sessions', 'sessdata', $sessdata, array('id'=>$this->recordid));
  216. } else {
  217. // This happens in the first request when session record was just created in manager.
  218. $this->database->set_field('sessions', 'sessdata', $sessdata, array('sid'=>$sid));
  219. }
  220. } catch (\Exception $ex) {
  221. // Do not rethrow exceptions here, this should not happen.
  222. error_log('Unknown exception when writing database session data : '.$sid.' - '.$ex->getMessage());
  223. }
  224. return true;
  225. }
  226. /**
  227. * Destroy session handler.
  228. *
  229. * {@see http://php.net/manual/en/function.session-set-save-handler.php}
  230. *
  231. * @param string $sid
  232. * @return bool success
  233. */
  234. public function handler_destroy($sid) {
  235. if (!$session = $this->database->get_record('sessions', array('sid'=>$sid), 'id, sid')) {
  236. if ($sid == session_id()) {
  237. $this->recordid = null;
  238. $this->lasthash = null;
  239. }
  240. return true;
  241. }
  242. if ($this->recordid and $session->id == $this->recordid) {
  243. try {
  244. $this->database->release_session_lock($this->recordid);
  245. } catch (\Exception $ex) {
  246. // Ignore problems.
  247. }
  248. $this->recordid = null;
  249. $this->lasthash = null;
  250. }
  251. $this->database->delete_records('sessions', array('id'=>$session->id));
  252. return true;
  253. }
  254. /**
  255. * GC session handler.
  256. *
  257. * {@see http://php.net/manual/en/function.session-set-save-handler.php}
  258. *
  259. * @param int $ignored_maxlifetime moodle uses special timeout rules
  260. * @return bool success
  261. */
  262. public function handler_gc($ignored_maxlifetime) {
  263. // This should do something only if cron is not running properly...
  264. if (!$stalelifetime = ini_get('session.gc_maxlifetime')) {
  265. return true;
  266. }
  267. $params = array('purgebefore' => (time() - $stalelifetime));
  268. $this->database->delete_records_select('sessions', 'userid = 0 AND timemodified < :purgebefore', $params);
  269. return true;
  270. }
  271. }