Commit e922fe23 authored by Petr Skoda's avatar Petr Skoda
Browse files

MDL-29602 accesslib improvements

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.
parent 6731a04d
......@@ -65,13 +65,15 @@ if ($requestedqtype) {
// Get the question counts, and all the context information, for each
// context. That is, rows of these results can be used as $context objects.
$ctxpreload = context_helper::get_preload_record_columns_sql('con');
$ctxgroupby = implode(',', array_keys(context_helper::get_preload_record_columns('con')));
$counts = $DB->get_records_sql("
SELECT qc.contextid, count(1) as numquestions, sum(hidden) as numhidden, con.id, con.contextlevel, con.instanceid, con.path, con.depth
SELECT qc.contextid, count(1) as numquestions, sum(hidden) as numhidden, $ctxpreload
FROM {question} q
JOIN {question_categories} qc ON q.category = qc.id
JOIN {context} con ON con.id = qc.contextid
$sqlqtypetest
GROUP BY contextid, con.id, con.contextlevel, con.instanceid, con.path, con.depth
GROUP BY qc.contextid, $ctxgroupby
ORDER BY numquestions DESC, numhidden ASC, con.contextlevel ASC, con.id ASC", $params);
// Print the report heading.
......@@ -94,8 +96,10 @@ if ($requestedqtype) {
$totalhidden = 0;
foreach ($counts as $count) {
// Work out a link for editing questions in this context.
$contextname = print_context_name($count);
$url = question_edit_url($count);
context_helper::preload_from_record($count);
$context = context::instance_by_id($count->contextid);
$contextname = $context->get_context_name();
$url = question_edit_url($context);
if ($url) {
$contextname = '<a href="' . $url . '" title="' .
get_string('editquestionshere', 'report_questioninstances') .
......
......@@ -96,7 +96,8 @@ foreach ($contexts as $conid => $con) {
/// Put the contexts into a tree structure.
foreach ($contexts as $conid => $con) {
$parentcontextid = get_parent_contextid($con);
$context = context::instance_by_id($conid);
$parentcontextid = get_parent_contextid($context);
if ($parentcontextid) {
$contexts[$parentcontextid]->children[] = $conid;
}
......@@ -156,13 +157,13 @@ function print_report_tree($contextid, $contexts, $systemcontext, $fullname) {
}
// Pull the current context into an array for convinience.
$context = $contexts[$contextid];
$context = context::instance_by_id($contextid);
// Print the context name.
echo $OUTPUT->heading(print_context_name($contexts[$contextid]), 4, 'contextname');
echo $OUTPUT->heading($context->get_context_name(), 4, 'contextname');
// If there are any role assignments here, print them.
foreach ($context->roleassignments as $ra) {
foreach ($contexts[$contextid]->roleassignments as $ra) {
$value = $ra->contextid . ',' . $ra->roleid;
$inputid = 'unassign' . $value;
......
......@@ -138,7 +138,8 @@ if ($capability) {
// Put the contexts into a tree structure.
foreach ($contexts as $conid => $con) {
$parentcontextid = get_parent_contextid($con);
$context = context::instance_by_id($conid);
$parentcontextid = get_parent_contextid($context);
if ($parentcontextid) {
$contexts[$parentcontextid]->children[] = $conid;
}
......@@ -196,7 +197,8 @@ function print_report_tree($contextid, $contexts, $allroles) {
$url = "$CFG->wwwroot/$CFG->admin/roles/override.php?contextid=$contextid";
$title = get_string('changeoverrides', 'tool_capability');
}
echo '<h3><a href="' . $url . '" title="' . $title . '">', print_context_name($contexts[$contextid]), '</a></h3>';
$context = context::instance_by_id($contextid);
echo '<h3><a href="' . $url . '" title="' . $title . '">', $context->get_context_name(), '</a></h3>';
// If there are any role overrides here, print them.
if (!empty($contexts[$contextid]->rolecapabilities)) {
......
......@@ -39,7 +39,9 @@
if ($shibbolethauth->user_login($frm->username, $frm->password)) {
$USER = authenticate_user_login($frm->username, $frm->password);
$user = authenticate_user_login($frm->username, $frm->password);
enrol_check_plugins($user);
session_set_user($user);
$USER->loggedin = true;
$USER->site = $CFG->wwwroot; // for added security, store the site in the
......@@ -75,9 +77,6 @@
}
}
enrol_check_plugins($USER);
load_all_capabilities(); /// This is what lets the user do anything on the site :-)
redirect($urltogo);
exit;
......
This files describes API changes in /auth/* - plugins,
information provided here is intended especially for developers.
=== 2.2 ===
required changes in code:
* the correct sequence to set up global $USER is:
$user = get_complete_user_data('username', $username); // or $user = authenticate_user_login()
enrol_check_plugins($user);
session_set_user($user);
......@@ -143,9 +143,9 @@ if (!empty($entry->id)) {
if ($CFG->useblogassociations && ($blogassociations = $DB->get_records('blog_association', array('blogid' => $entry->id)))) {
foreach ($blogassociations as $assocrec) {
$contextrec = $DB->get_record('context', array('id' => $assocrec->contextid));
$context = get_context_instance_by_id($assocrec->contextid);
switch ($contextrec->contextlevel) {
switch ($context->contextlevel) {
case CONTEXT_COURSE:
$entry->courseassoc = $assocrec->contextid;
break;
......
......@@ -95,7 +95,7 @@ class blog_edit_form extends moodleform {
$a->modname = $mod->name;
$context = get_context_instance(CONTEXT_MODULE, $modid);
} else {
$context = $DB->get_record('context', array('id' => $entry->modassoc));
$context = get_context_instance_by_id($entry->modassoc);
$cm = $DB->get_record('course_modules', array('id' => $context->instanceid));
$a = new stdClass();
$a->modtype = $DB->get_field('modules', 'name', array('id' => $cm->module));
......@@ -134,7 +134,7 @@ class blog_edit_form extends moodleform {
// validate course association
if (!empty($data['courseassoc']) && has_capability('moodle/blog:associatecourse', $sitecontext)) {
$coursecontext = $DB->get_record('context', array('id' => $data['courseassoc'], 'contextlevel' => CONTEXT_COURSE));
$coursecontext = get_context_instance(CONTEXT_COURSE, $data['courseassoc']);
if ($coursecontext) {
if (!is_enrolled($coursecontext) and !is_viewing($coursecontext)) {
......@@ -149,12 +149,12 @@ class blog_edit_form extends moodleform {
if (!empty($data['modassoc'])) {
$modcontextid = $data['modassoc'];
$modcontext = $DB->get_record('context', array('id' => $modcontextid, 'contextlevel' => CONTEXT_MODULE));
$modcontext = get_context_instance(CONTEXT_MODULE, $modcontextid);
if ($modcontext) {
// get context of the mod's course
$path = explode('/', $modcontext->path);
$coursecontext = $DB->get_record('context', array('id' => $path[(count($path) - 2)]));
$coursecontext = get_context_instance_by_id($path[(count($path) - 2)]);
// ensure only one course is associated
if (!empty($data['courseassoc'])) {
......
......@@ -247,10 +247,10 @@ class blog_entry {
// First find and show the associated course
foreach ($blogassociations as $assocrec) {
$contextrec = $DB->get_record('context', array('id' => $assocrec->contextid));
if ($contextrec->contextlevel == CONTEXT_COURSE) {
$assocurl = new moodle_url('/course/view.php', array('id' => $contextrec->instanceid));
$text = $DB->get_field('course', 'shortname', array('id' => $contextrec->instanceid)); //TODO: performance!!!!
$context = get_context_instance_by_id($assocrec->contextid);
if ($context->contextlevel == CONTEXT_COURSE) {
$assocurl = new moodle_url('/course/view.php', array('id' => $context->instanceid));
$text = $DB->get_field('course', 'shortname', array('id' => $context->instanceid)); //TODO: performance!!!!
$assocstr .= $OUTPUT->action_icon($assocurl, new pix_icon('i/course', $text), null, array(), true);
$hascourseassocs = true;
$assoctype = get_string('course');
......@@ -259,15 +259,15 @@ class blog_entry {
// Now show mod association
foreach ($blogassociations as $assocrec) {
$contextrec = $DB->get_record('context', array('id' => $assocrec->contextid));
$context = get_context_instance_by_id($assocrec->contextid);
if ($contextrec->contextlevel == CONTEXT_MODULE) {
if ($context->contextlevel == CONTEXT_MODULE) {
if ($hascourseassocs) {
$assocstr .= ', ';
$hascourseassocs = false;
}
$modinfo = $DB->get_record('course_modules', array('id' => $contextrec->instanceid));
$modinfo = $DB->get_record('course_modules', array('id' => $context->instanceid));
$modname = $DB->get_field('modules', 'name', array('id' => $modinfo->module));
$assocurl = new moodle_url('/mod/'.$modname.'/view.php', array('id' => $modinfo->id));
......
......@@ -77,7 +77,7 @@ class enrol_guest_plugin extends enrol_plugin {
if (empty($instance->password)) {
// Temporarily assign them some guest role for this context
$context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
$USER->access = load_temp_role($context, $CFG->guestroleid, $USER->access);
load_temp_course_role($context, $CFG->guestroleid);
return ENROL_REQUIRE_LOGIN_CACHE_PERIOD + time();
}
......@@ -131,7 +131,7 @@ class enrol_guest_plugin extends enrol_plugin {
// add guest role
$context = get_context_instance(CONTEXT_COURSE, $instance->courseid);
$USER->access = load_temp_role($context, $CFG->guestroleid, $USER->access);
load_temp_course_role($context, $CFG->guestroleid);
// go to the originally requested page
if (!empty($SESSION->wantsurl)) {
......
......@@ -38,9 +38,6 @@ $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST);
require_login();
// Refreshing enrolment data in the USER session
load_all_capabilities();
if ($SESSION->wantsurl) {
$destination = $SESSION->wantsurl;
unset($SESSION->wantsurl);
......
This files describes API changes in /enrol/* - plugins,
information provided here is intended especially for developers.
=== 2.2 ===
required changes in code:
* load_temp_role() is deprecated, use load_temp_course_role() instead, temp role not loaded
* remove_temp_role() is deprecated, use remove_temp_course_roles() instead
This diff is collapsed.
......@@ -1014,7 +1014,11 @@ function fix_course_sortorder() {
// now fix the paths and depths in context table if needed
if ($fixcontexts) {
rebuild_contexts($fixcontexts);
foreach ($fixcontexts as $fixcontext) {
$fixcontext->reset_paths(false);
}
context_helper::build_all_paths(false);
unset($fixcontexts);
}
// release memory
......
......@@ -29,14 +29,17 @@ defined('MOODLE_INTERNAL') || die();
function xmldb_main_install() {
global $CFG, $DB, $SITE;
/// make sure system context exists
$syscontext = get_system_context(false);
if ($syscontext->id != 1) {
/// Make sure system context exists
$syscontext = context_system::instance(0, MUST_EXIST, false);
if ($syscontext->id != SYSCONTEXTID) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new system context id!');
}
/// create site course
/// Create site course
if ($DB->record_exists('course', array())) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create frontpage course, courses already exist.');
}
$newsite = new stdClass();
$newsite->fullname = '';
$newsite->shortname = '';
......@@ -48,18 +51,38 @@ function xmldb_main_install() {
$newsite->timecreated = time();
$newsite->timemodified = $newsite->timecreated;
$newsite->id = $DB->insert_record('course', $newsite);
if (defined('SITEID')) {
$newsite->id = SITEID;
$DB->import_record('course', $newsite);
$DB->get_manager()->reset_sequence('course');
} else {
$newsite->id = $DB->insert_record('course', $newsite);
define('SITEID', $newsite->id);
}
$SITE = get_site();
if ($newsite->id != 1 or $SITE->id != 1) {
if ($newsite->id != $SITE->id) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new site course id!');
}
// Make sure site course context exists
context_course::instance($SITE->id);
// Update the global frontpage cache
$SITE = $DB->get_record('course', array('id'=>$newsite->id), '*', MUST_EXIST);
/// make sure site course context exists
get_context_instance(CONTEXT_COURSE, $SITE->id);
/// Create default course category
if ($DB->record_exists('course_categories', array())) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create default course category, categories already exist.');
}
$cat = new stdClass();
$cat->name = get_string('miscellaneous');
$cat->depth = 1;
$cat->sortorder = MAX_COURSES_IN_CATEGORY;
$cat->timemodified = time();
$catid = $DB->insert_record('course_categories', $cat);
$DB->set_field('course_categories', 'path', '/'.$catid, array('id'=>$catid));
// Make sure category context exists
context_coursecat::instance($catid);
/// create default course category
$cat = get_course_category();
$defaults = array(
'rolesactive' => '0', // marks fully set up system
......@@ -82,7 +105,7 @@ function xmldb_main_install() {
}
/// bootstrap mnet
/// Bootstrap mnet
$mnethost = new stdClass();
$mnethost->wwwroot = $CFG->wwwroot;
$mnethost->name = '';
......@@ -137,7 +160,10 @@ function xmldb_main_install() {
$mnetallhosts->id = $DB->insert_record('mnet_host', $mnetallhosts, true);
set_config('mnet_all_hosts_id', $mnetallhosts->id);
/// Create guest record - do not assign any role, guest user get's the default guest role automatically on the fly
/// Create guest record - do not assign any role, guest user gets the default guest role automatically on the fly
if ($DB->record_exists('user', array())) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create default users, users already exist.');
}
$guest = new stdClass();
$guest->auth = 'manual';
$guest->username = 'guest';
......@@ -156,6 +182,8 @@ function xmldb_main_install() {
}
// Store guest id
set_config('siteguest', $guest->id);
// Make sure user context exists
context_user::instance($guest->id);
/// Now create admin user
......@@ -176,8 +204,10 @@ function xmldb_main_install() {
if ($admin->id != 2) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new admin user id!');
}
/// Store list of admins
// Store list of admins
set_config('siteadmins', $admin->id);
// Make sure user context exists
context_user::instance($admin->id);
/// Install the roles system.
......@@ -264,17 +294,16 @@ function xmldb_main_install() {
require_once($CFG->libdir . '/licenselib.php');
license_manager::install_licenses();
/// Add two lines of data into this new table
// Init profile pages defaults
if ($DB->record_exists('my_pages', array())) {
throw new moodle_exception('generalexceptionmessage', 'error', '', 'Can not create default profile pages, records already exist.');
}
$mypage = new stdClass();
$mypage->userid = NULL;
$mypage->name = '__default';
$mypage->private = 0;
$mypage->sortorder = 0;
if (!$DB->record_exists('my_pages', array('userid'=>NULL, 'private'=>0))) {
$DB->insert_record('my_pages', $mypage);
}
$DB->insert_record('my_pages', $mypage);
$mypage->private = 1;
if (!$DB->record_exists('my_pages', array('userid'=>NULL, 'private'=>1))) {
$DB->insert_record('my_pages', $mypage);
}
$DB->insert_record('my_pages', $mypage);
}
......@@ -280,28 +280,28 @@ function isteacher() {
* @deprecated
*/
function isteacherinanycourse() {
error('Function isteacherinanycourse() was removed, please use capabilities instead!');
throw new coding_Exception('Function isteacherinanycourse() was removed, please use capabilities instead!');
}
/**
* @deprecated
*/
function get_guest() {
error('Function get_guest() was removed, please use capabilities instead!');
throw new coding_Exception('Function get_guest() was removed, please use capabilities instead!');
}
/**
* @deprecated
*/
function isguest() {
error('Function isguest() was removed, please use capabilities instead!');
throw new coding_Exception('Function isguest() was removed, please use capabilities instead!');
}
/**
* @deprecated
*/
function get_teacher() {
error('Function get_teacher() was removed, please use capabilities instead!');
throw new coding_Exception('Function get_teacher() was removed, please use capabilities instead!');
}
/**
......@@ -371,15 +371,7 @@ function get_recent_enrolments($courseid, $timestart) {
* @return object
*/
function make_context_subobj($rec) {
$ctx = new StdClass;
$ctx->id = $rec->ctxid; unset($rec->ctxid);
$ctx->path = $rec->ctxpath; unset($rec->ctxpath);
$ctx->depth = $rec->ctxdepth; unset($rec->ctxdepth);
$ctx->contextlevel = $rec->ctxlevel; unset($rec->ctxlevel);
$ctx->instanceid = $rec->id;
$rec->context = $ctx;
return $rec;
throw new coding_Exception('make_context_subobj() was removed, use new context preloading');
}
/**
......@@ -395,10 +387,7 @@ function make_context_subobj($rec) {
* for this thing.
*/
function is_context_subobj_valid($rec, $contextlevel) {
return isset($rec->context) && isset($rec->context->id) &&
isset($rec->context->path) && isset($rec->context->depth) &&
isset($rec->context->contextlevel) && isset($rec->context->instanceid) &&
$rec->context->contextlevel == $contextlevel && $rec->context->instanceid == $rec->id;
throw new coding_Exception('is_context_subobj_valid() was removed, use new context preloading');
}
/**
......@@ -415,9 +404,7 @@ function is_context_subobj_valid($rec, $contextlevel) {
* @param integer $contextlevel the type of thing $rec is, one of the CONTEXT_... constants.
*/
function ensure_context_subobj_present(&$rec, $contextlevel) {
if (!is_context_subobj_valid($rec, $contextlevel)) {
$rec->context = get_context_instance($contextlevel, $rec->id);
}
throw new coding_Exception('ensure_context_subobj_present() was removed, use new context preloading');
}
########### FROM weblib.php ##########################################################################
......
......@@ -188,7 +188,9 @@ function enrol_is_enabled($enrol) {
* Check all the login enrolment information for the given user object
* by querying the enrolment plugins
*
* @param object $user
* This function may be very slow, use only once after log-in or login-as.
*
* @param stdClass $user
* @return void
*/
function enrol_check_plugins($user) {
......@@ -201,7 +203,7 @@ function enrol_check_plugins($user) {
if (is_siteadmin()) {
// no sync for admin user, please use admin accounts only for admin tasks like the unix root user!
// if plugin fails on sync admins need to be able to log in
// if plugin fails on sync admins need to be able to log in and fix the settings
return;
}
......@@ -1150,7 +1152,7 @@ abstract class enrol_plugin {
}
if (isset($USER->enrol['tempguest'][$courseid])) {
unset($USER->enrol['tempguest'][$courseid]);
$USER->access = remove_temp_roles($context, $USER->access);
remove_temp_course_roles($context);
}
}
}
......@@ -1272,7 +1274,7 @@ abstract class enrol_plugin {
}
if (isset($USER->enrol['tempguest'][$courseid])) {
unset($USER->enrol['tempguest'][$courseid]);
$USER->access = remove_temp_roles($context, $USER->access);
remove_temp_course_roles($context);
}
}
}
......
......@@ -2790,7 +2790,7 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
} else {
//expired
unset($USER->enrol['tempguest'][$course->id]);
$USER->access = remove_temp_roles($coursecontext, $USER->access);
remove_temp_course_roles($coursecontext);
}
}
......@@ -2817,7 +2817,7 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
$access = true;
// remove traces of previous temp guest access
$USER->access = remove_temp_roles($coursecontext, $USER->access);
remove_temp_course_roles($coursecontext);
} else {
$instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
......@@ -2831,7 +2831,7 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
$until = $enrols[$instance->enrol]->try_autoenrol($instance);
if ($until !== false) {
$USER->enrol['enrolled'][$course->id] = $until;
$USER->access = remove_temp_roles($coursecontext, $USER->access);
remove_temp_course_roles($coursecontext);
$access = true;
break;
}
......@@ -3031,6 +3031,7 @@ function require_user_key_login($script, $instance=null) {
}
/// emulate normal session
enrol_check_plugins($user);
session_set_user($user);
/// note we are not using normal login
......@@ -3882,12 +3883,15 @@ function complete_user_login($user) {
// this helps prevent session fixation attacks from the same domain
session_regenerate_id(true);
// let enrol plugins deal with new enrolments if necessary
enrol_check_plugins($user);
// check enrolments, load caps and setup $USER object
session_set_user($user);
// reload preferences from DB
unset($user->preference);
check_user_preferences_loaded($user);
unset($USER->preference);
check_user_preferences_loaded($USER);
// update login times
update_user_login_times();
......
......@@ -2868,7 +2868,7 @@ class settings_navigation extends navigation_node {
$context = $this->context;
if ($context->contextlevel == CONTEXT_BLOCK) {
$this->load_block_settings();
$context = $DB->get_record_sql('SELECT ctx.* FROM {block_instances} bi LEFT JOIN {context} ctx ON ctx.id=bi.parentcontextid WHERE bi.id=?', array($context->instanceid));
$context = $context->get_parent_context();
}
switch ($context->contextlevel) {
......
......@@ -894,7 +894,8 @@ function get_moodle_cookie() {
/**
* Setup $USER object - called during login, loginas, etc.
* Preloads capabilities and checks enrolment plugins
*
* Call sync_user_enrolments() manually after log-in, or log-in-as.
*
* @param stdClass $user full user record object
* @return void
......@@ -902,11 +903,6 @@ function get_moodle_cookie() {
function session_set_user($user) {
$_SESSION['USER'] = $user;
unset($_SESSION['USER']->description); // conserve memory
if (!isset($_SESSION['USER']->access)) {
// check enrolments and load caps only once
enrol_check_plugins($_SESSION['USER']);
load_all_capabilities();
}
sesskey(); // init session key
}
......@@ -950,6 +946,10 @@ function session_loginas($userid, $context) {
$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);
}
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment