diff --git a/repository/lib.php b/repository/lib.php index 62312099d58..0886ccb93fb 100644 --- a/repository/lib.php +++ b/repository/lib.php @@ -651,45 +651,60 @@ abstract class repository { * @return stored_file|null */ public static function get_moodle_file($source) { - $params = unserialize(base64_decode($source)); - if (empty($params) || !is_array($params)) { - return null; - } - foreach (array('contextid', 'itemid', 'filename', 'filepath', 'component') as $key) { - if (!array_key_exists($key, $params)) { - return null; - } - } - $contextid = clean_param($params['contextid'], PARAM_INT); - $component = clean_param($params['component'], PARAM_COMPONENT); - $filearea = clean_param($params['filearea'], PARAM_AREA); - $itemid = clean_param($params['itemid'], PARAM_INT); - $filepath = clean_param($params['filepath'], PARAM_PATH); - $filename = clean_param($params['filename'], PARAM_FILE); + $params = file_storage::unpack_reference($source, true); $fs = get_file_storage(); - return $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename); + return $fs->get_file($params['contextid'], $params['component'], $params['filearea'], + $params['itemid'], $params['filepath'], $params['filename']); } /** - * This function is used to copy a moodle file to draft area + * Repository method to make sure that user can access particular file. * - * @param string $encoded The metainfo of file, it is base64 encoded php serialized data + * This is checked when user tries to pick the file from repository to deal with + * potential parameter substitutions is request + * + * @param string $source + * @return bool whether the file is accessible by current user + */ + public function file_is_accessible($source) { + if ($this->has_moodle_files()) { + try { + $params = file_storage::unpack_reference($source, true); + } catch (file_reference_exception $e) { + return false; + } + $browser = get_file_browser(); + $context = get_context_instance_by_id($params['contextid']); + $file_info = $browser->get_file_info($context, $params['component'], $params['filearea'], + $params['itemid'], $params['filepath'], $params['filename']); + return !empty($file_info); + } + return true; + } + + /** + * This function is used to copy a moodle file to draft area. + * + * It DOES NOT check if the user is allowed to access this file because the actual file + * can be located in the area where user does not have access to but there is an alias + * to this file in the area where user CAN access it. + * {@link file_is_accessible} should be called for alias location before calling this function. + * + * @param string $source The metainfo of file, it is base64 encoded php serialized data * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other * attributes of the new file * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds * the limit, the file_exception is thrown. - * @return array The information of file + * @return array The information about the created file */ - public function copy_to_area($encoded, $filerecord, $maxbytes = -1) { + public function copy_to_area($source, $filerecord, $maxbytes = -1) { global $USER; $fs = get_file_storage(); - $browser = get_file_browser(); if ($this->has_moodle_files() == false) { throw new coding_exception('Only repository used to browse moodle files can use repository::copy_to_area()'); } - $params = unserialize(base64_decode($encoded)); $user_context = context_user::instance($USER->id); $filerecord = (array)$filerecord; @@ -701,17 +716,9 @@ abstract class repository { $new_filepath = $filerecord['filepath']; $new_filename = $filerecord['filename']; - $contextid = clean_param($params['contextid'], PARAM_INT); - $fileitemid = clean_param($params['itemid'], PARAM_INT); - $filename = clean_param($params['filename'], PARAM_FILE); - $filepath = clean_param($params['filepath'], PARAM_PATH);; - $filearea = clean_param($params['filearea'], PARAM_AREA); - $component = clean_param($params['component'], PARAM_COMPONENT); - - $context = get_context_instance_by_id($contextid); // the file needs to copied to draft area - $file_info = $browser->get_file_info($context, $component, $filearea, $fileitemid, $filepath, $filename); - if ($maxbytes !== -1 && $file_info->get_filesize() > $maxbytes) { + $stored_file = self::get_moodle_file($source); + if ($maxbytes != -1 && $stored_file->get_filesize() > $maxbytes) { throw new file_exception('maxbytes'); } @@ -719,7 +726,7 @@ abstract class repository { // create new file $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename); $filerecord['filename'] = $unused_filename; - $file_info->copy_to_storage($filerecord); + $fs->create_file_from_storedfile($filerecord, $stored_file); $event = array(); $event['event'] = 'fileexists'; $event['newfile'] = new stdClass; @@ -729,17 +736,17 @@ abstract class repository { $event['existingfile'] = new stdClass; $event['existingfile']->filepath = $new_filepath; $event['existingfile']->filename = $new_filename; - $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; + $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out(); return $event; } else { - $file_info->copy_to_storage($filerecord); + $fs->create_file_from_storedfile($filerecord, $stored_file); $info = array(); $info['itemid'] = $draftitemid; - $info['file'] = $new_filename; - $info['title'] = $new_filename; + $info['file'] = $new_filename; + $info['title'] = $new_filename; $info['contextid'] = $user_context->id; - $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; - $info['filesize'] = $file_info->get_filesize(); + $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out(); + $info['filesize'] = $stored_file->get_filesize(); return $info; } } diff --git a/repository/recent/lib.php b/repository/recent/lib.php index 2c1acf23579..f1265c84add 100644 --- a/repository/recent/lib.php +++ b/repository/recent/lib.php @@ -177,91 +177,22 @@ class repository_recent extends repository { public function supported_returntypes() { return FILE_INTERNAL; } + /** - * This function overwrite the default implement to copying file using file_storage + * Repository method to make sure that user can access particular file. * - * @param string $encoded The information of file, it is base64 encoded php serialized data - * @param stdClass|array $filerecord contains itemid, filepath, filename and optionally other - * attributes of the new file - * @param int $maxbytes maximum allowed size of file, -1 if unlimited. If size of file exceeds - * the limit, the file_exception is thrown. - * @return array The information of file + * This is checked when user tries to pick the file from repository to deal with + * potential parameter substitutions is request + * + * @todo MDL-33805 remove this function when recent files are managed correctly + * + * @param string $source + * @return bool whether the file is accessible by current user */ - public function copy_to_area($encoded, $filerecord, $maxbytes = -1) { + public function file_is_accessible($source) { global $USER; - - $user_context = get_context_instance(CONTEXT_USER, $USER->id); - - $filerecord = (array)$filerecord; - // make sure the new file will be created in user draft area - $filerecord['component'] = 'user'; // make sure - $filerecord['filearea'] = 'draft'; // make sure - $filerecord['contextid'] = $user_context->id; - $filerecord['sortorder'] = 0; - $draftitemid = $filerecord['itemid']; - $new_filepath = $filerecord['filepath']; - $new_filename = $filerecord['filename']; - - $fs = get_file_storage(); - - $params = unserialize(base64_decode($encoded)); - - $contextid = clean_param($params['contextid'], PARAM_INT); - $fileitemid = clean_param($params['itemid'], PARAM_INT); - $filename = clean_param($params['filename'], PARAM_FILE); - $filepath = clean_param($params['filepath'], PARAM_PATH);; - $filearea = clean_param($params['filearea'], PARAM_AREA); - $component = clean_param($params['component'], PARAM_COMPONENT); - - // XXX: - // When user try to pick a file from other filearea, normally file api will use file browse to - // operate the files with capability check, but in some areas, users don't have permission to - // browse the files (for example, forum_attachment area). - // - // To get 'recent' plugin working, we need to use lower level file_stoarge class to bypass the - // capability check, we will use a better workaround to improve it. - // TODO MDL-33297 apply here - if ($stored_file = $fs->get_file($contextid, $component, $filearea, $fileitemid, $filepath, $filename)) { - // verify user id - if ($USER->id != $stored_file->get_userid()) { - throw new moodle_exception('errornotyourfile', 'repository'); - } - if ($maxbytes !== -1 && $stored_file->get_filesize() > $maxbytes) { - throw new file_exception('maxbytes'); - } - - // test if file already exists - if (repository::draftfile_exists($draftitemid, $new_filepath, $new_filename)) { - // create new file - $unused_filename = repository::get_unused_filename($draftitemid, $new_filepath, $new_filename); - $filerecord['filename'] = $unused_filename; - // create a tmp file - $fs->create_file_from_storedfile($filerecord, $stored_file); - $event = array(); - $event['event'] = 'fileexists'; - $event['newfile'] = new stdClass; - $event['newfile']->filepath = $new_filepath; - $event['newfile']->filename = $unused_filename; - $event['newfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $unused_filename)->out(); - $event['existingfile'] = new stdClass; - $event['existingfile']->filepath = $new_filepath; - $event['existingfile']->filename = $new_filename; - $event['existingfile']->url = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; - return $event; - } else { - $fs->create_file_from_storedfile($filerecord, $stored_file); - $info = array(); - $info['title'] = $new_filename; - $info['file'] = $new_filename; - $info['itemid'] = $draftitemid; - $info['filesize'] = $stored_file->get_filesize(); - $info['url'] = moodle_url::make_draftfile_url($draftitemid, $new_filepath, $new_filename)->out();; - $info['contextid'] = $user_context->id; - return $info; - } - } - return false; - + $file = self::get_moodle_file($source); + return (!empty($file) && $file->get_userid() == $USER->id); } /** diff --git a/repository/repository_ajax.php b/repository/repository_ajax.php index c9863c4037e..488ff2f109c 100644 --- a/repository/repository_ajax.php +++ b/repository/repository_ajax.php @@ -232,7 +232,14 @@ switch ($action) { $record->userid = $USER->id; $record->sortorder = 0; + // Check that user has permission to access this file + if (!$repo->file_is_accessible($source)) { + throw new file_exception('storedfilecannotread'); + } + // If file is already a reference, set $source = file source, $repo = file repository + // note that in this case user may not have permission to access the source file directly + // so no file_browser/file_info can be used below if ($repo->has_moodle_files()) { $file = repository::get_moodle_file($source); if ($file && $file->is_external_file()) { @@ -298,13 +305,13 @@ switch ($action) { } else { // Download file to moodle. $downloadedfile = $repo->get_file($source, $saveas_filename); - if ($downloadedfile['path'] === false) { + if (empty($downloadedfile['path'])) { $err->error = get_string('cannotdownload', 'repository'); die(json_encode($err)); } // Check if exceed maxbytes. - if (($maxbytes!==-1) && (filesize($downloadedfile['path']) > $maxbytes)) { + if ($maxbytes != -1 && filesize($downloadedfile['path']) > $maxbytes) { throw new file_exception('maxbytes'); }