mirror of
https://github.com/moodle/moodle.git
synced 2025-01-18 05:58:34 +01:00
e922fe23b6
Refactoring and improvements of the accesslib.php library including prevention of access for not-logged-in users when forcelogin enabled, improved context caching, OOP refactoring of contexts, fixed context loading, deduplication of role definitions in user sessions, installation improvements, decoupling of enrolment checking from capability loading, added detection of deleted and non-existent users in has_capability(), new function accesslib test, auth and enrol upgrade notes. More details are available in tracker subtasks.
1006 lines
32 KiB
PHP
1006 lines
32 KiB
PHP
<?php
|
|
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* @package core
|
|
* @subpackage session
|
|
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
|
|
* @copyright 2008, 2009 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
/**
|
|
* Factory method returning moodle_session object.
|
|
* @return moodle_session
|
|
*/
|
|
function session_get_instance() {
|
|
global $CFG, $DB;
|
|
|
|
static $session = null;
|
|
|
|
if (is_null($session)) {
|
|
if (empty($CFG->sessiontimeout)) {
|
|
$CFG->sessiontimeout = 7200;
|
|
}
|
|
|
|
if (defined('SESSION_CUSTOM_CLASS')) {
|
|
// this is a hook for webservices, key based login, etc.
|
|
if (defined('SESSION_CUSTOM_FILE')) {
|
|
require_once($CFG->dirroot.SESSION_CUSTOM_FILE);
|
|
}
|
|
$session_class = SESSION_CUSTOM_CLASS;
|
|
$session = new $session_class();
|
|
|
|
} else if ((!isset($CFG->dbsessions) or $CFG->dbsessions) and $DB->session_lock_supported()) {
|
|
// default recommended session type
|
|
$session = new database_session();
|
|
|
|
} else {
|
|
// legacy limited file based storage - some features and auth plugins will not work, sorry
|
|
$session = new legacy_file_session();
|
|
}
|
|
}
|
|
|
|
return $session;
|
|
}
|
|
|
|
/**
|
|
* Moodle session abstraction
|
|
*
|
|
* @package core
|
|
* @subpackage session
|
|
* @copyright 2008 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
interface moodle_session {
|
|
/**
|
|
* Terminate current session
|
|
* @return void
|
|
*/
|
|
public function terminate_current();
|
|
|
|
/**
|
|
* No more changes in session expected.
|
|
* Unblocks the sessions, other scripts may start executing in parallel.
|
|
* @return void
|
|
*/
|
|
public function write_close();
|
|
|
|
/**
|
|
* Check for existing session with id $sid
|
|
* @param unknown_type $sid
|
|
* @return boolean true if session found.
|
|
*/
|
|
public function session_exists($sid);
|
|
}
|
|
|
|
/**
|
|
* Class handling all session and cookies related stuff.
|
|
*
|
|
* @package core
|
|
* @subpackage session
|
|
* @copyright 2009 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
abstract class session_stub implements moodle_session {
|
|
protected $justloggedout;
|
|
|
|
public function __construct() {
|
|
global $CFG;
|
|
|
|
if (NO_MOODLE_COOKIES) {
|
|
// session not used at all
|
|
$_SESSION = array();
|
|
$_SESSION['SESSION'] = new stdClass();
|
|
$_SESSION['USER'] = new stdClass();
|
|
|
|
} else {
|
|
$this->prepare_cookies();
|
|
$this->init_session_storage();
|
|
|
|
$newsession = empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
|
|
|
|
ini_set('session.use_trans_sid', '0');
|
|
|
|
session_name('MoodleSession'.$CFG->sessioncookie);
|
|
session_set_cookie_params(0, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
|
|
session_start();
|
|
if (!isset($_SESSION['SESSION'])) {
|
|
$_SESSION['SESSION'] = new stdClass();
|
|
if (!$newsession and !$this->justloggedout) {
|
|
$_SESSION['SESSION']->has_timed_out = true;
|
|
}
|
|
}
|
|
if (!isset($_SESSION['USER'])) {
|
|
$_SESSION['USER'] = new stdClass();
|
|
}
|
|
}
|
|
|
|
$this->check_user_initialised();
|
|
|
|
$this->check_security();
|
|
}
|
|
|
|
/**
|
|
* Terminates active moodle session
|
|
*/
|
|
public function terminate_current() {
|
|
global $CFG, $SESSION, $USER, $DB;
|
|
|
|
try {
|
|
$DB->delete_records('external_tokens', array('sid'=>session_id(), 'tokentype'=>EXTERNAL_TOKEN_EMBEDDED));
|
|
} catch (Exception $ignored) {
|
|
// probably install/upgrade - ignore this problem
|
|
}
|
|
|
|
if (NO_MOODLE_COOKIES) {
|
|
return;
|
|
}
|
|
|
|
// Initialize variable to pass-by-reference to headers_sent(&$file, &$line)
|
|
$_SESSION = array();
|
|
$_SESSION['SESSION'] = new stdClass();
|
|
$_SESSION['USER'] = new stdClass();
|
|
$_SESSION['USER']->id = 0;
|
|
if (isset($CFG->mnet_localhost_id)) {
|
|
$_SESSION['USER']->mnethostid = $CFG->mnet_localhost_id;
|
|
}
|
|
$SESSION = $_SESSION['SESSION']; // this may not work properly
|
|
$USER = $_SESSION['USER']; // this may not work properly
|
|
|
|
$file = null;
|
|
$line = null;
|
|
if (headers_sent($file, $line)) {
|
|
error_log('Can not terminate session properly - headers were already sent in file: '.$file.' on line '.$line);
|
|
}
|
|
|
|
// now let's try to get a new session id and delete the old one
|
|
$this->justloggedout = true;
|
|
session_regenerate_id(true);
|
|
$this->justloggedout = false;
|
|
|
|
// write the new session
|
|
session_write_close();
|
|
}
|
|
|
|
/**
|
|
* No more changes in session expected.
|
|
* Unblocks the sessions, other scripts may start executing in parallel.
|
|
* @return void
|
|
*/
|
|
public function write_close() {
|
|
if (NO_MOODLE_COOKIES) {
|
|
return;
|
|
}
|
|
|
|
session_write_close();
|
|
}
|
|
|
|
/**
|
|
* Initialise $USER object, handles google access
|
|
* and sets up not logged in user properly.
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function check_user_initialised() {
|
|
global $CFG;
|
|
|
|
if (isset($_SESSION['USER']->id)) {
|
|
// already set up $USER
|
|
return;
|
|
}
|
|
|
|
$user = null;
|
|
|
|
if (!empty($CFG->opentogoogle) and !NO_MOODLE_COOKIES) {
|
|
if (is_web_crawler()) {
|
|
$user = guest_user();
|
|
}
|
|
if (!empty($CFG->guestloginbutton) and !$user and !empty($_SERVER['HTTP_REFERER'])) {
|
|
// automaticaly log in users coming from search engine results
|
|
if (strpos($_SERVER['HTTP_REFERER'], 'google') !== false ) {
|
|
$user = guest_user();
|
|
} else if (strpos($_SERVER['HTTP_REFERER'], 'altavista') !== false ) {
|
|
$user = guest_user();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$user) {
|
|
$user = new stdClass();
|
|
$user->id = 0; // to enable proper function of $CFG->notloggedinroleid hack
|
|
if (isset($CFG->mnet_localhost_id)) {
|
|
$user->mnethostid = $CFG->mnet_localhost_id;
|
|
} else {
|
|
$user->mnethostid = 1;
|
|
}
|
|
}
|
|
session_set_user($user);
|
|
}
|
|
|
|
/**
|
|
* Does various session security checks
|
|
* @global void
|
|
*/
|
|
protected function check_security() {
|
|
global $CFG;
|
|
|
|
if (NO_MOODLE_COOKIES) {
|
|
return;
|
|
}
|
|
|
|
if (!empty($_SESSION['USER']->id) and !empty($CFG->tracksessionip)) {
|
|
/// Make sure current IP matches the one for this session
|
|
$remoteaddr = getremoteaddr();
|
|
|
|
if (empty($_SESSION['USER']->sessionip)) {
|
|
$_SESSION['USER']->sessionip = $remoteaddr;
|
|
}
|
|
|
|
if ($_SESSION['USER']->sessionip != $remoteaddr) {
|
|
// this is a security feature - terminate the session in case of any doubt
|
|
$this->terminate_current();
|
|
print_error('sessionipnomatch2', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepare cookies and various system settings
|
|
*/
|
|
protected function prepare_cookies() {
|
|
global $CFG;
|
|
|
|
if (!isset($CFG->cookiesecure) or (strpos($CFG->wwwroot, 'https://') !== 0 and empty($CFG->sslproxy))) {
|
|
$CFG->cookiesecure = 0;
|
|
}
|
|
|
|
if (!isset($CFG->cookiehttponly)) {
|
|
$CFG->cookiehttponly = 0;
|
|
}
|
|
|
|
/// Set sessioncookie and sessioncookiepath variable if it isn't already
|
|
if (!isset($CFG->sessioncookie)) {
|
|
$CFG->sessioncookie = '';
|
|
}
|
|
|
|
// make sure cookie domain makes sense for this wwwroot
|
|
if (!isset($CFG->sessioncookiedomain)) {
|
|
$CFG->sessioncookiedomain = '';
|
|
} else if ($CFG->sessioncookiedomain !== '') {
|
|
$host = parse_url($CFG->wwwroot, PHP_URL_HOST);
|
|
if ($CFG->sessioncookiedomain !== $host) {
|
|
if (substr($CFG->sessioncookiedomain, 0, 1) === '.') {
|
|
if (!preg_match('|^.*'.preg_quote($CFG->sessioncookiedomain, '|').'$|', $host)) {
|
|
// invalid domain - it must be end part of host
|
|
$CFG->sessioncookiedomain = '';
|
|
}
|
|
} else {
|
|
if (!preg_match('|^.*\.'.preg_quote($CFG->sessioncookiedomain, '|').'$|', $host)) {
|
|
// invalid domain - it must be end part of host
|
|
$CFG->sessioncookiedomain = '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// make sure the cookiepath is valid for this wwwroot or autodetect if not specified
|
|
if (!isset($CFG->sessioncookiepath)) {
|
|
$CFG->sessioncookiepath = '';
|
|
}
|
|
if ($CFG->sessioncookiepath !== '/') {
|
|
$path = parse_url($CFG->wwwroot, PHP_URL_PATH).'/';
|
|
if ($CFG->sessioncookiepath === '') {
|
|
$CFG->sessioncookiepath = $path;
|
|
} else {
|
|
if (strpos($path, $CFG->sessioncookiepath) !== 0 or substr($CFG->sessioncookiepath, -1) !== '/') {
|
|
$CFG->sessioncookiepath = $path;
|
|
}
|
|
}
|
|
}
|
|
|
|
//discard session ID from POST, GET and globals to tighten security,
|
|
//this is session fixation prevention
|
|
unset(${'MoodleSession'.$CFG->sessioncookie});
|
|
unset($_GET['MoodleSession'.$CFG->sessioncookie]);
|
|
unset($_POST['MoodleSession'.$CFG->sessioncookie]);
|
|
unset($_REQUEST['MoodleSession'.$CFG->sessioncookie]);
|
|
|
|
//compatibility hack for Moodle Cron, cookies not deleted, but set to "deleted" - should not be needed with NO_MOODLE_COOKIES in cron.php now
|
|
if (!empty($_COOKIE['MoodleSession'.$CFG->sessioncookie]) && $_COOKIE['MoodleSession'.$CFG->sessioncookie] == "deleted") {
|
|
unset($_COOKIE['MoodleSession'.$CFG->sessioncookie]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inits session storage.
|
|
*/
|
|
protected abstract function init_session_storage();
|
|
}
|
|
|
|
/**
|
|
* Legacy moodle sessions stored in files, not recommended any more.
|
|
*
|
|
* @package core
|
|
* @subpackage session
|
|
* @copyright 2009 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class legacy_file_session extends session_stub {
|
|
protected function init_session_storage() {
|
|
global $CFG;
|
|
|
|
ini_set('session.save_handler', 'files');
|
|
|
|
// Some distros disable GC by setting probability to 0
|
|
// overriding the PHP default of 1
|
|
// (gc_probability is divided by gc_divisor, which defaults to 1000)
|
|
if (ini_get('session.gc_probability') == 0) {
|
|
ini_set('session.gc_probability', 1);
|
|
}
|
|
|
|
ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
|
|
|
|
// make sure sessions dir exists and is writable, throws exception if not
|
|
make_upload_directory('sessions');
|
|
|
|
// Need to disable debugging since disk_free_space()
|
|
// will fail on very large partitions (see MDL-19222)
|
|
$freespace = @disk_free_space($CFG->dataroot.'/sessions');
|
|
if (!($freespace > 2048) and $freespace !== false) {
|
|
print_error('sessiondiskfull', 'error');
|
|
}
|
|
ini_set('session.save_path', $CFG->dataroot .'/sessions');
|
|
}
|
|
/**
|
|
* Check for existing session with id $sid
|
|
* @param unknown_type $sid
|
|
* @return boolean true if session found.
|
|
*/
|
|
public function session_exists($sid){
|
|
global $CFG;
|
|
|
|
$sid = clean_param($sid, PARAM_FILE);
|
|
$sessionfile = "$CFG->dataroot/sessions/sess_$sid";
|
|
return file_exists($sessionfile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recommended moodle session storage.
|
|
*
|
|
* @package core
|
|
* @subpackage session
|
|
* @copyright 2009 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class database_session extends session_stub {
|
|
protected $record = null;
|
|
protected $database = null;
|
|
|
|
public function __construct() {
|
|
global $DB;
|
|
$this->database = $DB;
|
|
parent::__construct();
|
|
|
|
if (!empty($this->record->state)) {
|
|
// something is very wrong
|
|
session_kill($this->record->sid);
|
|
|
|
if ($this->record->state == 9) {
|
|
print_error('dbsessionmysqlpacketsize', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
public function session_exists($sid){
|
|
global $CFG;
|
|
try {
|
|
$sql = "SELECT * FROM {sessions} WHERE timemodified < ? AND sid=? AND state=?";
|
|
$params = array(time() + $CFG->sessiontimeout, $sid, 0);
|
|
|
|
return $this->database->record_exists_sql($sql, $params);
|
|
} catch (dml_exception $ex) {
|
|
error_log('Error checking existance of database session');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
protected function init_session_storage() {
|
|
global $CFG;
|
|
|
|
// gc only from CRON - individual user timeouts now checked during each access
|
|
ini_set('session.gc_probability', 0);
|
|
|
|
ini_set('session.gc_maxlifetime', $CFG->sessiontimeout);
|
|
|
|
$result = session_set_save_handler(array($this, 'handler_open'),
|
|
array($this, 'handler_close'),
|
|
array($this, 'handler_read'),
|
|
array($this, 'handler_write'),
|
|
array($this, 'handler_destroy'),
|
|
array($this, 'handler_gc'));
|
|
if (!$result) {
|
|
print_error('dbsessionhandlerproblem', 'error');
|
|
}
|
|
}
|
|
|
|
public function handler_open($save_path, $session_name) {
|
|
return true;
|
|
}
|
|
|
|
public function handler_close() {
|
|
if (isset($this->record->id)) {
|
|
$this->database->release_session_lock($this->record->id);
|
|
}
|
|
$this->record = null;
|
|
return true;
|
|
}
|
|
|
|
public function handler_read($sid) {
|
|
global $CFG;
|
|
|
|
if ($this->record and $this->record->sid != $sid) {
|
|
error_log('Weird error reading database session - mismatched sid');
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
if ($record = $this->database->get_record('sessions', array('sid'=>$sid))) {
|
|
$this->database->get_session_lock($record->id);
|
|
|
|
} else {
|
|
$record = new stdClass();
|
|
$record->state = 0;
|
|
$record->sid = $sid;
|
|
$record->sessdata = null;
|
|
$record->userid = 0;
|
|
$record->timecreated = $record->timemodified = time();
|
|
$record->firstip = $record->lastip = getremoteaddr();
|
|
$record->id = $this->database->insert_record_raw('sessions', $record);
|
|
|
|
$this->database->get_session_lock($record->id);
|
|
}
|
|
} catch (dml_exception $ex) {
|
|
error_log('Can not read or insert database sessions');
|
|
return '';
|
|
}
|
|
|
|
// verify timeout
|
|
if ($record->timemodified + $CFG->sessiontimeout < time()) {
|
|
$ignoretimeout = false;
|
|
if (!empty($record->userid)) { // skips not logged in
|
|
if ($user = $this->database->get_record('user', array('id'=>$record->userid))) {
|
|
if (!isguestuser($user)) {
|
|
$authsequence = get_enabled_auth_plugins(); // auths, in sequence
|
|
foreach($authsequence as $authname) {
|
|
$authplugin = get_auth_plugin($authname);
|
|
if ($authplugin->ignore_timeout_hook($user, $record->sid, $record->timecreated, $record->timemodified)) {
|
|
$ignoretimeout = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($ignoretimeout) {
|
|
//refresh session
|
|
$record->timemodified = time();
|
|
try {
|
|
$this->database->update_record('sessions', $record);
|
|
} catch (dml_exception $ex) {
|
|
error_log('Can not refresh database session');
|
|
return '';
|
|
}
|
|
} else {
|
|
//time out session
|
|
$record->state = 0;
|
|
$record->sessdata = null;
|
|
$record->userid = 0;
|
|
$record->timecreated = $record->timemodified = time();
|
|
$record->firstip = $record->lastip = getremoteaddr();
|
|
try {
|
|
$this->database->update_record('sessions', $record);
|
|
} catch (dml_exception $ex) {
|
|
error_log('Can not time out database session');
|
|
return '';
|
|
}
|
|
}
|
|
}
|
|
|
|
$data = is_null($record->sessdata) ? '' : base64_decode($record->sessdata);
|
|
|
|
unset($record->sessdata); // conserve memory
|
|
$this->record = $record;
|
|
|
|
return $data;
|
|
}
|
|
|
|
public function handler_write($sid, $session_data) {
|
|
global $USER;
|
|
|
|
// TODO: MDL-20625 we need to rollback all active transactions and log error if any open needed
|
|
|
|
$userid = 0;
|
|
if (!empty($USER->realuser)) {
|
|
$userid = $USER->realuser;
|
|
} else if (!empty($USER->id)) {
|
|
$userid = $USER->id;
|
|
}
|
|
|
|
if (isset($this->record->id)) {
|
|
$record = new stdClass();
|
|
$record->state = 0;
|
|
$record->sid = $sid; // might be regenerating sid
|
|
$this->record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
|
|
$this->record->userid = $userid;
|
|
$this->record->timemodified = time();
|
|
$this->record->lastip = getremoteaddr();
|
|
|
|
// TODO: verify session changed before doing update,
|
|
// also make sure the timemodified field is changed only every 10s if nothing else changes MDL-20462
|
|
|
|
try {
|
|
$this->database->update_record_raw('sessions', $this->record);
|
|
} catch (dml_exception $ex) {
|
|
if ($this->database->get_dbfamily() === 'mysql') {
|
|
try {
|
|
$this->database->set_field('sessions', 'state', 9, array('id'=>$this->record->id));
|
|
} catch (Exception $ignored) {
|
|
|
|
}
|
|
error_log('Can not write database session - please verify max_allowed_packet is at least 4M!');
|
|
} else {
|
|
error_log('Can not write database session');
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// session already destroyed
|
|
$record = new stdClass();
|
|
$record->state = 0;
|
|
$record->sid = $sid;
|
|
$record->sessdata = base64_encode($session_data); // there might be some binary mess :-(
|
|
$record->userid = $userid;
|
|
$record->timecreated = $record->timemodified = time();
|
|
$record->firstip = $record->lastip = getremoteaddr();
|
|
$record->id = $this->database->insert_record_raw('sessions', $record);
|
|
$this->record = $record;
|
|
|
|
try {
|
|
$this->database->get_session_lock($this->record->id);
|
|
} catch (dml_exception $ex) {
|
|
error_log('Can not write new database session');
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function handler_destroy($sid) {
|
|
session_kill($sid);
|
|
|
|
if (isset($this->record->id) and $this->record->sid === $sid) {
|
|
$this->database->release_session_lock($this->record->id);
|
|
$this->record = null;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function handler_gc($ignored_maxlifetime) {
|
|
session_gc();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* returns true if legacy session used.
|
|
* @return bool true if legacy(==file) based session used
|
|
*/
|
|
function session_is_legacy() {
|
|
global $CFG, $DB;
|
|
return ((isset($CFG->dbsessions) and !$CFG->dbsessions) or !$DB->session_lock_supported());
|
|
}
|
|
|
|
/**
|
|
* Terminates all sessions, auth hooks are not executed.
|
|
* Useful in upgrade scripts.
|
|
*/
|
|
function session_kill_all() {
|
|
global $CFG, $DB;
|
|
|
|
// always check db table - custom session classes use sessions table
|
|
try {
|
|
$DB->delete_records('sessions');
|
|
} catch (dml_exception $ignored) {
|
|
// do not show any warnings - might be during upgrade/installation
|
|
}
|
|
|
|
if (session_is_legacy()) {
|
|
$sessiondir = "$CFG->dataroot/sessions";
|
|
if (is_dir($sessiondir)) {
|
|
foreach (glob("$sessiondir/sess_*") as $filename) {
|
|
@unlink($filename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark session as accessed, prevents timeouts.
|
|
* @param string $sid
|
|
*/
|
|
function session_touch($sid) {
|
|
global $CFG, $DB;
|
|
|
|
// always check db table - custom session classes use sessions table
|
|
try {
|
|
$sql = "UPDATE {sessions} SET timemodified=? WHERE sid=?";
|
|
$params = array(time(), $sid);
|
|
$DB->execute($sql, $params);
|
|
} catch (dml_exception $ignored) {
|
|
// do not show any warnings - might be during upgrade/installation
|
|
}
|
|
|
|
if (session_is_legacy()) {
|
|
$sid = clean_param($sid, PARAM_FILE);
|
|
$sessionfile = clean_param("$CFG->dataroot/sessions/sess_$sid", PARAM_FILE);
|
|
if (file_exists($sessionfile)) {
|
|
// if the file is locked it means that it will be updated anyway
|
|
@touch($sessionfile);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terminates one sessions, auth hooks are not executed.
|
|
*
|
|
* @param string $sid session id
|
|
*/
|
|
function session_kill($sid) {
|
|
global $CFG, $DB;
|
|
|
|
// always check db table - custom session classes use sessions table
|
|
try {
|
|
$DB->delete_records('sessions', array('sid'=>$sid));
|
|
} catch (dml_exception $ignored) {
|
|
// do not show any warnings - might be during upgrade/installation
|
|
}
|
|
|
|
if (session_is_legacy()) {
|
|
$sid = clean_param($sid, PARAM_FILE);
|
|
$sessionfile = "$CFG->dataroot/sessions/sess_$sid";
|
|
if (file_exists($sessionfile)) {
|
|
@unlink($sessionfile);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Terminates all sessions of one user, auth hooks are not executed.
|
|
* NOTE: This can not work for file based sessions!
|
|
*
|
|
* @param int $userid user id
|
|
*/
|
|
function session_kill_user($userid) {
|
|
global $CFG, $DB;
|
|
|
|
// always check db table - custom session classes use sessions table
|
|
try {
|
|
$DB->delete_records('sessions', array('userid'=>$userid));
|
|
} catch (dml_exception $ignored) {
|
|
// do not show any warnings - might be during upgrade/installation
|
|
}
|
|
|
|
if (session_is_legacy()) {
|
|
// log error?
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Session garbage collection
|
|
* - verify timeout for all users
|
|
* - kill sessions of all deleted users
|
|
* - kill sessions of users with disabled plugins or 'nologin' plugin
|
|
*
|
|
* NOTE: this can not work when legacy file sessions used!
|
|
*/
|
|
function session_gc() {
|
|
global $CFG, $DB;
|
|
|
|
$maxlifetime = $CFG->sessiontimeout;
|
|
|
|
try {
|
|
/// kill all sessions of deleted users
|
|
$DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE deleted <> 0)");
|
|
|
|
/// kill sessions of users with disabled plugins
|
|
$auth_sequence = get_enabled_auth_plugins(true);
|
|
$auth_sequence = array_flip($auth_sequence);
|
|
unset($auth_sequence['nologin']); // no login allowed
|
|
$auth_sequence = array_flip($auth_sequence);
|
|
$notplugins = null;
|
|
list($notplugins, $params) = $DB->get_in_or_equal($auth_sequence, SQL_PARAMS_QM, '', false);
|
|
$DB->delete_records_select('sessions', "userid IN (SELECT id FROM {user} WHERE auth $notplugins)", $params);
|
|
|
|
/// now get a list of time-out candidates
|
|
$sql = "SELECT u.*, s.sid, s.timecreated AS s_timecreated, s.timemodified AS s_timemodified
|
|
FROM {user} u
|
|
JOIN {sessions} s ON s.userid = u.id
|
|
WHERE s.timemodified + ? < ? AND u.id <> ?";
|
|
$params = array($maxlifetime, time(), $CFG->siteguest);
|
|
|
|
$authplugins = array();
|
|
foreach($auth_sequence as $authname) {
|
|
$authplugins[$authname] = get_auth_plugin($authname);
|
|
}
|
|
$rs = $DB->get_recordset_sql($sql, $params);
|
|
foreach ($rs as $user) {
|
|
foreach ($authplugins as $authplugin) {
|
|
if ($authplugin->ignore_timeout_hook($user, $user->sid, $user->s_timecreated, $user->s_timemodified)) {
|
|
continue;
|
|
}
|
|
}
|
|
$DB->delete_records('sessions', array('sid'=>$user->sid));
|
|
}
|
|
$rs->close();
|
|
|
|
$purgebefore = time() - $maxlifetime;
|
|
// delete expired sessions for guest user account
|
|
$DB->delete_records_select('sessions', 'userid = ? AND timemodified < ?', array($CFG->siteguest, $purgebefore));
|
|
// delete expired sessions for userid = 0 (not logged in)
|
|
$DB->delete_records_select('sessions', 'userid = 0 AND timemodified < ?', array($purgebefore));
|
|
} catch (dml_exception $ex) {
|
|
error_log('Error gc-ing sessions');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes sure that $USER->sesskey exists, if $USER itself exists. It sets a new sesskey
|
|
* if one does not already exist, but does not overwrite existing sesskeys. Returns the
|
|
* sesskey string if $USER exists, or boolean false if not.
|
|
*
|
|
* @uses $USER
|
|
* @return string
|
|
*/
|
|
function sesskey() {
|
|
// note: do not use $USER because it may not be initialised yet
|
|
if (empty($_SESSION['USER']->sesskey)) {
|
|
$_SESSION['USER']->sesskey = random_string(10);
|
|
}
|
|
|
|
return $_SESSION['USER']->sesskey;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check the sesskey and return true of false for whether it is valid.
|
|
* (You might like to imagine this function is called sesskey_is_valid().)
|
|
*
|
|
* Every script that lets the user perform a significant action (that is,
|
|
* changes data in the database) should check the sesskey before doing the action.
|
|
* Depending on your code flow, you may want to use the {@link require_sesskey()}
|
|
* helper function.
|
|
*
|
|
* @param string $sesskey The sesskey value to check (optional). Normally leave this blank
|
|
* and this function will do required_param('sesskey', ...).
|
|
* @return bool whether the sesskey sent in the request matches the one stored in the session.
|
|
*/
|
|
function confirm_sesskey($sesskey=NULL) {
|
|
global $USER;
|
|
|
|
if (!empty($USER->ignoresesskey)) {
|
|
return true;
|
|
}
|
|
|
|
if (empty($sesskey)) {
|
|
$sesskey = required_param('sesskey', PARAM_RAW); // Check script parameters
|
|
}
|
|
|
|
return (sesskey() === $sesskey);
|
|
}
|
|
|
|
/**
|
|
* Check the session key using {@link confirm_sesskey()},
|
|
* and cause a fatal error if it does not match.
|
|
*/
|
|
function require_sesskey() {
|
|
if (!confirm_sesskey()) {
|
|
print_error('invalidsesskey');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a moodle cookie with a weakly encrypted username
|
|
*
|
|
* @param string $username to encrypt and place in a cookie, '' means delete current cookie
|
|
* @return void
|
|
*/
|
|
function set_moodle_cookie($username) {
|
|
global $CFG;
|
|
|
|
if (NO_MOODLE_COOKIES) {
|
|
return;
|
|
}
|
|
|
|
if (empty($CFG->rememberusername)) {
|
|
// erase current and do not store permanent cookies
|
|
$username = '';
|
|
}
|
|
|
|
if ($username === 'guest') {
|
|
// keep previous cookie in case of guest account login
|
|
return;
|
|
}
|
|
|
|
$cookiename = 'MOODLEID_'.$CFG->sessioncookie;
|
|
|
|
// delete old cookie
|
|
setcookie($cookiename, '', time() - HOURSECS, $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
|
|
|
|
if ($username !== '') {
|
|
// set username cookie for 60 days
|
|
setcookie($cookiename, rc4encrypt($username), time()+(DAYSECS*60), $CFG->sessioncookiepath, $CFG->sessioncookiedomain, $CFG->cookiesecure, $CFG->cookiehttponly);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a moodle cookie with a weakly encrypted username
|
|
*
|
|
* @return string username
|
|
*/
|
|
function get_moodle_cookie() {
|
|
global $CFG;
|
|
|
|
if (NO_MOODLE_COOKIES) {
|
|
return '';
|
|
}
|
|
|
|
if (empty($CFG->rememberusername)) {
|
|
return '';
|
|
}
|
|
|
|
$cookiename = 'MOODLEID_'.$CFG->sessioncookie;
|
|
|
|
if (empty($_COOKIE[$cookiename])) {
|
|
return '';
|
|
} else {
|
|
$username = rc4decrypt($_COOKIE[$cookiename]);
|
|
if ($username === 'guest' or $username === 'nobody') {
|
|
// backwards compatibility - we do not set these cookies any more
|
|
return '';
|
|
}
|
|
return $username;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Setup $USER object - called during login, loginas, etc.
|
|
*
|
|
* Call sync_user_enrolments() manually after log-in, or log-in-as.
|
|
*
|
|
* @param stdClass $user full user record object
|
|
* @return void
|
|
*/
|
|
function session_set_user($user) {
|
|
$_SESSION['USER'] = $user;
|
|
unset($_SESSION['USER']->description); // conserve memory
|
|
sesskey(); // init session key
|
|
}
|
|
|
|
/**
|
|
* Is current $USER logged-in-as somebody else?
|
|
* @return bool
|
|
*/
|
|
function session_is_loggedinas() {
|
|
return !empty($_SESSION['USER']->realuser);
|
|
}
|
|
|
|
/**
|
|
* Returns the $USER object ignoring current login-as session
|
|
* @return stdClass user object
|
|
*/
|
|
function session_get_realuser() {
|
|
if (session_is_loggedinas()) {
|
|
return $_SESSION['REALUSER'];
|
|
} else {
|
|
return $_SESSION['USER'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Login as another user - no security checks here.
|
|
* @param int $userid
|
|
* @param stdClass $context
|
|
* @return void
|
|
*/
|
|
function session_loginas($userid, $context) {
|
|
if (session_is_loggedinas()) {
|
|
return;
|
|
}
|
|
|
|
// switch to fresh new $SESSION
|
|
$_SESSION['REALSESSION'] = $_SESSION['SESSION'];
|
|
$_SESSION['SESSION'] = new stdClass();
|
|
|
|
/// Create the new $USER object with all details and reload needed capabilities
|
|
$_SESSION['REALUSER'] = $_SESSION['USER'];
|
|
$user = get_complete_user_data('id', $userid);
|
|
$user->realuser = $_SESSION['REALUSER']->id;
|
|
$user->loginascontext = $context;
|
|
|
|
// let enrol plugins deal with new enrolments if necessary
|
|
enrol_check_plugins($user);
|
|
// set up global $USER
|
|
session_set_user($user);
|
|
}
|
|
|
|
/**
|
|
* Sets up current user and course environment (lang, etc.) in cron.
|
|
* Do not use outside of cron script!
|
|
*
|
|
* @param stdClass $user full user object, null means default cron user (admin)
|
|
* @param $course full course record, null means $SITE
|
|
* @return void
|
|
*/
|
|
function cron_setup_user($user = NULL, $course = NULL) {
|
|
global $CFG, $SITE, $PAGE;
|
|
|
|
static $cronuser = NULL;
|
|
static $cronsession = NULL;
|
|
|
|
if (empty($cronuser)) {
|
|
/// ignore admins timezone, language and locale - use site default instead!
|
|
$cronuser = get_admin();
|
|
$cronuser->timezone = $CFG->timezone;
|
|
$cronuser->lang = '';
|
|
$cronuser->theme = '';
|
|
unset($cronuser->description);
|
|
|
|
$cronsession = new stdClass();
|
|
}
|
|
|
|
if (!$user) {
|
|
// cached default cron user (==modified admin for now)
|
|
session_set_user($cronuser);
|
|
$_SESSION['SESSION'] = $cronsession;
|
|
|
|
} else {
|
|
// emulate real user session - needed for caps in cron
|
|
if ($_SESSION['USER']->id != $user->id) {
|
|
session_set_user($user);
|
|
$_SESSION['SESSION'] = new stdClass();
|
|
}
|
|
}
|
|
|
|
// TODO MDL-19774 relying on global $PAGE in cron is a bad idea.
|
|
// Temporary hack so that cron does not give fatal errors.
|
|
$PAGE = new moodle_page();
|
|
if ($course) {
|
|
$PAGE->set_course($course);
|
|
} else {
|
|
$PAGE->set_course($SITE);
|
|
}
|
|
|
|
// TODO: it should be possible to improve perf by caching some limited number of users here ;-)
|
|
|
|
}
|