From 1772a3e63a40a3dacf826e30c778d36dbf1b8516 Mon Sep 17 00:00:00 2001 From: Gero Lueckemeyer <gero.lueckemeyer@hft-stuttgart.de> Date: Thu, 16 Nov 2023 16:01:06 +0100 Subject: [PATCH] added privacy api implementation like in assignsubmission_file --- dta.zip | Bin 20533 -> 23694 bytes dta/models/DtaResult.php | 4 +- dta/privacy/provider.php | 264 +++++++++++++++++++++++++++++++++ dta/utils/view.php | 2 +- test/privacy/apicompliance.php | 99 +++++++++++++ test/privacy/deletion.php | 70 +++++++++ test/privacy/export.php | 87 +++++++++++ 7 files changed, 523 insertions(+), 3 deletions(-) create mode 100644 dta/privacy/provider.php create mode 100644 test/privacy/apicompliance.php create mode 100644 test/privacy/deletion.php create mode 100644 test/privacy/export.php diff --git a/dta.zip b/dta.zip index 1dc2cab2e4b76667b091b2d6c98f629ee9e195a3..afca1bf23f47123d6decf766493a73598665084d 100644 GIT binary patch delta 5975 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 Q00I>M&B~`<|Erw;0@t4<LjV8( delta 2849 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 diff --git a/dta/models/DtaResult.php b/dta/models/DtaResult.php index 85e1ee7..a06e33d 100644 --- a/dta/models/DtaResult.php +++ b/dta/models/DtaResult.php @@ -69,7 +69,7 @@ class DtaResult { /** * @var $failurereason Reason of test failure if applicable, "" otherwise. - */ + */ public $failurereason; /** @@ -119,7 +119,7 @@ class DtaResultSummary { /** * @var $timestamp Result timestamp for chronological ordering and deletion of previous results. - */ + */ public $timestamp; /** diff --git a/dta/privacy/provider.php b/dta/privacy/provider.php new file mode 100644 index 0000000..7f30ade --- /dev/null +++ b/dta/privacy/provider.php @@ -0,0 +1,264 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * provider for data privacy + * + * @package assignsubmission_dta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright Gero Lueckemeyer and student project teams + */ +namespace assignsubmission_dta\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use core_privacy\local\metadata\collection; +use \core_privacy\local\request\writer; +use \core_privacy\local\request\contextlist; +use \mod_assign\privacy\assign_plugin_request_data; + +class provider implements + // This plugin does store personal user data. + \core_privacy\local\metadata\provider, + \mod_assign\privacy\assignsubmission_provider, + \mod_assign\privacy\assignsubmission_user_provider + { + + /** + * File area for dta submission assignment. + */ + const ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION = "submissions_dta"; + + public static function get_metadata(collection $collection): collection { + $collection->add_subsystem_link( + 'core_files', + [], + 'privacy:metadata:core_files' + ); + + $collection->add_database_table( + 'assignsubmission_dta_summary', + [ + 'assignmentid' => 'privacy:metadata:assignsubmission_dta_summary:assignmentid', + 'submissionid' => 'privacy:metadata:assignsubmission_dta_summary:submissionid', + 'timestamp' => 'privacy:metadata:assignsubmission_dta_summary:timestamp', + 'global_stacktrace' => 'privacy:metadata:assignsubmission_dta_summary:global_stacktrace', + 'successful_competencies' => 'privacy:metadata:assignsubmission_dta_summary:successful_competencies', + 'tested_competencies' => 'privacy:metadata:assignsubmission_dta_summary:tested_competencies', + ], + 'privacy:metadata:assignsubmission_dta_summary' + ); + + $collection->add_database_table( + 'assignsubmission_dta_result', + [ + 'assignmentid' => 'privacy:metadata:assignsubmission_dta_result:assignmentid', + 'submissionid' => 'privacy:metadata:assignsubmission_dta_result:submissionid', + 'package_name' => 'privacy:metadata:assignsubmission_dta_result:package_name', + 'class_name' => 'privacy:metadata:assignsubmission_dta_result:class_name', + 'name' => 'privacy:metadata:assignsubmission_dta_result:name', + 'state' => 'privacy:metadata:assignsubmission_dta_result:state', + 'failure_type' => 'privacy:metadata:assignsubmission_dta_result:failure_type', + 'failure_reason' => 'privacy:metadata:assignsubmission_dta_result:failure_reason', + 'stacktrace' => 'privacy:metadata:assignsubmission_dta_result:stacktrace', + 'column_number' => 'privacy:metadata:assignsubmission_dta_result:column_number', + 'line_number' => 'privacy:metadata:assignsubmission_dta_result:line_number', + 'position' => 'privacy:metadata:assignsubmission_dta_result:position', + ], + 'privacy:metadata:assignsubmission_dta_result' + ); + + $collection->add_external_location_link('dta_backend', [ + 'assignmentid' => 'privacy:metadata:assignsubmission_dta_result:assignmentid', + 'submissionid' => 'privacy:metadata:assignsubmission_dta_result:submissionid', + 'submissioncontent' => 'privacy:metadata:core_files', + ], + 'privacy:metadata:dta_backend' + ); + + return $collection; + } + + /** + * This is covered by mod_assign provider and the query on assign_submissions. + * + * @param int $userid The user ID that we are finding contexts for. + * @param contextlist $contextlist A context list to add sql and params to for contexts. + */ + public static function get_context_for_userid_within_submission(int $userid, contextlist $contextlist) { + // This is already fetched from mod_assign. + } + + /** + * This is also covered by the mod_assign provider and its queries. + * + * @param \mod_assign\privacy\useridlist $useridlist An object for obtaining user IDs of students. + */ + public static function get_student_user_ids(\mod_assign\privacy\useridlist $useridlist) { + // This is already fetched from mod_assign. + } + + /** + * If you have tables that contain userids and you can generate entries in your tables without creating an + * entry in the assign_submission table then please fill in this method. + * + * @param userlist $userlist The userlist object + */ + public static function get_userids_from_context(\core_privacy\local\request\userlist $userlist) { + // Not required. + } + + /** + * Export all user data for this plugin. + * + * @param assign_plugin_request_data $exportdata Data used to determine which context and user to export and other useful + * information to help with exporting. + */ + public static function export_submission_user_data(assign_plugin_request_data $exportdata) { + // We currently don't show submissions to teachers when exporting their data. + $context = $exportdata->get_context(); + if ($exportdata->get_user() != null) { + return null; + } + $user = new \stdClass(); + $assign = $exportdata->get_assign(); + $submission = $exportdata->get_pluginobject(); + $files = get_files($submission, $user); + foreach ($files as $file) { + $userid = $exportdata->get_pluginobject()->userid; + $dtaresultsummary=DBUtils::getresultsummaryfromdatabase($assign->id, $submission->id); + // Submitted file. + writer::with_context($exportdata->get_context())->export_file($exportdata->get_subcontext(), $file) + // DTA result. + ->export_related_data($dtaresultsummary); + + // Plagiarism data. + $coursecontext = $context->get_course_context(); + \core_plagiarism\privacy\provider::export_plagiarism_user_data($userid, $context, $exportdata->get_subcontext(), [ + 'cmid' => $context->instanceid, + 'course' => $coursecontext->instanceid, + 'userid' => $userid, + 'file' => $file + ]); + } + } + + /** + * Any call to this method should delete all user data for the context defined in the deletion_criteria. + * + * @param assign_plugin_request_data $requestdata Information useful for deleting user data. + */ + public static function delete_submission_for_context(assign_plugin_request_data $requestdata) { + global $DB; + + \core_plagiarism\privacy\provider::delete_plagiarism_for_context($requestdata->get_context()); + + $fs = get_file_storage(); + $fs->delete_area_files($requestdata->get_context()->id, 'assignsubmission_dta', ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION); + + $assignmentid = $requestdata->get_assign()->get_instance()->id; + + // Delete records from assignsubmission_dta tables. + $DB->delete_records('assignsubmission_dta_result', ['assignmentid' => $assignmentid]); + $DB->delete_records('assignsubmission_dta_summary', ['assignmentid' => $assignmentid]); + } + + /** + * A call to this method should delete user data (where practical) using the userid and submission. + * + * @param assign_plugin_request_data $deletedata Details about the user and context to focus the deletion. + */ + public static function delete_submission_for_userid(assign_plugin_request_data $deletedata) { + global $DB; + + \core_plagiarism\privacy\provider::delete_plagiarism_for_user($deletedata->get_user()->id, $deletedata->get_context()); + + $assignmentid = $deletedata->get_assign()->get_instance()->id; + $submissionid = $deletedata->get_pluginobject()->id; + + $fs = get_file_storage(); + $fs->delete_area_files($deletedata->get_context()->id, 'assignsubmission_dta', ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, + $submissionid); + + // Delete records from assignsubmission_dta tables. Also possible with a list as below. + $DB->delete_records('assignsubmission_dta_result', [ + 'assignmentid' => $assignmentid, + 'submissionid' => $submissionid, + ]); + $DB->delete_records('assignsubmission_dta_summary', [ + 'assignmentid' => $assignmentid, + 'submissionid' => $submissionid, + ]); + } + + /** + * Deletes all submissions for the submission ids / userids provided in a context. + * assign_plugin_request_data contains: + * - context + * - assign object + * - submission ids (pluginids) + * - user ids + * @param assign_plugin_request_data $deletedata A class that contains the relevant information required for deletion. + */ + public static function delete_submissions(assign_plugin_request_data $deletedata) { + global $DB; + + \core_plagiarism\privacy\provider::delete_plagiarism_for_users($deletedata->get_userids(), $deletedata->get_context()); + + if (empty($deletedata->get_submissionids())) { + return; + } + $fs = get_file_storage(); + list($sql, $params) = $DB->get_in_or_equal($deletedata->get_submissionids(), SQL_PARAMS_NAMED); + $fs->delete_area_files_select($deletedata->get_context()->id, 'assignsubmission_file', ASSIGNSUBMISSION_FILE_FILEAREA, + $sql, $params); + + $params['assignid'] = $deletedata->get_assignid(); + $DB->delete_records_select('assignsubmission_dta_result', "assignmentid = :assignid AND submissionid $sql", $params); + $DB->delete_records_select('assignsubmission_dta_summary', "assignmentid = :assignid AND submissionid $sql", $params); + } + + /** + * Produce a list of files suitable for export that represent this feedback or submission + * + * @param stdClass $submission The submission + * @param stdClass $user The user record - unused + * @return array - return an array of files indexed by filename + */ + public function get_files(stdClass $submission, stdClass $user) { + $result = array(); + $fs = get_file_storage(); + + $files = $fs->get_area_files($this->assignment->get_context()->id, + 'assignsubmission_file', + ASSIGNSUBMISSION_DTA_FILEAREA_SUBMISSION, + $submission->id, + 'timemodified', + false); + + foreach ($files as $file) { + // Do we return the full folder path or just the file name? + if (isset($submission->exportfullpath) && $submission->exportfullpath == false) { + $result[$file->get_filename()] = $file; + } else { + $result[$file->get_filepath().$file->get_filename()] = $file; + } + } + return $result; + } + + +} \ No newline at end of file diff --git a/dta/utils/view.php b/dta/utils/view.php index 34d0881..ebd223a 100644 --- a/dta/utils/view.php +++ b/dta/utils/view.php @@ -241,7 +241,7 @@ class view_submission_utils { // New copy of base attributes array. $resultrowattributes = $tablerowattributes; $tmp = ""; - $tmp .= html_writer::tag("td", get_string("comp" . $index, self::COMPONENT_NAME), $resultrowattributes); + $tmp .= html_writer::tag("td", get_string("comp" . $index, self::COMPONENT_NAME), $resultrowattributes); $tmp .= html_writer::tag("td", 100 * floatval($shown) / floatval($comp) . "% " . "(" . $shown . " / " . $comp . ")", $resultrowattributes); $tmp .= html_writer::tag("td", get_string("comp_expl" . $index, self::COMPONENT_NAME), $resultrowattributes); diff --git a/test/privacy/apicompliance.php b/test/privacy/apicompliance.php new file mode 100644 index 0000000..121ad63 --- /dev/null +++ b/test/privacy/apicompliance.php @@ -0,0 +1,99 @@ +<?php + +// Set this if you want to run the script for one component only. Otherwise leave empty. +$CHECK_COMPONENT = ''; + +define('CLI_SCRIPT', true); + +require_once('config.php'); + +$user = \core_user::get_user(2); + +\core\session\manager::init_empty_session(); +\core\session\manager::set_user($user); + +$rc = new \ReflectionClass(\core_privacy\manager::class); +$rcm = $rc->getMethod('get_component_list'); +$rcm->setAccessible(true); + +$manager = new \core_privacy\manager(); +$components = $rcm->invoke($manager); + +$list = (object) [ + 'good' => [], + 'bad' => [], +]; + +foreach ($components as $component) { + if ($CHECK_COMPONENT && $component !== $CHECK_COMPONENT) { + continue; + } + $compliant = $manager->component_is_compliant($component); + if ($compliant) { + $list->good[] = $component; + } else { + $list->bad[] = $component; + } +} + +echo "The following plugins are not compliant:\n"; +echo "=> " . implode("\n=> ", array_values($list->bad)) . "\n"; + +echo "\n"; +echo "Testing the compliant plugins:\n"; +foreach ($list->good as $component) { + $classname = \core_privacy\manager::get_provider_classname_for_component($component); + echo "== {$component} ($classname) ==\n"; + if (check_implements($component, \core_privacy\local\metadata\null_provider::class)) { + echo " Claims not to store any data with reason:\n"; + echo " '" . get_string($classname::get_reason(), $component) . "'\n"; + } + else if (check_implements($component, \core_privacy\local\metadata\provider::class)) { + $collection = new \core_privacy\local\metadata\collection($component); + $classname::get_metadata($collection); + $count = count($collection->get_collection()); + echo " Found {$count} items of metadata\n"; + if (empty($count)) { + echo "!!! No metadata found!!! This an error.\n"; + } + + if (check_implements($component, \core_privacy\local\request\user_preference_provider::class)) { + $userprefdescribed = false; + foreach ($collection->get_collection() as $item) { + if ($item instanceof \core_privacy\local\metadata\types\user_preference) { + $userprefdescribed = true; + echo " ".$item->get_name()." : ".get_string($item->get_summary(), $component) . "\n"; + } + } + if (!$userprefdescribed) { + echo "!!! User preference found, but was not described in metadata\n"; + } + } + + if (check_implements($component, \core_privacy\local\request\core_user_data_provider::class)) { + // No need to check the return type - it's enforced by the interface. + $contextlist = $classname::get_contexts_for_userid($user->id); + $approvedcontextlist = new \core_privacy\local\request\approved_contextlist($user, $contextlist->get_component(), $contextlist->get_contextids()); + if (count($approvedcontextlist)) { + $classname::export_user_data($approvedcontextlist); + echo " Successfully ran a test export\n"; + } else { + echo " Nothing to export.\n"; + } + } + if (check_implements($component, \core_privacy\local\request\shared_data_provider::class)) { + echo " This is a shared data provider\n"; + } + } +} + +echo "\n\n== Done ==\n"; + +function check_implements($component, $interface) { + $manager = new \core_privacy\manager(); + $rc = new \ReflectionClass(\core_privacy\manager::class); + $rcm = $rc->getMethod('component_implements'); + $rcm->setAccessible(true); + + return $rcm->invoke($manager, $component, $interface); +} \ No newline at end of file diff --git a/test/privacy/deletion.php b/test/privacy/deletion.php new file mode 100644 index 0000000..8fdb75c --- /dev/null +++ b/test/privacy/deletion.php @@ -0,0 +1,70 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +define('CLI_SCRIPT', true); +require_once('config.php'); +require_once("$CFG->libdir/clilib.php"); + +list($options, $unrecognized) = cli_get_params( + [ + 'username' => '', + 'userid' => '', + ], + [] +); + +$user = null; +$username = $options['username']; +$userid = $options['userid']; + +if (!empty($options['username'])) { + $user = \core_user::get_user_by_username($options['username']); +} else if (!empty($options['userid'])) { + $user = \core_user::get_user($options['userid']); +} + +while (empty($user)) { + if (!empty($username)) { + echo "Unable to find a user with username '{$username}'.\n"; + echo "Try again.\n"; + } else if (!empty($userid)) { + echo "Unable to find a user with userid '{$userid}'.\n"; + echo "Try again.\n"; + } + $username = readline("Username: "); + $user = \core_user::get_user_by_username($username); +} + +echo "Processing delete for " . fullname($user) . "\n"; + +\core\session\manager::init_empty_session(); +\core\session\manager::set_user($user); + +$manager = new \core_privacy\manager(); + +$approvedlist = new \core_privacy\local\request\contextlist_collection($user->id); + +$trace = new text_progress_trace(); +$contextlists = $manager->get_contexts_for_userid($user->id, $trace); +foreach ($contextlists as $contextlist) { + $approvedlist->add_contextlist(new \core_privacy\local\request\approved_contextlist( + $user, + $contextlist->get_component(), + $contextlist->get_contextids() + )); +} + +$manager->delete_data_for_user($approvedlist, $trace); \ No newline at end of file diff --git a/test/privacy/export.php b/test/privacy/export.php new file mode 100644 index 0000000..f56d617 --- /dev/null +++ b/test/privacy/export.php @@ -0,0 +1,87 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * Helper utility to perform a test export. + * + * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define('CLI_SCRIPT', true); +require_once('config.php'); +require_once("$CFG->libdir/clilib.php"); + +list($options, $unrecognized) = cli_get_params( + [ + 'username' => '', + 'userid' => '', + ], + [] +); + +$user = null; +$username = $options['username']; +$userid = $options['userid']; + +if (!empty($options['username'])) { + $user = \core_user::get_user_by_username($options['username']); +} else if (!empty($options['userid'])) { + $user = \core_user::get_user($options['userid']); +} + +while (empty($user)) { + if (!empty($username)) { + echo "Unable to find a user with username '{$username}'.\n"; + echo "Try again.\n"; + } else if (!empty($userid)) { + echo "Unable to find a user with userid '{$userid}'.\n"; + echo "Try again.\n"; + } + $username = readline("Username: "); + $user = \core_user::get_user_by_username($username); +} + +echo "Processing export for " . fullname($user) . "\n"; + +\core\session\manager::init_empty_session(); +\core\session\manager::set_user($user); + +$PAGE = new moodle_page(); +$OUTPUT = new core_renderer($PAGE, RENDERER_TARGET_GENERAL); + +$manager = new \core_privacy\manager(); + +$approvedlist = new \core_privacy\local\request\contextlist_collection($user->id); + +$contextlists = $manager->get_contexts_for_userid($user->id); +foreach ($contextlists as $contextlist) { + $approvedlist->add_contextlist(new \core_privacy\local\request\approved_contextlist( + $user, + $contextlist->get_component(), + $contextlist->get_contextids() + )); +} + +$exportedcontent = $manager->export_user_data($approvedlist); +$basedir = make_temp_directory('privacy'); +$exportpath = make_unique_writable_directory($basedir, true); +$fp = get_file_packer(); +$fp->extract_to_pathname($exportedcontent, $exportpath); + +echo "\n"; +echo "== File export was uncompressed to {$exportpath}\n"; +echo "============================================================================\n"; \ No newline at end of file -- GitLab