MDL-21579 "Implement session token for embedded application" implemented a second...
[moodle.git] / lib / sessionlib.php
blob31c9f2c7965bde8f0cb563191dc2c798156d62f7
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * @package moodlecore
20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 /**
25 * Factory method returning moodle_session object.
26 * @return moodle_session
28 function session_get_instance() {
29 global $CFG, $DB;
31 static $session = null;
33 if (is_null($session)) {
34 if (empty($CFG->sessiontimeout)) {
35 $CFG->sessiontimeout = 7200;
38 if (defined('SESSION_CUSTOM_CLASS')) {
39 // this is a hook for webservices, key based login, etc.
40 if (defined('SESSION_CUSTOM_FILE')) {
41 require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
43 $session_class = SESSION_CUSTOM_CLASS;
44 $session = new $session_class();
46 } else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
47 // default recommended session type
48 $session = new database_session();
50 } else {
51 // legacy limited file based storage - some features and auth plugins will not work, sorry
52 $session = new legacy_file_session();
56 return $session;
59 /**
60 * @package moodlecore
61 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
62 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
64 interface moodle_session {
65 /**
66 * Terminate current session
67 * @return void
69 public function terminate_current();
71 /**
72 * No more changes in session expected.
73 * Unblocks the sesions, other scripts may start executing in parallel.
74 * @return void
76 public function write_close();
78 /**
79 * Check for existing session with id $sid
80 * @param unknown_type $sid
81 * @return boolean true if session found.
83 public function session_exists($sid);
86 /**
87 * Class handling all session and cookies related stuff.
89 * @package moodlecore
90 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
91 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
93 abstract class session_stub implements moodle_session {
94 protected $justloggedout;
96 public function __construct() {
97 global $CFG;
99 if (!defined('NO_MOODLE_COOKIES')) {
100 if (empty($CFG->version) or $CFG->version < 2009011900) {
101 // no session before sessions table gets greated
102 define('NO_MOODLE_COOKIES', true);
103 } else if (CLI_SCRIPT) {
104 // CLI scripts can not have session
105 define('NO_MOODLE_COOKIES', true);
106 } else {
107 define('NO_MOODLE_COOKIES', false);
111 if (NO_MOODLE_COOKIES) {
112 // session not used at all
113 $CFG->usesid = 0;
115 $_SESSION = array();
116 $_SESSION['SESSION'] = new object();
117 $_SESSION['USER'] = new object();
119 } else {
120 $this->prepare_cookies();
121 $this->init_session_storage();
123 $newsession = empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
125 if (!empty($CFG->usesid) && $newsession) {
126 sid_start_ob();
127 } else {
128 $CFG->usesid = 0;
129 ini_set('session.use_trans_sid', '0');
132 session_name('MoodleSession'.$CFG->sessioncookie);
133 session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
134 session_start();
135 if (!isset($_SESSION['SESSION'])) {
136 $_SESSION['SESSION'] = new object();
137 if (!$newsession and !$this->justloggedout) {
138 $_SESSION['SESSION']->has_timed_out = true;
141 if (!isset($_SESSION['USER'])) {
142 $_SESSION['USER'] = new object();
146 $this->check_user_initialised();
148 $this->check_security();
152 * Terminates active moodle session
154 public function terminate_current() {
155 global $CFG, $SESSION, $USER, $DB;
157 $DB->delete_records('external_tokens', array('sid'=>session_id(), 'tokentype'=>EXTERNAL_TOKEN_EMBEDDED));
159 if (NO_MOODLE_COOKIES) {
160 return;
163 // Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
164 $_SESSION = array();
165 $_SESSION['SESSION'] = new object();
166 $_SESSION['USER'] = new object();
167 $_SESSION['USER']->id = 0;
168 if (isset($CFG->mnet_localhost_id)) {
169 $_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
171 $SESSION = $_SESSION['SESSION']; // this may not work properly
172 $USER = $_SESSION['USER']; // this may not work properly
174 $file = null;
175 $line = null;
176 if (headers_sent($file, $line)) {
177 error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
180 // now let's try to get a new session id and delete the old one
181 $this->justloggedout = true;
182 session_regenerate_id(true);
183 $this->justloggedout = false;
185 // write the new session
186 session_write_close();
190 * No more changes in session expected.
191 * Unblocks the sesions, other scripts may start executing in parallel.
192 * @return void
194 public function write_close() {
195 if (NO_MOODLE_COOKIES) {
196 return;
199 session_write_close();
203 * Initialise $USER object, handles google access
204 * and sets up not logged in user properly.
206 * @return void
208 protected function check_user_initialised() {
209 if (isset($_SESSION['USER']->id)) {
210 // already set up $USER
211 return;
214 $user = null;
216 if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
217 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
218 // allow web spiders in as guest users
219 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false ) {
220 $user = guest_user();
221 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false ) { // Google
222 $user = guest_user();
223 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'Yahoo! Slurp') !== false ) { // Yahoo
224 $user = guest_user();
225 } else if (strpos($_SERVER['HTTP_USER_AGENT'], '[ZSEBOT]') !== false ) { // Zoomspider
226 $user = guest_user();
227 } else if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSNBOT') !== false ) { // MSN Search
228 $user = guest_user();
231 if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
232 // automaticaly log in users coming from search engine results
233 if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
234 $user = guest_user();
235 } else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
236 $user = guest_user();
241 if (!$user) {
242 $user = new object();
243 $user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
244 if (isset($CFG->mnet_localhost_id)) {
245 $user->mnethostid = $CFG->mnet_localhost_id;
246 } else {
247 $user->mnethostid = 1;
250 session_set_user($user);
254 * Does various session security checks
255 * @global void
257 protected function check_security() {
258 global $CFG;
260 if (NO_MOODLE_COOKIES) {
261 return;
264 if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
265 /// Make sure current IP matches the one for this session
266 $remoteaddr = getremoteaddr();
268 if (empty($_SESSION['USER']->sessionip)) {
269 $_SESSION['USER']->sessionip = $remoteaddr;
272 if ($_SESSION['USER']->sessionip != $remoteaddr) {
273 // this is a security feature - terminate the session in case of any doubt
274 $this->terminate_current();
275 print_error('sessionipnomatch2', 'error');
281 * Prepare cookies and varions system settings
283 protected function prepare_cookies() {
284 global $CFG;
286 if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
287 $CFG->cookiesecure = 0;
290 if (!isset($CFG->cookiehttponly)) {
291 $CFG->cookiehttponly = 0;
294 /// Set sessioncookie and sessioncookiepath variable if it isn't already
295 if (!isset($CFG->sessioncookie)) {
296 $CFG->sessioncookie = '';
298 if (!isset($CFG->sessioncookiedomain)) {
299 $CFG->sessioncookiedomain = '';
301 if (!isset($CFG->sessioncookiepath)) {
302 $CFG->sessioncookiepath = '/';
305 //discard session ID from POST, GET and globals to tighten security,
306 //this session fixation prevention can not be used in cookieless mode
307 if (empty($CFG->usesid)) {
308 unset(${'MoodleSession'.$CFG->sessioncookie});
309 unset($_GET['MoodleSession'.$CFG->sessioncookie]);
310 unset($_POST['MoodleSession'.$CFG->sessioncookie]);
311 unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
313 //compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
314 if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
315 unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
320 * Inits session storage.
322 protected abstract function init_session_storage();
326 * Legacy moodle sessions stored in files, not recommended any more.
328 * @package moodlecore
329 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
330 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
332 class legacy_file_session extends session_stub {
333 protected function init_session_storage() {
334 global $CFG;
336 ini_set('session.save_handler', 'files');
338 // Some distros disable GC by setting probability to 0
339 // overriding the PHP default of 1
340 // (gc_probability is divided by gc_divisor, which defaults to 1000)
341 if (ini_get('session.gc_probability') == 0) {
342 ini_set('session.gc_probability', 1);
345 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
347 if (!file_exists($CFG->dataroot .'/sessions')) {
348 make_upload_directory('sessions');
350 if (!is_writable($CFG->dataroot .'/sessions/')) {
351 print_error('sessionnotwritable', 'error');
353 // Need to disable debugging since disk_free_space()
354 // will fail on very large partitions (see MDL-19222)
355 $freespace = @disk_free_space($CFG->dataroot.'/sessions');
356 if (!($freespace > 2048) and $freespace !== false) {
357 print_error('sessiondiskfull', 'error');
359 ini_set('session.save_path', $CFG->dataroot .'/sessions');
362 * Check for existing session with id $sid
363 * @param unknown_type $sid
364 * @return boolean true if session found.
366 public function session_exists($sid){
367 $sid = clean_param($sid, PARAM_FILE);
368 $sessionfile = clean_param("$CFG->dataroot/sessions/sess_$sid", PARAM_FILE);
369 return file_exists($sessionfile);
376 * Recommended moodle session storage.
378 * @package moodlecore
379 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
380 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
382 class database_session extends session_stub {
383 protected $record = null;
384 protected $database = null;
386 public function __construct() {
387 global $DB;
388 $this->database = $DB;
389 parent::__construct();
391 if (!empty($this->record->state)) {
392 // something is very wrong
393 session_kill($this->record->sid);
395 if ($this->record->state == 9) {
396 print_error('dbsessionmysqlpacketsize', 'error');
401 public function session_exists($sid){
402 global $CFG;
403 try {
404 $sql = "SELECT * FROM {sessions} WHERE timemodified < ? AND sid=? AND state=?";
405 $params = array(time() + $CFG->sessiontimeout, $sid, 0);
407 return $this->database->record_exists_sql($sql, $params);
408 } catch (dml_exception $ex) {
409 error_log('Error checking existance of database session');
410 return false;
414 protected function init_session_storage() {
415 global $CFG;
417 // gc only from CRON - individual user timeouts now checked during each access
418 ini_set('session.gc_probability', 0);
420 ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
422 $result = session_set_save_handler(array($this, 'handler_open'),
423 array($this, 'handler_close'),
424 array($this, 'handler_read'),
425 array($this, 'handler_write'),
426 array($this, 'handler_destroy'),
427 array($this, 'handler_gc'));
428 if (!$result) {
429 print_error('dbsessionhandlerproblem', 'error');
433 public function handler_open($save_path, $session_name) {
434 return true;
437 public function handler_close() {
438 if (isset($this->record->id)) {
439 $this->database->release_session_lock($this->record->id);
441 $this->record = null;
442 return true;
445 public function handler_read($sid) {
446 global $CFG;
448 if ($this->record and $this->record->sid != $sid) {
449 error_log('Weird error reading database session - mismatched sid');
450 return '';
453 try {
454 if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
455 $this->database->get_session_lock($record->id);
457 } else {
458 $record = new object();
459 $record->state = 0;
460 $record->sid = $sid;
461 $record->sessdata = null;
462 $record->userid = 0;
463 $record->timecreated = $record->timemodified = time();
464 $record->firstip = $record->lastip = getremoteaddr();
465 $record->id = $this->database->insert_record_raw('sessions', $record);
467 $this->database->get_session_lock($record->id);
469 } catch (dml_exception $ex) {
470 error_log('Can not read or insert database sessions');
471 return '';
474 // verify timeout
475 if ($record->timemodified + $CFG->sessiontimeout < time()) {
476 $ignoretimeout = false;
477 if (!empty($record->userid)) { // skips not logged in
478 if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
479 if ($user->username !== 'guest') {
480 $authsequence = get_enabled_auth_plugins(); // auths, in sequence
481 foreach($authsequence as $authname) {
482 $authplugin = get_auth_plugin($authname);
483 if ($authplugin->ignore_timeout_hook($user, $record->sid, $record->timecreated, $record->timemodified)) {
484 $ignoretimeout = true;
485 break;
491 if ($ignoretimeout) {
492 //refresh session
493 $record->timemodified = time();
494 try {
495 $this->database->update_record('sessions', $record);
496 } catch (dml_exception $ex) {
497 error_log('Can not refresh database session');
498 return '';
500 } else {
501 //time out session
502 $record->state = 0;
503 $record->sessdata = null;
504 $record->userid = 0;
505 $record->timecreated = $record->timemodified = time();
506 $record->firstip = $record->lastip = getremoteaddr();
507 try {
508 $this->database->update_record('sessions', $record);
509 } catch (dml_exception $ex) {
510 error_log('Can not time out database session');
511 return '';
516 $data = is_null($record->sessdata) ? '' : base64_decode($record->sessdata);
518 unset($record->sessdata); // conserve memory
519 $this->record = $record;
521 return $data;
524 public function handler_write($sid, $session_data) {
525 global $USER;
527 // TODO: MDL-20625 we need to rollabck all active transactions and log error if any open needed
529 $userid = 0;
530 if (!empty($USER->realuser)) {
531 $userid = $USER->realuser;
532 } else if (!empty($USER->id)) {
533 $userid = $USER->id;
536 if (isset($this->record->id)) {
537 $record->state = 0;
538 $record->sid = $sid; // might be regenerating sid
539 $this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
540 $this->record->userid = $userid;
541 $this->record->timemodified = time();
542 $this->record->lastip = getremoteaddr();
544 // TODO: verify session changed before doing update,
545 // also make sure the timemodified field is changed only overy 10s if nothing else changes MDL-20462
547 try {
548 $this->database->update_record_raw('sessions', $this->record);
549 } catch (dml_exception $ex) {
550 if ($this->database->get_dbfamily() === 'mysql') {
551 try {
552 $this->database->set_field('sessions', 'state', 9, array('id'=>$this->record->id));
553 } catch (Exception $ignored) {
556 error_log('Can not write database session - please verify max_allowed_packet is at least 4M!');
557 } else {
558 error_log('Can not write database session');
562 } else {
563 // session already destroyed
564 $record = new object();
565 $record->state = 0;
566 $record->sid = $sid;
567 $record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
568 $record->userid = $userid;
569 $record->timecreated = $record->timemodified = time();
570 $record->firstip = $record->lastip = getremoteaddr();
571 $record->id = $this->database->insert_record_raw('sessions', $record);
572 $this->record = $record;
574 try {
575 $this->database->get_session_lock($this->record->id);
576 } catch (dml_exception $ex) {
577 error_log('Can not write new database session');
581 return true;
584 public function handler_destroy($sid) {
585 session_kill($sid);
587 if (isset($this->record->id) and $this->record->sid === $sid) {
588 $this->database->release_session_lock($this->record->id);
589 $this->record = null;
592 return true;
595 public function handler_gc($ignored_maxlifetime) {
596 session_gc();
597 return true;
602 * returns true if legacy session used.
603 * @return bool true if legacy(==file) based session used
605 function session_is_legacy() {
606 global $CFG, $DB;
607 return ((isset($CFG->dbsessions) and !$CFG->dbsessions) or !$DB->session_lock_supported());
611 * Terminates all sessions, auth hooks are not executed.
612 * Useful in ugrade scripts.
614 function session_kill_all() {
615 global $CFG, $DB;
617 // always check db table - custom session classes use sessions table
618 try {
619 $DB->delete_records('sessions');
620 } catch (dml_exception $ignored) {
621 // do not show any warnings - might be during upgrade/installation
624 if (session_is_legacy()) {
625 $sessiondir = "$CFG->dataroot/sessions";
626 if (is_dir($sessiondir)) {
627 foreach (glob("$sessiondir/sess_*") as $filename) {
628 @unlink($filename);
635 * Mark session as accessed, prevents timeouts.
636 * @param string $sid
638 function session_touch($sid) {
639 global $CFG, $DB;
641 // always check db table - custom session classes use sessions table
642 try {
643 $sql = "UPDATE {sessions} SET timemodified=? WHERE sid=?";
644 $params = array(time(), $sid);
645 $DB->execute($sql, $params);
646 } catch (dml_exception $ignored) {
647 // do not show any warnings - might be during upgrade/installation
650 if (session_is_legacy()) {
651 $sid = clean_param($sid, PARAM_FILE);
652 $sessionfile = clean_param("$CFG->dataroot/sessions/sess_$sid", PARAM_FILE);
653 if (file_exists($sessionfile)) {
654 // if the file is locked it means that it will be updated anyway
655 @touch($sessionfile);
661 * Terminates one sessions, auth hooks are not executed.
663 * @param string $sid session id
665 function session_kill($sid) {
666 global $CFG, $DB;
668 // always check db table - custom session classes use sessions table
669 try {
670 $DB->delete_records('sessions', array('sid'=>$sid));
671 } catch (dml_exception $ignored) {
672 // do not show any warnings - might be during upgrade/installation
675 if (session_is_legacy()) {
676 $sid = clean_param($sid, PARAM_FILE);
677 $sessionfile = "$CFG->dataroot/sessions/sess_$sid";
678 if (file_exists($sessionfile)) {
679 @unlink($sessionfile);
685 * Terminates all sessions of one user, auth hooks are not executed.
686 * NOTE: This can not work for file based sessions!
688 * @param int $userid user id
690 function session_kill_user($userid) {
691 global $CFG, $DB;
693 // always check db table - custom session classes use sessions table
694 try {
695 $DB->delete_records('sessions', array('userid'=>$userid));
696 } catch (dml_exception $ignored) {
697 // do not show any warnings - might be during upgrade/installation
700 if (session_is_legacy()) {
701 // log error?
706 * Session garbage collection
707 * - verify timeout for all users
708 * - kill sessions of all deleted users
709 * - kill sessions of users with disabled plugins or 'nologin' plugin
711 * NOTE: this can not work when legacy file sessions used!
713 function session_gc() {
714 global $CFG, $DB;
716 $maxlifetime = $CFG->sessiontimeout;
718 try {
719 /// kill all sessions of deleted users
720 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0)");
722 /// kill sessions of users with disabled plugins
723 $auth_sequence = get_enabled_auth_plugins(true);
724 $auth_sequence = array_flip($auth_sequence);
725 unset($auth_sequence['nologin']); // no login allowed
726 $auth_sequence = array_flip($auth_sequence);
727 $notplugins = null;
728 list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
729 $DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params);
731 /// now get a list of time-out candidates
732 $sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
733 FROM {user} u
734 JOIN {sessions} s ON s.userid = u.id
735 WHERE s.timemodified + ? < ? AND u.username <> 'guest'";
736 $params = array($maxlifetime, time());
738 $authplugins = array();
739 foreach($auth_sequence as $authname) {
740 $authplugins[$authname] = get_auth_plugin($authname);
742 $rs = $DB->get_recordset_sql($sql, $params);
743 foreach ($rs as $user) {
744 foreach ($authplugins as $authplugin) {
745 if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
746 continue;
749 $DB->delete_records('sessions', array('sid'=>$user->sid));
751 $rs->close();
752 } catch (dml_exception $ex) {
753 error_log('Error gc-ing sessions');
758 * Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
759 * if one does not already exist, but does not overwrite existing sesskeys. Returns the
760 * sesskey string if $USER exists, or boolean false if not.
762 * @uses $USER
763 * @return string
765 function sesskey() {
766 global $USER;
768 if (empty($USER->sesskey)) {
769 $USER->sesskey = random_string(10);
772 return $USER->sesskey;
777 * Check the sesskey and return true of false for whether it is valid.
778 * (You might like to imagine this function is called sesskey_is_valid().)
780 * Every script that lets the user perform a significant action (that is,
781 * changes data in the database) should check the sesskey before doing the action.
782 * Depending on your code flow, you may want to use the {@link require_sesskey()}
783 * helper function.
785 * @param string $sesskey The sesskey value to check (optional). Normally leave this blank
786 * and this function will do required_param('sesskey', ...).
787 * @return bool whether the sesskey sent in the request matches the one stored in the session.
789 function confirm_sesskey($sesskey=NULL) {
790 global $USER;
792 if (!empty($USER->ignoresesskey)) {
793 return true;
796 if (empty($sesskey)) {
797 $sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
800 return (sesskey() === $sesskey);
804 * Check the session key using {@link confirm_sesskey()},
805 * and cause a fatal error if it does not match.
807 function require_sesskey() {
808 if (!confirm_sesskey()) {
809 print_error('invalidsesskey');
814 * Sets a moodle cookie with a weakly encrypted string
816 * @uses $CFG
817 * @uses DAYSECS
818 * @uses HOURSECS
819 * @param string $thing The string to encrypt and place in a cookie
821 function set_moodle_cookie($thing) {
822 global $CFG;
824 if (NO_MOODLE_COOKIES) {
825 return;
828 if ($thing == 'guest') { // Ignore guest account
829 return;
832 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
834 $days = 60;
835 $seconds = DAYSECS*$days;
837 setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
838 setcookie($cookiename, rc4encrypt($thing), time()+$seconds, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
842 * Gets a moodle cookie with a weakly encrypted string
844 * @uses $CFG
845 * @return string
847 function get_moodle_cookie() {
848 global $CFG;
850 if (NO_MOODLE_COOKIES) {
851 return '';
854 $cookiename = 'MOODLEID_'.$CFG->sessioncookie;
856 if (empty($_COOKIE[$cookiename])) {
857 return '';
858 } else {
859 $thing = rc4decrypt($_COOKIE[$cookiename]);
860 return ($thing == 'guest') ? '': $thing; // Ignore guest account
866 * Setup $USER object - called during login, loginas, etc.
867 * Preloads capabilities and checks enrolment plugins
869 * @param object $user full user record object
870 * @return void
872 function session_set_user($user) {
873 $_SESSION['USER'] = $user;
874 unset($_SESSION['USER']->description); // conserve memory
875 if (!isset($_SESSION['USER']->access)) {
876 // check enrolments and load caps only once
877 check_enrolment_plugins($_SESSION['USER']);
878 load_all_capabilities();
880 sesskey(); // init session key
884 * Is current $USER logged-in-as somebody else?
885 * @return bool
887 function session_is_loggedinas() {
888 return !empty($_SESSION['USER']->realuser);
892 * Returns the $USER object ignoring current login-as session
893 * @return object user object
895 function session_get_realuser() {
896 if (session_is_loggedinas()) {
897 return $_SESSION['REALUSER'];
898 } else {
899 return $_SESSION['USER'];
904 * Login as another user - no security checks here.
905 * @param int $userid
906 * @param object $context
907 * @return void
909 function session_loginas($userid, $context) {
910 if (session_is_loggedinas()) {
911 return;
914 // switch to fresh new $SESSION
915 $_SESSION['REALSESSION'] = $_SESSION['SESSION'];
916 $_SESSION['SESSION'] = new object();
918 /// Create the new $USER object with all details and reload needed capabilitites
919 $_SESSION['REALUSER'] = $_SESSION['USER'];
920 $user = get_complete_user_data('id', $userid);
921 $user->realuser = $_SESSION['REALUSER']->id;
922 $user->loginascontext = $context;
923 session_set_user($user);
927 * Terminate login-as session
928 * @return void
930 function session_unloginas() {
931 if (!session_is_loggedinas()) {
932 return;
935 $_SESSION['SESSION'] = $_SESSION['REALSESSION'];
936 unset($_SESSION['REALSESSION']);
938 $_SESSION['USER'] = $_SESSION['REALUSER'];
939 unset($_SESSION['REALUSER']);
943 * Sets up current user and course enviroment (lang, etc.) in cron.
944 * Do not use outside of cron script!
946 * @param object $user full user object, null means default cron user (admin)
947 * @param $course full course record, null means $SITE
948 * @return void
950 function cron_setup_user($user=null, $course=null) {
951 global $CFG, $SITE, $PAGE;
953 static $cronuser = null;
954 static $cronsession = null;
956 if (empty($cronuser)) {
957 /// ignore admins timezone, language and locale - use site deafult instead!
958 $cronuser = get_admin();
959 $cronuser->timezone = $CFG->timezone;
960 $cronuser->lang = '';
961 $cronuser->theme = '';
962 unset($cronuser->description);
964 $cronsession = array();
967 if (!$user) {
968 // cached default cron user (==modified admin for now)
969 session_set_user($cronuser);
970 $_SESSION['SESSION'] = $cronsession;
972 } else {
973 // emulate real user session - needed for caps in cron
974 if ($_SESSION['USER']->id != $user->id) {
975 session_set_user($user);
976 $_SESSION['SESSION'] = array();
980 // TODO MDL-19774 relying on global $PAGE in cron is a bad idea.
981 // Temporary hack so that cron does not give fatal errors.
982 $PAGE = new moodle_page();
983 if ($course) {
984 $PAGE->set_course($course);
985 } else {
986 $PAGE->set_course($SITE);
989 // TODO: it should be possible to improve perf by caching some limited number of users here ;-)
994 * Enable cookieless sessions by including $CFG->usesid=true;
995 * in config.php.
996 * Based on code from php manual by Richard at postamble.co.uk
997 * Attempts to use cookies if cookies not present then uses session ids attached to all urls and forms to pass session id from page to page.
998 * If site is open to google, google is given guest access as usual and there are no sessions. No session ids will be attached to urls for googlebot.
999 * This doesn't require trans_sid to be turned on but this is recommended for better performance
1000 * you should put :
1001 * session.use_trans_sid = 1
1002 * in your php.ini file and make sure that you don't have a line like this in your php.ini
1003 * session.use_only_cookies = 1
1004 * @author Richard at postamble.co.uk and Jamie Pratt
1005 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
1008 * You won't call this function directly. This function is used to process
1009 * text buffered by php in an output buffer. All output is run through this function
1010 * before it is ouput.
1011 * @param string $buffer is the output sent from php
1012 * @return string the output sent to the browser
1014 function sid_ob_rewrite($buffer){
1015 $replacements = array(
1016 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")([^"]*)(")/i',
1017 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')([^\']*)(\')/i');
1019 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
1020 $buffer = preg_replace('/<form\s[^>]*>/i',
1021 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
1023 return $buffer;
1026 * You won't call this function directly. This function is used to process
1027 * text buffered by php in an output buffer. All output is run through this function
1028 * before it is ouput.
1029 * This function only processes absolute urls, it is used when we decide that
1030 * php is processing other urls itself but needs some help with internal absolute urls still.
1031 * @param string $buffer is the output sent from php
1032 * @return string the output sent to the browser
1034 function sid_ob_rewrite_absolute($buffer){
1035 $replacements = array(
1036 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*")((?:http|https)[^"]*)(")/i',
1037 '/(<\s*(a|link|script|frame|area)\s[^>]*(href|src)\s*=\s*\')((?:http|https)[^\']*)(\')/i');
1039 $buffer = preg_replace_callback($replacements, 'sid_rewrite_link_tag', $buffer);
1040 $buffer = preg_replace('/<form\s[^>]*>/i',
1041 '\0<input type="hidden" name="' . session_name() . '" value="' . session_id() . '"/>', $buffer);
1042 return $buffer;
1046 * A function to process link, a and script tags found
1047 * by preg_replace_callback in {@link sid_ob_rewrite($buffer)}.
1049 function sid_rewrite_link_tag($matches){
1050 $url = $matches[4];
1051 $url = sid_process_url($url);
1052 return $matches[1].$url.$matches[5];
1056 * You can call this function directly. This function is used to process
1057 * urls to add a moodle session id to the url for internal links.
1058 * @param string $url is a url
1059 * @return string the processed url
1061 function sid_process_url($url) {
1062 global $CFG;
1064 if ((preg_match('/^(http|https):/i', $url)) // absolute url
1065 && ((stripos($url, $CFG->wwwroot)!==0) && stripos($url, $CFG->httpswwwroot)!==0)) { // and not local one
1066 return $url; //don't attach sessid to non local urls
1068 if ($url[0]=='#' || (stripos($url, 'javascript:')===0)) {
1069 return $url; //don't attach sessid to anchors
1071 if (strpos($url, session_name())!==FALSE) {
1072 return $url; //don't attach sessid to url that already has one sessid
1074 if (strpos($url, "?")===FALSE) {
1075 $append = "?".strip_tags(session_name() . '=' . session_id());
1076 } else {
1077 $append = "&amp;".strip_tags(session_name() . '=' . session_id());
1079 //put sessid before any anchor
1080 $p = strpos($url, "#");
1081 if ($p!==FALSE){
1082 $anch = substr($url, $p);
1083 $url = substr($url, 0, $p).$append.$anch ;
1084 } else {
1085 $url .= $append ;
1087 return $url;
1091 * Call this function before there has been any output to the browser to
1092 * buffer output and add session ids to all internal links.
1094 function sid_start_ob(){
1095 global $CFG;
1096 //don't attach sess id for bots
1098 if (!empty($_SERVER['HTTP_USER_AGENT'])) {
1099 if (!empty($CFG->opentogoogle)) {
1100 if (strpos($_SERVER['HTTP_USER_AGENT'], 'Googlebot') !== false) {
1101 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1102 $CFG->usesid=false;
1103 return;
1105 if (strpos($_SERVER['HTTP_USER_AGENT'], 'google.com') !== false) {
1106 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1107 $CFG->usesid=false;
1108 return;
1111 if (strpos($_SERVER['HTTP_USER_AGENT'], 'W3C_Validator') !== false) {
1112 @ini_set('session.use_trans_sid', '0'); // try and turn off trans_sid
1113 $CFG->usesid=false;
1114 return;
1118 @ini_set('session.use_trans_sid', '1'); // try and turn on trans_sid
1120 if (ini_get('session.use_trans_sid') != 0) {
1121 // use trans sid as its available
1122 ini_set('url_rewriter.tags', 'a=href,area=href,script=src,link=href,frame=src,form=fakeentry');
1123 ob_start('sid_ob_rewrite_absolute');
1124 } else {
1125 //rewrite all links ourselves
1126 ob_start('sid_ob_rewrite');