From 4a74ab0200d472f600d238787ebbc5e782b2135d Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Sun, 3 Jun 2018 21:06:45 +0800 Subject: [PATCH] MDL-36754 core_files: Add a token version of pluginfile --- files/classes/privacy/provider.php | 95 ++++++++++++++++++++++++++++++++++- lang/en/files.php | 1 + lib/moodlelib.php | 7 ++- pluginfile.php | 8 ++- pluginfile.php => tokenpluginfile.php | 30 ++++++----- 5 files changed, 123 insertions(+), 18 deletions(-) copy pluginfile.php => tokenpluginfile.php (56%) diff --git a/files/classes/privacy/provider.php b/files/classes/privacy/provider.php index 2bf2dbbe460..865d75fbe25 100644 --- a/files/classes/privacy/provider.php +++ b/files/classes/privacy/provider.php @@ -27,6 +27,8 @@ namespace core_files\privacy; defined('MOODLE_INTERNAL') || die(); use core_privacy\local\metadata\collection; +use core_privacy\local\request\contextlist; +use core_privacy\local\request\approved_contextlist; /** * Data provider class. @@ -41,7 +43,10 @@ use core_privacy\local\metadata\collection; */ class provider implements \core_privacy\local\metadata\provider, - \core_privacy\local\request\subsystem\plugin_provider { + \core_privacy\local\request\subsystem\plugin_provider, + + // We store a userkey for token-based file access. + \core_privacy\local\request\subsystem\provider { /** * Returns metadata. @@ -65,7 +70,95 @@ class provider implements 'timemodified' => 'privacy:metadata:files:timemodified', ], 'privacy:metadata:files'); + $collection->add_subsystem_link('core_userkey', [], 'privacy:metadata:core_userkey'); + return $collection; } + /** + * Get the list of contexts that contain user information for the specified user. + * + * This is currently just the user context. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + $sql = "SELECT ctx.id + FROM {user_private_key} k + JOIN {user} u ON k.userid = u.id + JOIN {context} ctx ON ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel + WHERE k.userid = :userid AND k.script = :script"; + $params = [ + 'userid' => $userid, + 'contextlevel' => CONTEXT_USER, + 'script' => 'core_files', + ]; + $contextlist = new contextlist(); + $contextlist->add_from_sql($sql, $params); + + return $contextlist; + } + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + // If the user has data, then only the CONTEXT_USER should be present so get the first context. + $contexts = $contextlist->get_contexts(); + if (count($contexts) == 0) { + return; + } + + // Sanity check that context is at the user context level, then get the userid. + $context = reset($contexts); + if ($context->contextlevel !== CONTEXT_USER) { + return; + } + + // Export associated userkeys. + $subcontext = [ + get_string('files'), + ]; + \core_userkey\privacy\provider::export_userkeys($context, $subcontext, 'core_files'); + } + + /** + * Delete all use data which matches the specified deletion_criteria. + * + * @param context $context A user context. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + // Sanity check that context is at the user context level, then get the userid. + if ($context->contextlevel !== CONTEXT_USER) { + return; + } + + // Delete all the userkeys. + \core_userkey\privacy\provider::delete_userkeys('core_files', $context->instanceid); + } + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + // If the user has data, then only the user context should be present so get the first context. + $contexts = $contextlist->get_contexts(); + if (count($contexts) == 0) { + return; + } + + // Sanity check that context is at the user context level, then get the userid. + $context = reset($contexts); + if ($context->contextlevel !== CONTEXT_USER) { + return; + } + + // Delete all the userkeys for core_files.. + \core_userkey\privacy\provider::delete_userkeys('core_files', $context->instanceid); + } } diff --git a/lang/en/files.php b/lang/en/files.php index 6cb55ac4e0d..b2e305b483e 100644 --- a/lang/en/files.php +++ b/lang/en/files.php @@ -37,3 +37,4 @@ $string['privacy:metadata:files:source'] = 'The source of the file'; $string['privacy:metadata:files:timecreated'] = 'The time when the file was created'; $string['privacy:metadata:files:timemodified'] = 'The time when the file was last modified'; $string['privacy:metadata:files:userid'] = 'The user who created the file'; +$string['privacy:metadata:core_userkey'] = 'A private token is generated and stored. This token can be used to access Moodle files without requiring you to log in.'; diff --git a/lib/moodlelib.php b/lib/moodlelib.php index e26ef637ae5..4962aa46499 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -3112,9 +3112,10 @@ function validate_user_key($keyvalue, $script, $instance) { * @uses PARAM_ALPHANUM * @param string $script unique script identifier * @param int $instance optional instance id + * @param string $keyvalue The key. If not supplied, this will be fetched from the current session. * @return int Instance ID */ -function require_user_key_login($script, $instance=null) { +function require_user_key_login($script, $instance = null, $keyvalue = null) { global $DB; if (!NO_MOODLE_COOKIES) { @@ -3124,7 +3125,9 @@ function require_user_key_login($script, $instance=null) { // Extra safety. \core\session\manager::write_close(); - $keyvalue = required_param('key', PARAM_ALPHANUM); + if (null === $keyvalue) { + $keyvalue = required_param('key', PARAM_ALPHANUM); + } $key = validate_user_key($keyvalue, $script, $instance); diff --git a/pluginfile.php b/pluginfile.php index 3d6d542496e..24e6cda10e9 100644 --- a/pluginfile.php +++ b/pluginfile.php @@ -25,12 +25,16 @@ */ // Disable moodle specific debug messages and any errors in output. -define('NO_DEBUG_DISPLAY', true); +if (!defined('NO_DEBUG_DISPLAY')) { + define('NO_DEBUG_DISPLAY', true); +} require_once('config.php'); require_once('lib/filelib.php'); -$relativepath = get_file_argument(); +if (empty($relativepath)) { + $relativepath = get_file_argument(); +} $forcedownload = optional_param('forcedownload', 0, PARAM_BOOL); $preview = optional_param('preview', null, PARAM_ALPHANUM); // Offline means download the file from the repository and serve it, even if it was an external link. diff --git a/pluginfile.php b/tokenpluginfile.php similarity index 56% copy from pluginfile.php copy to tokenpluginfile.php index 3d6d542496e..156d4126f80 100644 --- a/pluginfile.php +++ b/tokenpluginfile.php @@ -1,5 +1,4 @@ . /** - * This script delegates file serving to individual plugins + * Entry point for token-based access to pluginfile.php. * * @package core - * @subpackage file - * @copyright 2008 Petr Skoda (http://skodak.org) + * @copyright 2018 Andrew Nicols * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -// Disable moodle specific debug messages and any errors in output. +// Disable the use of sessions/cookies - we recreate $USER for every call. +define('NO_MOODLE_COOKIES', true); + +// Disable debugging for this script. +// It is typically used to display images. define('NO_DEBUG_DISPLAY', true); require_once('config.php'); -require_once('lib/filelib.php'); $relativepath = get_file_argument(); -$forcedownload = optional_param('forcedownload', 0, PARAM_BOOL); -$preview = optional_param('preview', null, PARAM_ALPHANUM); -// Offline means download the file from the repository and serve it, even if it was an external link. -// The repository may have to export the file to an offline format. -$offline = optional_param('offline', 0, PARAM_BOOL); -$embed = optional_param('embed', 0, PARAM_BOOL); -file_pluginfile($relativepath, $forcedownload, $preview, $offline, $embed); +$token = optional_param('token', '', PARAM_ALPHANUM); +if (0 == strpos($relativepath, '/token/')) { + $relativepath = ltrim($relativepath, '/'); + $pathparts = explode('/', $relativepath, 2); + $token = $pathparts[0]; + $relativepath = "/{$pathparts[1]}"; +} + +require_user_key_login('core_files', null, $token); +require_once('pluginfile.php'); -- 2.11.4.GIT