From ec0d6ea2a9e5ebd1a457e51e2cf639f27ca082a8 Mon Sep 17 00:00:00 2001 From: Dongsheng Cai Date: Fri, 25 Nov 2011 14:44:13 +0800 Subject: [PATCH 1/2] MDL-28646 Web service: core_course_get_contents() 1. Implement core_course_get_contents web service 2. Implement callbacks in folder, resource, page and url modules 3. Implement download script for web service --- course/externallib.php | 205 +++++- lang/en/error.php | 1 + lang/en/webservice.php | 3 + lib/db/services.php | 12 +- lib/filelib.php | 766 +++++++++++++++++++++++ mod/folder/lib.php | 33 + mod/page/lib.php | 101 ++- mod/resource/lib.php | 33 + mod/url/lib.php | 37 ++ pluginfile.php | 751 +--------------------- webservice/pluginfile.php | 119 ++++ webservice/simpletest/testwebservice.php | 26 +- webservice/upload.php | 35 ++ 13 files changed, 1354 insertions(+), 768 deletions(-) create mode 100644 webservice/pluginfile.php diff --git a/course/externallib.php b/course/externallib.php index 3724e303d85..e6a86ea9e87 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -33,6 +33,209 @@ require_once("$CFG->libdir/externallib.php"); */ class core_course_external extends external_api { + /** + * Returns description of method parameters + * @return external_function_parameters + */ + public static function get_course_contents_parameters() { + return new external_function_parameters( + array('courseid' => new external_value(PARAM_INT, 'course id'), + 'options' => new external_multiple_structure ( + new external_single_structure( + array('name' => new external_value(PARAM_ALPHANUM, 'option name'), + 'value' => new external_value(PARAM_RAW, 'the value of the option, this param is personaly validated in the external function.') + ) + ), 'Options, not used yet, might be used in later version', VALUE_DEFAULT, array()) + ) + ); + } + + /** + * Get course contents + * @param int $courseid + * @param array $options, not used yet, might be used in later version + * @return array + */ + public static function get_course_contents($courseid, $options) { + global $CFG, $DB; + require_once($CFG->dirroot . "/course/lib.php"); + + //validate parameter + $params = self::validate_parameters(self::get_course_contents_parameters(), + array('courseid' => $courseid, 'options' => $options)); + + //retrieve the course + $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST); + + //check course format exist + if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) { + throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null, get_string('courseformatnotfound', 'error', '', $course->format)); + } else { + require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php'); + } + + // now security checks + $context = get_context_instance(CONTEXT_COURSE, $course->id); + try { + self::validate_context($context); + } catch (Exception $e) { + $exceptionparam = new stdClass(); + $exceptionparam->message = $e->getMessage(); + $exceptionparam->courseid = $course->id; + throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam); + } + + $canupdatecourse = has_capability('moodle/course:update', $context); + + //create return value + $coursecontents = array(); + + if ($canupdatecourse or $course->visible + or has_capability('moodle/course:viewhiddencourses', $context)) { + + //retrieve sections + $modinfo = get_fast_modinfo($course); + $sections = get_all_sections($course->id); + + //for each sections (first displayed to last displayed) + foreach ($sections as $key => $section) { + + $showsection = (has_capability('moodle/course:viewhiddensections', $context) or $section->visible or !$course->hiddensections); + if (!$showsection) { + continue; + } + + // reset $sectioncontents + $sectionvalues = array(); + $sectionvalues['id'] = $section->id; + $sectionvalues['name'] = get_section_name($course, $section); + $summary = file_rewrite_pluginfile_urls($section->summary, 'webservice/pluginfile.php', $context->id, 'course', 'section', $section->id); + $sectionvalues['visible'] = $section->visible; + $sectionvalues['summary'] = format_text($summary, $section->summaryformat); + $sectioncontents = array(); + + //for each module of the section + foreach ($modinfo->sections[$section->section] as $cmid) { //matching /course/lib.php:print_section() logic + $cm = $modinfo->cms[$cmid]; + + // stop here if the module is not visible to the user + if (!$cm->uservisible) { + continue; + } + + $module = array(); + + //common info (for people being able to see the module or availability dates) + $module['id'] = $cm->id; + $module['name'] = format_string($cm->name, true); + $module['modname'] = $cm->modname; + $module['modplural'] = $cm->modplural; + $module['modicon'] = $cm->get_icon_url()->out(false); + $module['indent'] = $cm->indent; + + $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); + + if (!empty($cm->showdescription)) { + $module['description'] = $cm->get_content(); + } + + //url of the module + $url = $cm->get_url(); + if ($url) { //labels don't have url + $module['url'] = $cm->get_url()->out(); + } + + $canviewhidden = has_capability('moodle/course:viewhiddenactivities', + get_context_instance(CONTEXT_MODULE, $cm->id)); + //user that can view hidden module should know about the visibility + $module['visible'] = $cm->visible; + + //availability date (also send to user who can see hidden module when the showavailabilyt is ON) + if ($canupdatecourse or ($CFG->enableavailability && $canviewhidden && $cm->showavailability)) { + $module['availablefrom'] = $cm->availablefrom; + $module['availableuntil'] = $cm->availableuntil; + } + + $baseurl = 'webservice/pluginfile.php'; + + //call $modulename_export_contents + //(each module callback take care about checking the capabilities) + require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php'); + $getcontentfunction = $cm->modname.'_export_contents'; + if (function_exists($getcontentfunction)) { + if ($contents = $getcontentfunction($cm, $baseurl)) { + $module['contents'] = $contents; + } + } + + //assign result to $sectioncontents + $sectioncontents[] = $module; + + } + $sectionvalues['modules'] = $sectioncontents; + + // assign result to $coursecontents + $coursecontents[] = $sectionvalues; + } + } + return $coursecontents; + } + + /** + * Returns description of method result value + * @return external_description + */ + public static function get_course_contents_returns() { + return new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'Section ID'), + 'name' => new external_value(PARAM_TEXT, 'Section name'), + 'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL), + 'summary' => new external_value(PARAM_RAW, 'Section description'), + 'modules' => new external_multiple_structure( + new external_single_structure( + array( + 'id' => new external_value(PARAM_INT, 'activity id'), + 'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL), + 'name' => new external_value(PARAM_TEXT, 'activity module name'), + 'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL), + 'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL), + 'modicon' => new external_value(PARAM_URL, 'activity icon url'), + 'modname' => new external_value(PARAM_PLUGIN, 'activity module type'), + 'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'), + 'availablefrom' => new external_value(PARAM_INT, 'module availability start date', VALUE_OPTIONAL), + 'availableuntil' => new external_value(PARAM_INT, 'module availability en date', VALUE_OPTIONAL), + 'indent' => new external_value(PARAM_INT, 'number of identation in the site'), + 'contents' => new external_multiple_structure( + new external_single_structure( + array( + // content info + 'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'), + 'filename'=> new external_value(PARAM_FILE, 'filename'), + 'filepath'=> new external_value(PARAM_PATH, 'filepath'), + 'filesize'=> new external_value(PARAM_INT, 'filesize'), + 'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL), + 'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL), + 'timecreated' => new external_value(PARAM_INT, 'Time created'), + 'timemodified' => new external_value(PARAM_INT, 'Time modified'), + 'sortorder' => new external_value(PARAM_INT, 'Content sort order'), + + // copyright related info + 'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'), + 'author' => new external_value(PARAM_TEXT, 'Content owner'), + 'license' => new external_value(PARAM_TEXT, 'Content license'), + ) + ), VALUE_DEFAULT, array() + ) + ) + ), 'list of module' + ) + ) + ) + ); + } + /** * Returns description of method parameters * @return external_function_parameters @@ -430,4 +633,4 @@ class moodle_course_external extends external_api { return core_course_external::create_courses_returns(); } -} \ No newline at end of file +} diff --git a/lang/en/error.php b/lang/en/error.php index f0e125add84..3591183bef7 100644 --- a/lang/en/error.php +++ b/lang/en/error.php @@ -164,6 +164,7 @@ $string['couldnotassignrole'] = 'A serious but unspecified error occurred while $string['couldnotupdatenoexistinguser'] = 'Cannot update the user - user doesn\'t exist'; $string['countriesphpempty'] = 'Error: The file countries.php in language pack {$a} is empty or missing.'; $string['coursedoesnotbelongtocategory'] = 'The course doesn\'t belong to this category'; +$string['courseformatnotfound'] = 'The course format \'{$a}\' doesn\'t exist or is not recognized'; $string['coursegroupunknown'] = 'Course corresponding to group {$a} not specified'; $string['courseidnotfound'] = 'Course id doesn\'t exist'; $string['coursemisconf'] = 'Course is misconfigured'; diff --git a/lang/en/webservice.php b/lang/en/webservice.php index 2d28d61cba4..7d04b9cf0e5 100644 --- a/lang/en/webservice.php +++ b/lang/en/webservice.php @@ -43,6 +43,7 @@ $string['apiexplorernotavalaible'] = 'API explorer not available yet.'; $string['arguments'] = 'Arguments'; $string['authmethod'] = 'Authentication method'; $string['cannotcreatetoken'] = 'No permission to create web service token for the service {$a}.'; +$string['cannotgetcoursecontents'] = 'Cannot get course contents'; $string['configwebserviceplugins'] = 'For security reasons, only protocols that are in use should be enabled.'; $string['context'] = 'Context'; $string['createservicedescription'] = 'A service is a set of web service functions. You will allow the user to access to a new service. On the Add service page check \'Enable\' and \'Authorised users\' options. Select \'No required capability\'.'; @@ -74,6 +75,7 @@ $string['enableprotocols'] = 'Enable protocols'; $string['enableprotocolsdescription'] = 'At least one protocol should be enabled. For security reasons, only protocols that are to be used should be enabled.'; $string['enablews'] = 'Enable web services'; $string['enablewsdescription'] = 'Web services must be enabled in Advanced features.'; +$string['enabledirectdownload'] = 'Web service file downloading must be enabled in external service settings'; $string['entertoken'] = 'Enter a security key/token:'; $string['error'] = 'Error: {$a}'; $string['errorcatcontextnotvalid'] = 'You cannot execute functions in the category context (category id:{$a->catid}). The context error message was: {$a->message}'; @@ -177,6 +179,7 @@ $string['token'] = 'Token'; $string['tokenauthlog'] = 'Token authentication'; $string['tokencreatedbyadmin'] = 'Can only be reset by administrator (*)'; $string['tokencreator'] = 'Creator'; +$string['unknownoptionkey'] = 'Unknown option key ({$a})'; $string['updateusersettings'] = 'Update'; $string['userasclients'] = 'Users as clients with token'; $string['userasclientsdescription'] = 'The following steps help you to set up the Moodle web service for users as clients. These steps also help to set up the recommended token (security keys) authentication method. In this use case, the user will generate his token from the security keys page via My profile settings.'; diff --git a/lib/db/services.php b/lib/db/services.php index f91ec49cbd2..e721810f7e7 100644 --- a/lib/db/services.php +++ b/lib/db/services.php @@ -404,6 +404,15 @@ $functions = array( 'capabilities'=> 'moodle/course:create,moodle/course:visibility', ), + 'core_course_get_contents' => array( + 'classname' => 'core_course_external', + 'methodname' => 'get_course_contents', + 'classpath' => 'course/externallib.php', + 'description' => 'Get course contents', + 'type' => 'read', + 'capabilities'=> 'moodle/course:update,moodle/course:viewhiddencourses', + ), + // === message related functions === 'moodle_message_send_instantmessages' => array( @@ -474,7 +483,8 @@ $services = array( 'moodle_notes_create_notes', 'moodle_user_get_course_participants_by_id', 'moodle_user_get_users_by_courseid', - 'moodle_message_send_instantmessages'), + 'moodle_message_send_instantmessages', + 'core_course_get_contents'), 'enabled' => 0, 'restrictedusers' => 0, 'shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, diff --git a/lib/filelib.php b/lib/filelib.php index 02c350150c2..af72cf70a12 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3137,3 +3137,769 @@ class filetype_parser { return $this->result; } } + +/** + * This function delegates file serving to individual plugins + * + * @param string $relativepath + * @param bool $forcedownload + * + * @package core + * @subpackage file + * @copyright 2008 Petr Skoda (http://skodak.org) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +function file_pluginfile($relativepath, $forcedownload) { + global $DB, $CFG, $USER; + // relative path must start with '/' + if (!$relativepath) { + print_error('invalidargorconf'); + } else if ($relativepath[0] != '/') { + print_error('pathdoesnotstartslash'); + } + + // extract relative path components + $args = explode('/', ltrim($relativepath, '/')); + + if (count($args) < 3) { // always at least context, component and filearea + print_error('invalidarguments'); + } + + $contextid = (int)array_shift($args); + $component = clean_param(array_shift($args), PARAM_COMPONENT); + $filearea = clean_param(array_shift($args), PARAM_AREA); + + list($context, $course, $cm) = get_context_info_array($contextid); + + $fs = get_file_storage(); + + // ======================================================================================================================== + if ($component === 'blog') { + // Blog file serving + if ($context->contextlevel != CONTEXT_SYSTEM) { + send_file_not_found(); + } + if ($filearea !== 'attachment' and $filearea !== 'post') { + send_file_not_found(); + } + + if (empty($CFG->bloglevel)) { + print_error('siteblogdisable', 'blog'); + } + + if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) { + require_login(); + if (isguestuser()) { + print_error('noguest'); + } + if ($CFG->bloglevel == BLOG_USER_LEVEL) { + if ($USER->id != $entry->userid) { + send_file_not_found(); + } + } + } + $entryid = (int)array_shift($args); + if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) { + send_file_not_found(); + } + + if ('publishstate' === 'public') { + if ($CFG->forcelogin) { + require_login(); + } + + } else if ('publishstate' === 'site') { + require_login(); + //ok + } else if ('publishstate' === 'draft') { + require_login(); + if ($USER->id != $entry->userid) { + send_file_not_found(); + } + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + + if (!$file = $fs->get_file($context->id, $component, $filearea, $entryid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + send_stored_file($file, 10*60, 0, true); // download MUST be forced - security! + + // ======================================================================================================================== + } else if ($component === 'grade') { + if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) { + // Global gradebook files + if ($CFG->forcelogin) { + require_login(); + } + + $fullpath = "/$context->id/$component/$filearea/".implode('/', $args); + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) { + //TODO: nobody implemented this yet in grade edit form!! + send_file_not_found(); + + if ($CFG->forcelogin || $course->id != SITEID) { + require_login($course); + } + + $fullpath = "/$context->id/$component/$filearea/".implode('/', $args); + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'tag') { + if ($filearea === 'description' and $context->contextlevel == CONTEXT_SYSTEM) { + + // All tag descriptions are going to be public but we still need to respect forcelogin + if ($CFG->forcelogin) { + require_login(); + } + + $fullpath = "/$context->id/tag/description/".implode('/', $args); + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, true); + + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'calendar') { + if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_SYSTEM) { + + // All events here are public the one requirement is that we respect forcelogin + if ($CFG->forcelogin) { + require_login(); + } + + // Get the event if from the args array + $eventid = array_shift($args); + + // Load the event from the database + if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'eventtype'=>'site'))) { + send_file_not_found(); + } + // Check that we got an event and that it's userid is that of the user + + // Get the file and serve if successful + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) { + + // Must be logged in, if they are not then they obviously can't be this user + require_login(); + + // Don't want guests here, potentially saves a DB call + if (isguestuser()) { + send_file_not_found(); + } + + // Get the event if from the args array + $eventid = array_shift($args); + + // Load the event from the database - user id must match + if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'userid'=>$USER->id, 'eventtype'=>'user'))) { + send_file_not_found(); + } + + // Get the file and serve if successful + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) { + + // Respect forcelogin and require login unless this is the site.... it probably + // should NEVER be the site + if ($CFG->forcelogin || $course->id != SITEID) { + require_login($course); + } + + // Must be able to at least view the course + if (!is_enrolled($context) and !is_viewing($context)) { + //TODO: hmm, do we really want to block guests here? + send_file_not_found(); + } + + // Get the event id + $eventid = array_shift($args); + + // Load the event from the database we need to check whether it is + // a) valid course event + // b) a group event + // Group events use the course context (there is no group context) + if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'courseid'=>$course->id))) { + send_file_not_found(); + } + + // If its a group event require either membership of view all groups capability + if ($event->eventtype === 'group') { + if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($event->groupid, $USER->id)) { + send_file_not_found(); + } + } else if ($event->eventtype === 'course') { + //ok + } else { + // some other type + send_file_not_found(); + } + + // If we get this far we can serve the file + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'user') { + if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) { + $redirect = false; + if (count($args) == 1) { + $themename = theme_config::DEFAULT_THEME; + $filename = array_shift($args); + } else { + $themename = array_shift($args); + $filename = array_shift($args); + } + if ((!empty($CFG->forcelogin) and !isloggedin())) { + // protect images if login required and not logged in; + // do not use require_login() because it is expensive and not suitable here anyway + $redirect = true; + } + if (!$redirect and ($filename !== 'f1' and $filename !== 'f2')) { + $filename = 'f1'; + $redirect = true; + } + if (!$redirect && !$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) { + if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.jpg')) { + $redirect = true; + } + } + if ($redirect) { + $theme = theme_config::load($themename); + redirect($theme->pix_url('u/'.$filename, 'moodle')); + } + send_stored_file($file, 60*60*24); // enable long caching, there are many images on each page + + } else if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) { + require_login(); + + if (isguestuser()) { + send_file_not_found(); + } + + if ($USER->id !== $context->instanceid) { + send_file_not_found(); + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 0, 0, true); // must force download - security! + + } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) { + + if ($CFG->forcelogin) { + require_login(); + } + + $userid = $context->instanceid; + + if ($USER->id == $userid) { + // always can access own + + } else if (!empty($CFG->forceloginforprofiles)) { + require_login(); + + if (isguestuser()) { + send_file_not_found(); + } + + // we allow access to site profile of all course contacts (usually teachers) + if (!has_coursecontact_role($userid) && !has_capability('moodle/user:viewdetails', $context)) { + send_file_not_found(); + } + + $canview = false; + if (has_capability('moodle/user:viewdetails', $context)) { + $canview = true; + } else { + $courses = enrol_get_my_courses(); + } + + while (!$canview && count($courses) > 0) { + $course = array_shift($courses); + if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $course->id))) { + $canview = true; + } + } + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 0, 0, true); // must force download - security! + + } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) { + $userid = (int)array_shift($args); + $usercontext = get_context_instance(CONTEXT_USER, $userid); + + if ($CFG->forcelogin) { + require_login(); + } + + if (!empty($CFG->forceloginforprofiles)) { + require_login(); + if (isguestuser()) { + print_error('noguest'); + } + + //TODO: review this logic of user profile access prevention + if (!has_coursecontact_role($userid) and !has_capability('moodle/user:viewdetails', $usercontext)) { + print_error('usernotavailable'); + } + if (!has_capability('moodle/user:viewdetails', $context) && !has_capability('moodle/user:viewdetails', $usercontext)) { + print_error('cannotviewprofile'); + } + if (!is_enrolled($context, $userid)) { + print_error('notenrolledprofile'); + } + if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { + print_error('groupnotamember'); + } + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($usercontext->id, 'user', 'profile', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 0, 0, true); // must force download - security! + + } else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) { + require_login(); + + if (isguestuser()) { + send_file_not_found(); + } + $userid = $context->instanceid; + + if ($USER->id != $userid) { + send_file_not_found(); + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'user', 'backup', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 0, 0, true); // must force download - security! + + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'coursecat') { + if ($context->contextlevel != CONTEXT_COURSECAT) { + send_file_not_found(); + } + + if ($filearea === 'description') { + if ($CFG->forcelogin) { + // no login necessary - unless login forced everywhere + require_login(); + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'coursecat', 'description', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'course') { + if ($context->contextlevel != CONTEXT_COURSE) { + send_file_not_found(); + } + + if ($filearea === 'summary') { + if ($CFG->forcelogin) { + require_login(); + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'course', 'summary', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'section') { + if ($CFG->forcelogin) { + require_login($course); + } else if ($course->id != SITEID) { + require_login($course); + } + + $sectionid = (int)array_shift($args); + + if (!$section = $DB->get_record('course_sections', array('id'=>$sectionid, 'course'=>$course->id))) { + send_file_not_found(); + } + + if ($course->numsections < $section->section) { + if (!has_capability('moodle/course:update', $context)) { + // block access to unavailable sections if can not edit course + send_file_not_found(); + } + } + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'course', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else { + send_file_not_found(); + } + + } else if ($component === 'group') { + if ($context->contextlevel != CONTEXT_COURSE) { + send_file_not_found(); + } + + require_course_login($course, true, null, false); + + $groupid = (int)array_shift($args); + + $group = $DB->get_record('groups', array('id'=>$groupid, 'courseid'=>$course->id), '*', MUST_EXIST); + if (($course->groupmodeforce and $course->groupmode == SEPARATEGROUPS) and !has_capability('moodle/site:accessallgroups', $context) and !groups_is_member($group->id, $USER->id)) { + // do not allow access to separate group info if not member or teacher + send_file_not_found(); + } + + if ($filearea === 'description') { + + require_login($course); + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'group', 'description', $group->id, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'icon') { + $filename = array_pop($args); + + if ($filename !== 'f1' and $filename !== 'f2') { + send_file_not_found(); + } + if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.png')) { + if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.jpg')) { + send_file_not_found(); + } + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60); + + } else { + send_file_not_found(); + } + + } else if ($component === 'grouping') { + if ($context->contextlevel != CONTEXT_COURSE) { + send_file_not_found(); + } + + require_login($course); + + $groupingid = (int)array_shift($args); + + // note: everybody has access to grouping desc images for now + if ($filearea === 'description') { + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'grouping', 'description', $groupingid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'backup') { + if ($filearea === 'course' and $context->contextlevel == CONTEXT_COURSE) { + require_login($course); + require_capability('moodle/backup:downloadfile', $context); + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'backup', 'course', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 0, 0, $forcedownload); + + } else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) { + require_login($course); + require_capability('moodle/backup:downloadfile', $context); + + $sectionid = (int)array_shift($args); + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'backup', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) { + require_login($course, false, $cm); + require_capability('moodle/backup:downloadfile', $context); + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'backup', 'activity', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); + send_stored_file($file, 60*60, 0, $forcedownload); + + } else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) { + // Backup files that were generated by the automated backup systems. + + require_login($course); + require_capability('moodle/site:config', $context); + + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'backup', 'automated', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 0, 0, $forcedownload); + + } else { + send_file_not_found(); + } + + // ======================================================================================================================== + } else if ($component === 'question') { + require_once($CFG->libdir . '/questionlib.php'); + question_pluginfile($course, $context, 'question', $filearea, $args, $forcedownload); + send_file_not_found(); + + // ======================================================================================================================== + } else if ($component === 'grading') { + if ($filearea === 'description') { + // files embedded into the form definition description + + if ($context->contextlevel == CONTEXT_SYSTEM) { + require_login(); + + } else if ($context->contextlevel >= CONTEXT_COURSE) { + require_login($course, false, $cm); + + } else { + send_file_not_found(); + } + + $formid = (int)array_shift($args); + + $sql = "SELECT ga.id + FROM {grading_areas} ga + JOIN {grading_definitions} gd ON (gd.areaid = ga.id) + WHERE gd.id = ? AND ga.contextid = ?"; + $areaid = $DB->get_field_sql($sql, array($formid, $context->id), IGNORE_MISSING); + + if (!$areaid) { + send_file_not_found(); + } + + $fullpath = "/$context->id/$component/$filearea/$formid/".implode('/', $args); + + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + send_file_not_found(); + } + + session_get_instance()->write_close(); // unlock session during fileserving + send_stored_file($file, 60*60, 0, $forcedownload); + } + + // ======================================================================================================================== + } else if (strpos($component, 'mod_') === 0) { + $modname = substr($component, 4); + if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) { + send_file_not_found(); + } + require_once("$CFG->dirroot/mod/$modname/lib.php"); + + if ($context->contextlevel == CONTEXT_MODULE) { + if ($cm->modname !== $modname) { + // somebody tries to gain illegal access, cm type must match the component! + send_file_not_found(); + } + } + + if ($filearea === 'intro') { + if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) { + send_file_not_found(); + } + require_course_login($course, true, $cm); + + // all users may access it + $filename = array_pop($args); + $filepath = $args ? '/'.implode('/', $args).'/' : '/'; + if (!$file = $fs->get_file($context->id, 'mod_'.$modname, 'intro', 0, $filepath, $filename) or $file->is_directory()) { + send_file_not_found(); + } + + $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; + + // finally send the file + send_stored_file($file, $lifetime, 0); + } + + $filefunction = $component.'_pluginfile'; + $filefunctionold = $modname.'_pluginfile'; + if (function_exists($filefunction)) { + // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" + $filefunction($course, $cm, $context, $filearea, $args, $forcedownload); + } else if (function_exists($filefunctionold)) { + // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" + $filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload); + } + + send_file_not_found(); + + // ======================================================================================================================== + } else if (strpos($component, 'block_') === 0) { + $blockname = substr($component, 6); + // note: no more class methods in blocks please, that is .... + if (!file_exists("$CFG->dirroot/blocks/$blockname/lib.php")) { + send_file_not_found(); + } + require_once("$CFG->dirroot/blocks/$blockname/lib.php"); + + if ($context->contextlevel == CONTEXT_BLOCK) { + $birecord = $DB->get_record('block_instances', array('id'=>$context->instanceid), '*',MUST_EXIST); + if ($birecord->blockname !== $blockname) { + // somebody tries to gain illegal access, cm type must match the component! + send_file_not_found(); + } + } else { + $birecord = null; + } + + $filefunction = $component.'_pluginfile'; + if (function_exists($filefunction)) { + // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" + $filefunction($course, $birecord, $context, $filearea, $args, $forcedownload); + } + + send_file_not_found(); + + } else if (strpos($component, '_') === false) { + // all core subsystems have to be specified above, no more guessing here! + send_file_not_found(); + + } else { + // try to serve general plugin file in arbitrary context + $dir = get_component_directory($component); + if (!file_exists("$dir/lib.php")) { + send_file_not_found(); + } + include_once("$dir/lib.php"); + + $filefunction = $component.'_pluginfile'; + if (function_exists($filefunction)) { + // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" + $filefunction($course, $cm, $context, $filearea, $args, $forcedownload); + } + + send_file_not_found(); + } + +} diff --git a/mod/folder/lib.php b/mod/folder/lib.php index aaee01759ec..c7598c8fc8e 100644 --- a/mod/folder/lib.php +++ b/mod/folder/lib.php @@ -348,3 +348,36 @@ function folder_page_type_list($pagetype, $parentcontext, $currentcontext) { $module_pagetype = array('mod-folder-*'=>get_string('page-mod-folder-x', 'folder')); return $module_pagetype; } + +/** + * Export folder resource contents + * + * @return array of file content + */ +function folder_export_contents($cm, $baseurl) { + global $CFG, $DB; + $contents = array(); + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + $folder = $DB->get_record('folder', array('id'=>$cm->instance), '*', MUST_EXIST); + + $fs = get_file_storage(); + $files = $fs->get_area_files($context->id, 'mod_folder', 'content', 0, 'sortorder DESC, id ASC', false); + + foreach ($files as $fileinfo) { + $file = array(); + $file['type'] = 'file'; + $file['filename'] = $fileinfo->get_filename(); + $file['filepath'] = $fileinfo->get_filepath(); + $file['filesize'] = $fileinfo->get_filesize(); + $file['fileurl'] = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_folder/content/'.$folder->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true); + $file['timecreated'] = $fileinfo->get_timecreated(); + $file['timemodified'] = $fileinfo->get_timemodified(); + $file['sortorder'] = $fileinfo->get_sortorder(); + $file['userid'] = $fileinfo->get_userid(); + $file['author'] = $fileinfo->get_author(); + $file['license'] = $fileinfo->get_license(); + $contents[] = $file; + } + + return $contents; +} diff --git a/mod/page/lib.php b/mod/page/lib.php index bfa511551f3..ef3d84b52f4 100644 --- a/mod/page/lib.php +++ b/mod/page/lib.php @@ -369,26 +369,46 @@ function page_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa return false; } - array_shift($args); // ignore revision - designed to prevent caching problems only + // $arg could be revision number or index.html + $arg = array_shift($args); + if ($arg == 'index.html' || $arg == 'index.htm') { + // serve page content + $filename = $arg; - $fs = get_file_storage(); - $relativepath = implode('/', $args); - $fullpath = "/$context->id/mod_page/$filearea/0/$relativepath"; - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - $page = $DB->get_record('page', array('id'=>$cm->instance), 'id, legacyfiles', MUST_EXIST); - if ($page->legacyfiles != RESOURCELIB_LEGACYFILES_ACTIVE) { + if (!$page = $DB->get_record('page', array('id'=>$cm->instance), '*', MUST_EXIST)) { return false; } - if (!$file = resourcelib_try_file_migration('/'.$relativepath, $cm->id, $cm->course, 'mod_page', 'content', 0)) { - return false; + + // remove @@PLUGINFILE@@/ + $content = str_replace('@@PLUGINFILE@@/', '', $page->content); + + $formatoptions = new stdClass; + $formatoptions->noclean = true; + $formatoptions->overflowdiv = true; + $formatoptions->context = $context; + $content = format_text($content, $page->contentformat, $formatoptions); + + send_file($content, $filename, 0, 0, true, true); + } else { + $fs = get_file_storage(); + $relativepath = implode('/', $args); + $fullpath = "/$context->id/mod_page/$filearea/0/$relativepath"; + if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { + $page = $DB->get_record('page', array('id'=>$cm->instance), 'id, legacyfiles', MUST_EXIST); + if ($page->legacyfiles != RESOURCELIB_LEGACYFILES_ACTIVE) { + return false; + } + if (!$file = resourcelib_try_file_migration('/'.$relativepath, $cm->id, $cm->course, 'mod_page', 'content', 0)) { + return false; + } + //file migrate - update flag + $page->legacyfileslast = time(); + $DB->update_record('page', $page); } - //file migrate - update flag - $page->legacyfileslast = time(); - $DB->update_record('page', $page); + + // finally send the file + send_stored_file($file, 86400, 0, $forcedownload); } - - // finally send the file - send_stored_file($file, 86400, 0, $forcedownload); } @@ -422,3 +442,54 @@ function page_page_type_list($pagetype, $parentcontext, $currentcontext) { $module_pagetype = array('mod-page-*'=>get_string('page-mod-page-x', 'page')); return $module_pagetype; } + +/** + * Export page resource contents + * + * @return array of file content + */ +function page_export_contents($cm, $baseurl) { + global $CFG, $DB; + $contents = array(); + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + + $page = $DB->get_record('page', array('id'=>$cm->instance), '*', MUST_EXIST); + + // page contents + $fs = get_file_storage(); + $files = $fs->get_area_files($context->id, 'mod_page', 'content', 0, 'sortorder DESC, id ASC', false); + foreach ($files as $fileinfo) { + $file = array(); + $file['type'] = 'file'; + $file['filename'] = $fileinfo->get_filename(); + $file['filepath'] = $fileinfo->get_filepath(); + $file['filesize'] = $fileinfo->get_filesize(); + $file['fileurl'] = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_page/content/'.$page->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true); + $file['timecreated'] = $fileinfo->get_timecreated(); + $file['timemodified'] = $fileinfo->get_timemodified(); + $file['sortorder'] = $fileinfo->get_sortorder(); + $file['userid'] = $fileinfo->get_userid(); + $file['author'] = $fileinfo->get_author(); + $file['license'] = $fileinfo->get_license(); + $contents[] = $file; + } + + // page html conent + $filename = 'index.html'; + $pagefile = array(); + $pagefile['type'] = 'file'; + $pagefile['filename'] = $filename; + $pagefile['filepath'] = '/'; + $pagefile['filesize'] = 0; + $pagefile['fileurl'] = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_page/content/' . $filename, true); + $pagefile['timecreated'] = null; + $pagefile['timemodified'] = $page->timemodified; + // make this file as main file + $pagefile['sortorder'] = 1; + $pagefile['userid'] = null; + $pagefile['author'] = null; + $pagefile['license'] = null; + $contents[] = $pagefile; + + return $contents; +} diff --git a/mod/resource/lib.php b/mod/resource/lib.php index 341e588fc40..44afba3b5e1 100644 --- a/mod/resource/lib.php +++ b/mod/resource/lib.php @@ -446,3 +446,36 @@ function resource_page_type_list($pagetype, $parentcontext, $currentcontext) { $module_pagetype = array('mod-resource-*'=>get_string('page-mod-resource-x', 'resource')); return $module_pagetype; } + +/** + * Export file resource contents + * + * @return array of file content + */ +function resource_export_contents($cm, $baseurl) { + global $CFG, $DB; + $contents = array(); + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + $resource = $DB->get_record('resource', array('id'=>$cm->instance), '*', MUST_EXIST); + + $fs = get_file_storage(); + $files = $fs->get_area_files($context->id, 'mod_resource', 'content', 0, 'sortorder DESC, id ASC', false); + + foreach ($files as $fileinfo) { + $file = array(); + $file['type'] = 'file'; + $file['filename'] = $fileinfo->get_filename(); + $file['filepath'] = $fileinfo->get_filepath(); + $file['filesize'] = $fileinfo->get_filesize(); + $file['fileurl'] = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_resource/content/'.$resource->revision.$fileinfo->get_filepath().$fileinfo->get_filename(), true); + $file['timecreated'] = $fileinfo->get_timecreated(); + $file['timemodified'] = $fileinfo->get_timemodified(); + $file['sortorder'] = $fileinfo->get_sortorder(); + $file['userid'] = $fileinfo->get_userid(); + $file['author'] = $fileinfo->get_author(); + $file['license'] = $fileinfo->get_license(); + $contents[] = $file; + } + + return $contents; +} diff --git a/mod/url/lib.php b/mod/url/lib.php index c49fd401a20..f8400e25608 100644 --- a/mod/url/lib.php +++ b/mod/url/lib.php @@ -329,3 +329,40 @@ function url_page_type_list($pagetype, $parentcontext, $currentcontext) { $module_pagetype = array('mod-url-*'=>get_string('page-mod-url-x', 'url')); return $module_pagetype; } + +/** + * Export URL resource contents + * + * @return array of file content + */ +function url_export_contents($cm, $baseurl) { + global $CFG, $DB; + require_once("$CFG->dirroot/mod/url/locallib.php"); + $contents = array(); + $context = get_context_instance(CONTEXT_MODULE, $cm->id); + + $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); + $url = $DB->get_record('url', array('id'=>$cm->instance), '*', MUST_EXIST); + + $fullurl = str_replace('&', '&', url_get_full_url($url, $cm, $course)); + $isurl = clean_param($fullurl, PARAM_URL); + if (empty($isurl)) { + return null; + } + + $url = array(); + $url['type'] = 'url'; + $url['filename'] = $url->name; + $url['filepath'] = null; + $url['filesize'] = 0; + $url['fileurl'] = $fullurl; + $url['timecreated'] = null; + $url['timemodified'] = $url->timemodified; + $url['sortorder'] = null; + $url['userid'] = null; + $url['author'] = null; + $url['license'] = null; + $contents[] = $url; + + return $contents; +} diff --git a/pluginfile.php b/pluginfile.php index 974d23e3094..fd187ceb9ed 100644 --- a/pluginfile.php +++ b/pluginfile.php @@ -34,753 +34,4 @@ require_once('lib/filelib.php'); $relativepath = get_file_argument(); $forcedownload = optional_param('forcedownload', 0, PARAM_BOOL); -// relative path must start with '/' -if (!$relativepath) { - print_error('invalidargorconf'); -} else if ($relativepath[0] != '/') { - print_error('pathdoesnotstartslash'); -} - -// extract relative path components -$args = explode('/', ltrim($relativepath, '/')); - -if (count($args) < 3) { // always at least context, component and filearea - print_error('invalidarguments'); -} - -$contextid = (int)array_shift($args); -$component = clean_param(array_shift($args), PARAM_COMPONENT); -$filearea = clean_param(array_shift($args), PARAM_AREA); - -list($context, $course, $cm) = get_context_info_array($contextid); - -$fs = get_file_storage(); - -// ======================================================================================================================== -if ($component === 'blog') { - // Blog file serving - if ($context->contextlevel != CONTEXT_SYSTEM) { - send_file_not_found(); - } - if ($filearea !== 'attachment' and $filearea !== 'post') { - send_file_not_found(); - } - - if (empty($CFG->bloglevel)) { - print_error('siteblogdisable', 'blog'); - } - - if ($CFG->bloglevel < BLOG_GLOBAL_LEVEL) { - require_login(); - if (isguestuser()) { - print_error('noguest'); - } - if ($CFG->bloglevel == BLOG_USER_LEVEL) { - if ($USER->id != $entry->userid) { - send_file_not_found(); - } - } - } - $entryid = (int)array_shift($args); - if (!$entry = $DB->get_record('post', array('module'=>'blog', 'id'=>$entryid))) { - send_file_not_found(); - } - - if ('publishstate' === 'public') { - if ($CFG->forcelogin) { - require_login(); - } - - } else if ('publishstate' === 'site') { - require_login(); - //ok - } else if ('publishstate' === 'draft') { - require_login(); - if ($USER->id != $entry->userid) { - send_file_not_found(); - } - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - - if (!$file = $fs->get_file($context->id, $component, $filearea, $entryid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - send_stored_file($file, 10*60, 0, true); // download MUST be forced - security! - -// ======================================================================================================================== -} else if ($component === 'grade') { - if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) { - // Global gradebook files - if ($CFG->forcelogin) { - require_login(); - } - - $fullpath = "/$context->id/$component/$filearea/".implode('/', $args); - - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) { - //TODO: nobody implemented this yet in grade edit form!! - send_file_not_found(); - - if ($CFG->forcelogin || $course->id != SITEID) { - require_login($course); - } - - $fullpath = "/$context->id/$component/$filearea/".implode('/', $args); - - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'tag') { - if ($filearea === 'description' and $context->contextlevel == CONTEXT_SYSTEM) { - - // All tag descriptions are going to be public but we still need to respect forcelogin - if ($CFG->forcelogin) { - require_login(); - } - - $fullpath = "/$context->id/tag/description/".implode('/', $args); - - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, true); - - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'calendar') { - if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_SYSTEM) { - - // All events here are public the one requirement is that we respect forcelogin - if ($CFG->forcelogin) { - require_login(); - } - - // Get the event if from the args array - $eventid = array_shift($args); - - // Load the event from the database - if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'eventtype'=>'site'))) { - send_file_not_found(); - } - // Check that we got an event and that it's userid is that of the user - - // Get the file and serve if successful - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_USER) { - - // Must be logged in, if they are not then they obviously can't be this user - require_login(); - - // Don't want guests here, potentially saves a DB call - if (isguestuser()) { - send_file_not_found(); - } - - // Get the event if from the args array - $eventid = array_shift($args); - - // Load the event from the database - user id must match - if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'userid'=>$USER->id, 'eventtype'=>'user'))) { - send_file_not_found(); - } - - // Get the file and serve if successful - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'event_description' and $context->contextlevel == CONTEXT_COURSE) { - - // Respect forcelogin and require login unless this is the site.... it probably - // should NEVER be the site - if ($CFG->forcelogin || $course->id != SITEID) { - require_login($course); - } - - // Must be able to at least view the course - if (!is_enrolled($context) and !is_viewing($context)) { - //TODO: hmm, do we really want to block guests here? - send_file_not_found(); - } - - // Get the event id - $eventid = array_shift($args); - - // Load the event from the database we need to check whether it is - // a) valid course event - // b) a group event - // Group events use the course context (there is no group context) - if (!$event = $DB->get_record('event', array('id'=>(int)$eventid, 'courseid'=>$course->id))) { - send_file_not_found(); - } - - // If its a group event require either membership of view all groups capability - if ($event->eventtype === 'group') { - if (!has_capability('moodle/site:accessallgroups', $context) && !groups_is_member($event->groupid, $USER->id)) { - send_file_not_found(); - } - } else if ($event->eventtype === 'course') { - //ok - } else { - // some other type - send_file_not_found(); - } - - // If we get this far we can serve the file - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, $component, $filearea, $eventid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'user') { - if ($filearea === 'icon' and $context->contextlevel == CONTEXT_USER) { - $redirect = false; - if (count($args) == 1) { - $themename = theme_config::DEFAULT_THEME; - $filename = array_shift($args); - } else { - $themename = array_shift($args); - $filename = array_shift($args); - } - if ((!empty($CFG->forcelogin) and !isloggedin())) { - // protect images if login required and not logged in; - // do not use require_login() because it is expensive and not suitable here anyway - $redirect = true; - } - if (!$redirect and ($filename !== 'f1' and $filename !== 'f2')) { - $filename = 'f1'; - $redirect = true; - } - if (!$redirect && !$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) { - if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.jpg')) { - $redirect = true; - } - } - if ($redirect) { - $theme = theme_config::load($themename); - redirect($theme->pix_url('u/'.$filename, 'moodle')); - } - send_stored_file($file, 60*60*24); // enable long caching, there are many images on each page - - } else if ($filearea === 'private' and $context->contextlevel == CONTEXT_USER) { - require_login(); - - if (isguestuser()) { - send_file_not_found(); - } - - if ($USER->id !== $context->instanceid) { - send_file_not_found(); - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! - - } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_USER) { - - if ($CFG->forcelogin) { - require_login(); - } - - $userid = $context->instanceid; - - if ($USER->id == $userid) { - // always can access own - - } else if (!empty($CFG->forceloginforprofiles)) { - require_login(); - - if (isguestuser()) { - send_file_not_found(); - } - - // we allow access to site profile of all course contacts (usually teachers) - if (!has_coursecontact_role($userid) && !has_capability('moodle/user:viewdetails', $context)) { - send_file_not_found(); - } - - $canview = false; - if (has_capability('moodle/user:viewdetails', $context)) { - $canview = true; - } else { - $courses = enrol_get_my_courses(); - } - - while (!$canview && count($courses) > 0) { - $course = array_shift($courses); - if (has_capability('moodle/user:viewdetails', get_context_instance(CONTEXT_COURSE, $course->id))) { - $canview = true; - } - } - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, $component, $filearea, 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! - - } else if ($filearea === 'profile' and $context->contextlevel == CONTEXT_COURSE) { - $userid = (int)array_shift($args); - $usercontext = get_context_instance(CONTEXT_USER, $userid); - - if ($CFG->forcelogin) { - require_login(); - } - - if (!empty($CFG->forceloginforprofiles)) { - require_login(); - if (isguestuser()) { - print_error('noguest'); - } - - //TODO: review this logic of user profile access prevention - if (!has_coursecontact_role($userid) and !has_capability('moodle/user:viewdetails', $usercontext)) { - print_error('usernotavailable'); - } - if (!has_capability('moodle/user:viewdetails', $context) && !has_capability('moodle/user:viewdetails', $usercontext)) { - print_error('cannotviewprofile'); - } - if (!is_enrolled($context, $userid)) { - print_error('notenrolledprofile'); - } - if (groups_get_course_groupmode($course) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) { - print_error('groupnotamember'); - } - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($usercontext->id, 'user', 'profile', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! - - } else if ($filearea === 'backup' and $context->contextlevel == CONTEXT_USER) { - require_login(); - - if (isguestuser()) { - send_file_not_found(); - } - $userid = $context->instanceid; - - if ($USER->id != $userid) { - send_file_not_found(); - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'user', 'backup', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, true); // must force download - security! - - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'coursecat') { - if ($context->contextlevel != CONTEXT_COURSECAT) { - send_file_not_found(); - } - - if ($filearea === 'description') { - if ($CFG->forcelogin) { - // no login necessary - unless login forced everywhere - require_login(); - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'coursecat', 'description', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'course') { - if ($context->contextlevel != CONTEXT_COURSE) { - send_file_not_found(); - } - - if ($filearea === 'summary') { - if ($CFG->forcelogin) { - require_login(); - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'course', 'summary', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'section') { - if ($CFG->forcelogin) { - require_login($course); - } else if ($course->id != SITEID) { - require_login($course); - } - - $sectionid = (int)array_shift($args); - - if (!$section = $DB->get_record('course_sections', array('id'=>$sectionid, 'course'=>$course->id))) { - send_file_not_found(); - } - - if ($course->numsections < $section->section) { - if (!has_capability('moodle/course:update', $context)) { - // block access to unavailable sections if can not edit course - send_file_not_found(); - } - } - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'course', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else { - send_file_not_found(); - } - -} else if ($component === 'group') { - if ($context->contextlevel != CONTEXT_COURSE) { - send_file_not_found(); - } - - require_course_login($course, true, null, false); - - $groupid = (int)array_shift($args); - - $group = $DB->get_record('groups', array('id'=>$groupid, 'courseid'=>$course->id), '*', MUST_EXIST); - if (($course->groupmodeforce and $course->groupmode == SEPARATEGROUPS) and !has_capability('moodle/site:accessallgroups', $context) and !groups_is_member($group->id, $USER->id)) { - // do not allow access to separate group info if not member or teacher - send_file_not_found(); - } - - if ($filearea === 'description') { - - require_login($course); - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'group', 'description', $group->id, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'icon') { - $filename = array_pop($args); - - if ($filename !== 'f1' and $filename !== 'f2') { - send_file_not_found(); - } - if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.png')) { - if (!$file = $fs->get_file($context->id, 'group', 'icon', $group->id, '/', $filename.'.jpg')) { - send_file_not_found(); - } - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60); - - } else { - send_file_not_found(); - } - -} else if ($component === 'grouping') { - if ($context->contextlevel != CONTEXT_COURSE) { - send_file_not_found(); - } - - require_login($course); - - $groupingid = (int)array_shift($args); - - // note: everybody has access to grouping desc images for now - if ($filearea === 'description') { - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'grouping', 'description', $groupingid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'backup') { - if ($filearea === 'course' and $context->contextlevel == CONTEXT_COURSE) { - require_login($course); - require_capability('moodle/backup:downloadfile', $context); - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'backup', 'course', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, $forcedownload); - - } else if ($filearea === 'section' and $context->contextlevel == CONTEXT_COURSE) { - require_login($course); - require_capability('moodle/backup:downloadfile', $context); - - $sectionid = (int)array_shift($args); - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'backup', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'activity' and $context->contextlevel == CONTEXT_MODULE) { - require_login($course, false, $cm); - require_capability('moodle/backup:downloadfile', $context); - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'backup', 'activity', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); - send_stored_file($file, 60*60, 0, $forcedownload); - - } else if ($filearea === 'automated' and $context->contextlevel == CONTEXT_COURSE) { - // Backup files that were generated by the automated backup systems. - - require_login($course); - require_capability('moodle/site:config', $context); - - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'backup', 'automated', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 0, 0, $forcedownload); - - } else { - send_file_not_found(); - } - -// ======================================================================================================================== -} else if ($component === 'question') { - require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'question', $filearea, $args, $forcedownload); - send_file_not_found(); - -// ======================================================================================================================== -} else if ($component === 'grading') { - if ($filearea === 'description') { - // files embedded into the form definition description - - if ($context->contextlevel == CONTEXT_SYSTEM) { - require_login(); - - } else if ($context->contextlevel >= CONTEXT_COURSE) { - require_login($course, false, $cm); - - } else { - send_file_not_found(); - } - - $formid = (int)array_shift($args); - - $sql = "SELECT ga.id - FROM {grading_areas} ga - JOIN {grading_definitions} gd ON (gd.areaid = ga.id) - WHERE gd.id = ? AND ga.contextid = ?"; - $areaid = $DB->get_field_sql($sql, array($formid, $context->id), IGNORE_MISSING); - - if (!$areaid) { - send_file_not_found(); - } - - $fullpath = "/$context->id/$component/$filearea/$formid/".implode('/', $args); - - if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { - send_file_not_found(); - } - - session_get_instance()->write_close(); // unlock session during fileserving - send_stored_file($file, 60*60, 0, $forcedownload); - } - -// ======================================================================================================================== -} else if (strpos($component, 'mod_') === 0) { - $modname = substr($component, 4); - if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) { - send_file_not_found(); - } - require_once("$CFG->dirroot/mod/$modname/lib.php"); - - if ($context->contextlevel == CONTEXT_MODULE) { - if ($cm->modname !== $modname) { - // somebody tries to gain illegal access, cm type must match the component! - send_file_not_found(); - } - } - - if ($filearea === 'intro') { - if (!plugin_supports('mod', $modname, FEATURE_MOD_INTRO, true)) { - send_file_not_found(); - } - require_course_login($course, true, $cm); - - // all users may access it - $filename = array_pop($args); - $filepath = $args ? '/'.implode('/', $args).'/' : '/'; - if (!$file = $fs->get_file($context->id, 'mod_'.$modname, 'intro', 0, $filepath, $filename) or $file->is_directory()) { - send_file_not_found(); - } - - $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400; - - // finally send the file - send_stored_file($file, $lifetime, 0); - } - - $filefunction = $component.'_pluginfile'; - $filefunctionold = $modname.'_pluginfile'; - if (function_exists($filefunction)) { - // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunction($course, $cm, $context, $filearea, $args, $forcedownload); - } else if (function_exists($filefunctionold)) { - // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunctionold($course, $cm, $context, $filearea, $args, $forcedownload); - } - - send_file_not_found(); - -// ======================================================================================================================== -} else if (strpos($component, 'block_') === 0) { - $blockname = substr($component, 6); - // note: no more class methods in blocks please, that is .... - if (!file_exists("$CFG->dirroot/blocks/$blockname/lib.php")) { - send_file_not_found(); - } - require_once("$CFG->dirroot/blocks/$blockname/lib.php"); - - if ($context->contextlevel == CONTEXT_BLOCK) { - $birecord = $DB->get_record('block_instances', array('id'=>$context->instanceid), '*',MUST_EXIST); - if ($birecord->blockname !== $blockname) { - // somebody tries to gain illegal access, cm type must match the component! - send_file_not_found(); - } - } else { - $birecord = null; - } - - $filefunction = $component.'_pluginfile'; - if (function_exists($filefunction)) { - // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunction($course, $birecord, $context, $filearea, $args, $forcedownload); - } - - send_file_not_found(); - -} else if (strpos($component, '_') === false) { - // all core subsystems have to be specified above, no more guessing here! - send_file_not_found(); - -} else { - // try to serve general plugin file in arbitrary context - $dir = get_component_directory($component); - if (!file_exists("$dir/lib.php")) { - send_file_not_found(); - } - include_once("$dir/lib.php"); - - $filefunction = $component.'_pluginfile'; - if (function_exists($filefunction)) { - // if the function exists, it must send the file and terminate. Whatever it returns leads to "not found" - $filefunction($course, $cm, $context, $filearea, $args, $forcedownload); - } - - send_file_not_found(); -} +file_pluginfile($relativepath, $forcedownload); diff --git a/webservice/pluginfile.php b/webservice/pluginfile.php new file mode 100644 index 00000000000..3c9a60e158c --- /dev/null +++ b/webservice/pluginfile.php @@ -0,0 +1,119 @@ +. + +/** + * A script to serve files from web service client + * + * @package core + * @subpackage file + * @copyright 2011 Dongsheng Cai + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define('NO_MOODLE_COOKIES', true); +require_once(dirname(dirname(__FILE__)) . '/config.php'); +require_once($CFG->libdir . '/filelib.php'); + +$relativepath = get_file_argument(); +$token = required_param('token', PARAM_ALPHANUM); + +// web service must be enabled to use this script +if (!$CFG->enablewebservices) { + print_error('enablewsdescription', 'webservice'); +} + +// Obtain token record +if (!$token = $DB->get_record('external_tokens', array('token'=>$token))) { + print_error('invalidtoken', 'webservice'); +} + +//retrieve web service record +$servicesql = 'SELECT s.* + FROM {external_services} s, {external_tokens} t + WHERE t.externalserviceid = s.id + AND t.token = ? AND t.userid = ? AND s.enabled = 1'; +$service = $DB->get_record_sql($servicesql, array($token->token, $token->userid), MUST_EXIST); + +$enabledfiledownload = (int)$service->downloadfiles; + +if (empty($enabledfiledownload)) { + print_error('enabledirectdownload', 'webservice'); +} + +$user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST); + +//Non admin can not authenticate if maintenance mode +$hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user); +if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) { + print_error('sitemaintenance', 'admin'); +} + +// Validate token date +if ($token->validuntil and $token->validuntil < time()) { + add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('invalidtimedtoken', 'webservice'), 0); + $DB->delete_records('external_tokens', array('token'=>$token->token)); + print_error('invalidtimedtoken', 'webservice'); +} + +//assumes that if sid is set then there must be a valid associated session no matter the token type +if ($token->sid) { + $session = session_get_instance(); + if (!$session->session_exists($token->sid)) { + $DB->delete_records('external_tokens', array('sid'=>$token->sid)); + print_error('invalidtokensession', 'webservice'); + } +} + +// Check ip +if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { + add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr(), 0); + print_error('invalidiptoken', 'webservice'); +} + +//only confirmed user should be able to call web service +if (empty($user->confirmed)) { + add_to_log(SITEID, 'webservice', 'user unconfirmed', '', $user->username); + print_error('usernotconfirmed', 'moodle', '', $user->username); +} + +//check the user is suspended +if (!empty($user->suspended)) { + add_to_log(SITEID, 'webservice', 'user suspended', '', $user->username); + print_error('usersuspended', 'webservice'); +} + +//check if the auth method is nologin (in this case refuse connection) +if ($user->auth == 'nologin') { + add_to_log(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username); + print_error('nologinauth', 'webservice'); +} + +$auth = get_auth_plugin($user->auth); + +if (!empty($auth->config->expiration) and $auth->config->expiration == 1) { + $days2expire = $auth->password_expire($user->username); + if (intval($days2expire) < 0 ) { + add_to_log(SITEID, 'webservice', 'expired password', '', $user->username); + print_error('passwordisexpired', 'webservice'); + } +} + +// log token access +$DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id)); +session_set_user($user); + +file_pluginfile($relativepath, 0); diff --git a/webservice/simpletest/testwebservice.php b/webservice/simpletest/testwebservice.php index 629f312d43d..b59c3c49fa8 100644 --- a/webservice/simpletest/testwebservice.php +++ b/webservice/simpletest/testwebservice.php @@ -74,7 +74,8 @@ class webservice_test extends UnitTestCase { 'moodle_enrol_get_enrolled_users' => false, 'moodle_group_get_course_groups' => false, 'moodle_group_get_groupmembers' => false, - 'moodle_webservice_get_siteinfo' => false + 'moodle_webservice_get_siteinfo' => false, + 'core_course_get_contents' => false ); ////// WRITE DB tests //// @@ -247,6 +248,29 @@ class webservice_test extends UnitTestCase { $this->assertEqual(count($users), count($userids)); } + function core_course_get_contents($client) { + global $DB, $CFG; + $dbcourses = $DB->get_records('course'); + $function = 'core_course_get_contents'; + + $coursecontents = array(); + + foreach ($dbcourses as $dbcourse) { + $params = array('courseid' => $dbcourse->id); + + if (file_exists($CFG->dirroot . '/' . '/course/format/' . $dbcourse->format . '/lib.php')) { + $coursecontents = $client->call($function, $params); + } + + //TODO: some unit tests to check that generated course content data test match what + // the web service function is returning. + + //Realistic TODO: display the content of $coursecontents in your php log and check if you obtain + //what you are expecting + //varlog($coursecontents); + } + } + /** * This test will: * 1- create a user (core call) diff --git a/webservice/upload.php b/webservice/upload.php index 874b03ef1ae..30e288d1115 100644 --- a/webservice/upload.php +++ b/webservice/upload.php @@ -30,6 +30,12 @@ $filepath = optional_param('filepath', '/', PARAM_PATH); echo $OUTPUT->header(); +//Non admin can not authenticate if maintenance mode +$hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user); +if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) { + throw new moodle_exception('sitemaintenance', 'admin'); +} + // web service must be enabled to use this script if (!$CFG->enablewebservices) { throw new moodle_exception('enablewsdescription', 'webservice'); @@ -63,6 +69,35 @@ if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprest $user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST); +//check if the auth method is nologin (in this case refuse connection) +if ($auth=='nologin') { + add_to_log(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username); + throw new webservice_access_exception(get_string('nologinauth', 'webservice')); +} + +//only confirmed user should be able to call web service +if (empty($user->confirmed)) { + add_to_log(SITEID, 'webservice', 'user unconfirmed', '', $user->username); + throw new webservice_access_exception(get_string('usernotconfirmed', 'moodle', $user->username)); +} + +//check the user is suspended +if (!empty($user->suspended)) { + add_to_log(SITEID, 'webservice', 'user suspended', '', $user->username); + throw new webservice_access_exception(get_string('usersuspended', 'webservice')); +} + +// check if credentials have expired +$auth = get_auth_plugin($user->auth); + +if (!empty($auth->config->expiration) and $auth->config->expiration == 1) { + $days2expire = $auth->password_expire($user->username); + if (intval($days2expire) < 0 ) { + add_to_log(SITEID, 'webservice', 'expired password', '', $user->username); + throw new webservice_access_exception(get_string('passwordisexpired', 'webservice')); + } +} + // log token access $DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id)); From 07cc3d11e2081364d5d33f4f9cec5c30b62a53a5 Mon Sep 17 00:00:00 2001 From: Jerome Mouneyrac Date: Tue, 29 Nov 2011 11:18:36 +0800 Subject: [PATCH 2/2] MDL-28646 add missing authentication web service checks. Merge download/upload script checks in the same lib functions. Make the download scrit return json error message. Add missing webservice lang. Minor unit test doc improvement. --- lang/en/webservice.php | 1 + webservice/lib.php | 118 +++++++++++++++++++++++ webservice/pluginfile.php | 93 ++---------------- webservice/simpletest/testwebservice.php | 7 +- webservice/upload.php | 82 ++-------------- 5 files changed, 138 insertions(+), 163 deletions(-) diff --git a/lang/en/webservice.php b/lang/en/webservice.php index 7d04b9cf0e5..ea16baab885 100644 --- a/lang/en/webservice.php +++ b/lang/en/webservice.php @@ -162,6 +162,7 @@ $string['selectspecificuserdescription'] = 'Add the web services user as an auth $string['service'] = 'Service'; $string['servicehelpexplanation'] = 'A service is a set of functions. A service can be accessed by all users or just specified users.'; $string['servicename'] = 'Service name'; +$string['servicenotavailable'] = 'the web service is not available (it does not exist or it is disabled)'; $string['servicesbuiltin'] = 'Built-in services'; $string['servicescustom'] = 'Custom services'; $string['serviceusers'] = 'Authorised users'; diff --git a/webservice/lib.php b/webservice/lib.php index 86a3f4fa8a8..ecb6a9b1db9 100644 --- a/webservice/lib.php +++ b/webservice/lib.php @@ -34,6 +34,124 @@ define('WEBSERVICE_AUTHMETHOD_SESSION_TOKEN', 2); */ class webservice { + /** + * Authenticate user (used by download/upload file scripts) + * @param string $token + * @return array - contains the authenticated user, token and service objects + */ + public function authenticate_user($token) { + global $DB, $CFG; + + // web service must be enabled to use this script + if (!$CFG->enablewebservices) { + throw new webservice_access_exception(get_string('enablewsdescription', 'webservice')); + } + + // Obtain token record + if (!$token = $DB->get_record('external_tokens', array('token' => $token))) { + throw new webservice_access_exception(get_string('invalidtoken', 'webservice')); + } + + // Validate token date + if ($token->validuntil and $token->validuntil < time()) { + add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '', get_string('invalidtimedtoken', 'webservice'), 0); + $DB->delete_records('external_tokens', array('token' => $token->token)); + throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice')); + } + + // Check ip + if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { + add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '', get_string('failedtolog', 'webservice') . ": " . getremoteaddr(), 0); + throw new webservice_access_exception(get_string('invalidiptoken', 'webservice')); + } + + //retrieve user link to the token + $user = $DB->get_record('user', array('id' => $token->userid, 'deleted' => 0), '*', MUST_EXIST); + + // let enrol plugins deal with new enrolments if necessary + enrol_check_plugins($user); + + // setup user session to check capability + session_set_user($user); + + //assumes that if sid is set then there must be a valid associated session no matter the token type + if ($token->sid) { + $session = session_get_instance(); + if (!$session->session_exists($token->sid)) { + $DB->delete_records('external_tokens', array('sid' => $token->sid)); + throw new webservice_access_exception(get_string('invalidtokensession', 'webservice')); + } + } + + //Non admin can not authenticate if maintenance mode + $hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user); + if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) { + throw new webservice_access_exception(get_string('sitemaintenance', 'admin')); + } + + //retrieve web service record + $service = $DB->get_record('external_services', array('id' => $token->externalserviceid, 'enabled' => 1)); + if (empty($service)) { + // will throw exception if no token found + throw new webservice_access_exception(get_string('servicenotavailable', 'webservice')); + } + + //check if there is any required system capability + if ($service->requiredcapability and !has_capability($service->requiredcapability, get_context_instance(CONTEXT_SYSTEM), $user)) { + throw new webservice_access_exception(get_string('missingrequiredcapability', 'webservice', $service->requiredcapability)); + } + + //specific checks related to user restricted service + if ($service->restrictedusers) { + $authoriseduser = $DB->get_record('external_services_users', array('externalserviceid' => $service->id, 'userid' => $user->id)); + + if (empty($authoriseduser)) { + throw new webservice_access_exception(get_string('usernotallowed', 'webservice', $service->name)); + } + + if (!empty($authoriseduser->validuntil) and $authoriseduser->validuntil < time()) { + throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice')); + } + + if (!empty($authoriseduser->iprestriction) and !address_in_subnet(getremoteaddr(), $authoriseduser->iprestriction)) { + throw new webservice_access_exception(get_string('invalidiptoken', 'webservice')); + } + } + + //only confirmed user should be able to call web service + if (empty($user->confirmed)) { + add_to_log(SITEID, 'webservice', 'user unconfirmed', '', $user->username); + throw new webservice_access_exception(get_string('usernotconfirmed', 'moodle', $user->username)); + } + + //check the user is suspended + if (!empty($user->suspended)) { + add_to_log(SITEID, 'webservice', 'user suspended', '', $user->username); + throw new webservice_access_exception(get_string('usersuspended', 'webservice')); + } + + //check if the auth method is nologin (in this case refuse connection) + if ($user->auth == 'nologin') { + add_to_log(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username); + throw new webservice_access_exception(get_string('nologinauth', 'webservice')); + } + + //Check if the user password is expired + $auth = get_auth_plugin($user->auth); + if (!empty($auth->config->expiration) and $auth->config->expiration == 1) { + $days2expire = $auth->password_expire($user->username); + if (intval($days2expire) < 0) { + add_to_log(SITEID, 'webservice', 'expired password', '', $user->username); + throw new webservice_access_exception(get_string('passwordisexpired', 'webservice')); + } + } + + // log token access + $DB->set_field('external_tokens', 'lastaccess', time(), array('id' => $token->id)); + + return array('user' => $user, 'token' => $token, 'service' => $service); + } + /** * Add a user to the list of authorised user of a given service * @param object $user diff --git a/webservice/pluginfile.php b/webservice/pluginfile.php index 3c9a60e158c..186e36e709f 100644 --- a/webservice/pluginfile.php +++ b/webservice/pluginfile.php @@ -24,96 +24,23 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +define('AJAX_SCRIPT', true); define('NO_MOODLE_COOKIES', true); require_once(dirname(dirname(__FILE__)) . '/config.php'); require_once($CFG->libdir . '/filelib.php'); +require_once($CFG->dirroot . '/webservice/lib.php'); -$relativepath = get_file_argument(); +//authenticate the user $token = required_param('token', PARAM_ALPHANUM); +$webservicelib = new webservice(); +$authenticationinfo = $webservicelib->authenticate_user($token); -// web service must be enabled to use this script -if (!$CFG->enablewebservices) { - print_error('enablewsdescription', 'webservice'); -} - -// Obtain token record -if (!$token = $DB->get_record('external_tokens', array('token'=>$token))) { - print_error('invalidtoken', 'webservice'); -} - -//retrieve web service record -$servicesql = 'SELECT s.* - FROM {external_services} s, {external_tokens} t - WHERE t.externalserviceid = s.id - AND t.token = ? AND t.userid = ? AND s.enabled = 1'; -$service = $DB->get_record_sql($servicesql, array($token->token, $token->userid), MUST_EXIST); - -$enabledfiledownload = (int)$service->downloadfiles; - +//check the service allows file download +$enabledfiledownload = (int) ($authenticationinfo['service']->downloadfiles); if (empty($enabledfiledownload)) { - print_error('enabledirectdownload', 'webservice'); + throw new webservice_access_exception(get_string('enabledirectdownload', 'webservice')); } -$user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST); - -//Non admin can not authenticate if maintenance mode -$hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user); -if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) { - print_error('sitemaintenance', 'admin'); -} - -// Validate token date -if ($token->validuntil and $token->validuntil < time()) { - add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('invalidtimedtoken', 'webservice'), 0); - $DB->delete_records('external_tokens', array('token'=>$token->token)); - print_error('invalidtimedtoken', 'webservice'); -} - -//assumes that if sid is set then there must be a valid associated session no matter the token type -if ($token->sid) { - $session = session_get_instance(); - if (!$session->session_exists($token->sid)) { - $DB->delete_records('external_tokens', array('sid'=>$token->sid)); - print_error('invalidtokensession', 'webservice'); - } -} - -// Check ip -if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { - add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr(), 0); - print_error('invalidiptoken', 'webservice'); -} - -//only confirmed user should be able to call web service -if (empty($user->confirmed)) { - add_to_log(SITEID, 'webservice', 'user unconfirmed', '', $user->username); - print_error('usernotconfirmed', 'moodle', '', $user->username); -} - -//check the user is suspended -if (!empty($user->suspended)) { - add_to_log(SITEID, 'webservice', 'user suspended', '', $user->username); - print_error('usersuspended', 'webservice'); -} - -//check if the auth method is nologin (in this case refuse connection) -if ($user->auth == 'nologin') { - add_to_log(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username); - print_error('nologinauth', 'webservice'); -} - -$auth = get_auth_plugin($user->auth); - -if (!empty($auth->config->expiration) and $auth->config->expiration == 1) { - $days2expire = $auth->password_expire($user->username); - if (intval($days2expire) < 0 ) { - add_to_log(SITEID, 'webservice', 'expired password', '', $user->username); - print_error('passwordisexpired', 'webservice'); - } -} - -// log token access -$DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id)); -session_set_user($user); - +//finally we can serve the file :) +$relativepath = get_file_argument(); file_pluginfile($relativepath, 0); diff --git a/webservice/simpletest/testwebservice.php b/webservice/simpletest/testwebservice.php index b59c3c49fa8..1b4e3e895e6 100644 --- a/webservice/simpletest/testwebservice.php +++ b/webservice/simpletest/testwebservice.php @@ -262,12 +262,9 @@ class webservice_test extends UnitTestCase { $coursecontents = $client->call($function, $params); } - //TODO: some unit tests to check that generated course content data test match what - // the web service function is returning. - - //Realistic TODO: display the content of $coursecontents in your php log and check if you obtain + //Display the content of $coursecontents in your php log and check if you obtain //what you are expecting - //varlog($coursecontents); + //error_log(print_r($coursecontents, true)); } } diff --git a/webservice/upload.php b/webservice/upload.php index 30e288d1115..6219e377be8 100644 --- a/webservice/upload.php +++ b/webservice/upload.php @@ -25,85 +25,17 @@ define('AJAX_SCRIPT', true); define('NO_MOODLE_COOKIES', true); require_once(dirname(dirname(__FILE__)) . '/config.php'); -$token = required_param('token', PARAM_ALPHANUM); +require_once($CFG->dirroot . '/webservice/lib.php'); $filepath = optional_param('filepath', '/', PARAM_PATH); echo $OUTPUT->header(); -//Non admin can not authenticate if maintenance mode -$hassiteconfig = has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM), $user); -if (!empty($CFG->maintenance_enabled) and !$hassiteconfig) { - throw new moodle_exception('sitemaintenance', 'admin'); -} +//authenticate the user +$token = required_param('token', PARAM_ALPHANUM); +$webservicelib = new webservice(); +$authenticationinfo = $webservicelib->authenticate_user($token); -// web service must be enabled to use this script -if (!$CFG->enablewebservices) { - throw new moodle_exception('enablewsdescription', 'webservice'); -} -// Obtain token record -if (!$token = $DB->get_record('external_tokens', array('token'=>$token))) { - throw new webservice_access_exception(get_string('invalidtoken', 'webservice')); -} - -// Validate token date -if ($token->validuntil and $token->validuntil < time()) { - add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('invalidtimedtoken', 'webservice'), 0); - $DB->delete_records('external_tokens', array('token'=>$token->token)); - throw new webservice_access_exception(get_string('invalidtimedtoken', 'webservice')); -} - -//assumes that if sid is set then there must be a valid associated session no matter the token type -if ($token->sid) { - $session = session_get_instance(); - if (!$session->session_exists($token->sid)) { - $DB->delete_records('external_tokens', array('sid'=>$token->sid)); - throw new webservice_access_exception(get_string('invalidtokensession', 'webservice')); - } -} - -// Check ip -if ($token->iprestriction and !address_in_subnet(getremoteaddr(), $token->iprestriction)) { - add_to_log(SITEID, 'webservice', get_string('tokenauthlog', 'webservice'), '' , get_string('failedtolog', 'webservice').": ".getremoteaddr(), 0); - throw new webservice_access_exception(get_string('invalidiptoken', 'webservice')); -} - -$user = $DB->get_record('user', array('id'=>$token->userid, 'deleted'=>0), '*', MUST_EXIST); - -//check if the auth method is nologin (in this case refuse connection) -if ($auth=='nologin') { - add_to_log(SITEID, 'webservice', 'nologin auth attempt with web service', '', $user->username); - throw new webservice_access_exception(get_string('nologinauth', 'webservice')); -} - -//only confirmed user should be able to call web service -if (empty($user->confirmed)) { - add_to_log(SITEID, 'webservice', 'user unconfirmed', '', $user->username); - throw new webservice_access_exception(get_string('usernotconfirmed', 'moodle', $user->username)); -} - -//check the user is suspended -if (!empty($user->suspended)) { - add_to_log(SITEID, 'webservice', 'user suspended', '', $user->username); - throw new webservice_access_exception(get_string('usersuspended', 'webservice')); -} - -// check if credentials have expired -$auth = get_auth_plugin($user->auth); - -if (!empty($auth->config->expiration) and $auth->config->expiration == 1) { - $days2expire = $auth->password_expire($user->username); - if (intval($days2expire) < 0 ) { - add_to_log(SITEID, 'webservice', 'expired password', '', $user->username); - throw new webservice_access_exception(get_string('passwordisexpired', 'webservice')); - } -} - -// log token access -$DB->set_field('external_tokens', 'lastaccess', time(), array('id'=>$token->id)); - -// let enrol plugins deal with new enrolments if necessary -enrol_check_plugins($user); -session_set_user($user); +//check the user can manage his own files (can upload) $context = get_context_instance(CONTEXT_USER, $USER->id); require_capability('moodle/user:manageownfiles', $context); @@ -183,7 +115,7 @@ foreach ($files as $file) { $file_record->filepath = $filepath; $file_record->itemid = 0; $file_record->license = $CFG->sitedefaultlicense; - $file_record->author = fullname($user);; + $file_record->author = fullname($authenticationinfo['user']);; $file_record->source = ''; $stored_file = $fs->create_file_from_pathname($file_record, $file->filepath); $results[] = $file_record;