diff --git a/admin/tool/dataprivacy/classes/api.php b/admin/tool/dataprivacy/classes/api.php index 98e7951d666..915905badd8 100644 --- a/admin/tool/dataprivacy/classes/api.php +++ b/admin/tool/dataprivacy/classes/api.php @@ -1075,6 +1075,7 @@ class api { public static function add_request_contexts_with_status(contextlist_collection $clcollection, int $requestid, int $status) { $request = new data_request($requestid); $user = \core_user::get_user($request->get('userid')); + $isconfigured = data_registry::defaults_set(); foreach ($clcollection as $contextlist) { // Convert the \core_privacy\local\request\contextlist into a contextlist persistent and store it. $clp = \tool_dataprivacy\contextlist::from_contextlist($contextlist); @@ -1083,7 +1084,11 @@ class api { // Store the associated contexts in the contextlist. foreach ($contextlist->get_contextids() as $contextid) { - if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) { + if ($isconfigured && self::DATAREQUEST_TYPE_DELETE == $request->get('type')) { + // Data can only be deleted from it if the context is either expired, or unprotected. + // Note: We can only check whether a context is expired or unprotected if the site is configured and + // defaults are set appropriately. If they are not, we treat all contexts as though they are + // unprotected. $context = \context::instance_by_id($contextid); $purpose = static::get_effective_context_purpose($context); @@ -1163,10 +1168,11 @@ class api { * @return contextlist_collection the collection of approved_contextlist objects. */ public static function get_approved_contextlist_collection_for_request(data_request $request) : contextlist_collection { + global $DB; $foruser = core_user::get_user($request->get('userid')); + $isconfigured = data_registry::defaults_set(); // Fetch all approved contextlists and create the core_privacy\local\request\contextlist objects here. - global $DB; $sql = "SELECT cl.component, ctx.contextid FROM {" . request_contextlist::TABLE . "} rcl JOIN {" . contextlist::TABLE . "} cl ON rcl.contextlistid = cl.id @@ -1190,10 +1196,13 @@ class api { $contexts = []; } - if ($request->get('type') == static::DATAREQUEST_TYPE_DELETE) { + if ($isconfigured && $request->get('type') == static::DATAREQUEST_TYPE_DELETE) { $context = \context::instance_by_id($record->contextid); $purpose = static::get_effective_context_purpose($context); // Data can only be deleted from it if the context is either expired, or unprotected. + // Note: We can only check whether a context is expired or unprotected if the site is configured and + // defaults are set appropriately. If they are not, we treat all contexts as though they are + // unprotected. if (!expired_contexts_manager::is_context_expired_or_unprotected_for_user($context, $foruser)) { continue; } diff --git a/admin/tool/dataprivacy/classes/task/process_data_request_task.php b/admin/tool/dataprivacy/classes/task/process_data_request_task.php index 9e76945638c..50e47b3d5f0 100644 --- a/admin/tool/dataprivacy/classes/task/process_data_request_task.php +++ b/admin/tool/dataprivacy/classes/task/process_data_request_task.php @@ -74,11 +74,9 @@ class process_data_request_task extends adhoc_task { return; } - // If no site purpose is defined, reject requests since they cannot be processed. if (!\tool_dataprivacy\data_registry::defaults_set()) { - api::update_request_status($requestid, api::DATAREQUEST_STATUS_REJECTED); - mtrace('No site purpose defined. Request ' . $requestid . ' rejected.'); - return; + // Warn if no site purpose is defined. + mtrace('Warning: No purpose is defined at the system level. Deletion will delete all.'); } // Get the user details now. We might not be able to retrieve it later if it's a deletion processing. diff --git a/admin/tool/dataprivacy/datarequests.php b/admin/tool/dataprivacy/datarequests.php index ec6b9c2b1c5..331dd1960df 100644 --- a/admin/tool/dataprivacy/datarequests.php +++ b/admin/tool/dataprivacy/datarequests.php @@ -38,6 +38,10 @@ $title = get_string('datarequests', 'tool_dataprivacy'); echo $OUTPUT->header(); echo $OUTPUT->heading($title); +if (!\tool_dataprivacy\data_registry::defaults_set()) { + \core\notification::error(get_string('systemconfignotsetwarning', 'tool_dataprivacy')); +} + if (\tool_dataprivacy\api::is_site_dpo($USER->id)) { $filtersapplied = optional_param_array('request-filters', [-1], PARAM_NOTAGS); $filterscleared = optional_param('filters-cleared', 0, PARAM_INT); diff --git a/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php b/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php index fcfc5f81bc6..1310bf1cf3a 100644 --- a/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php +++ b/admin/tool/dataprivacy/lang/en/tool_dataprivacy.php @@ -308,6 +308,7 @@ $string['statusrejected'] = 'Rejected'; $string['subjectscope'] = 'Subject scope'; $string['subjectscope_help'] = 'The subject scope lists the roles which may be assigned in this context.'; $string['summary'] = 'Registry configuration summary'; +$string['systemconfignotsetwarning'] = 'A site purpose and category have not been defined. When these are not defined, all data will be removed when processing deletion requests.'; $string['user'] = 'User'; $string['userlistnoncompliant'] = 'Userlist provider missing'; $string['userlistexplanation'] = 'This plugin has the base provider but should also implement the userlist provider for full support of privacy functionality.'; diff --git a/admin/tool/dataprivacy/tests/api_test.php b/admin/tool/dataprivacy/tests/api_test.php index d902e6737c5..33d91c11fff 100644 --- a/admin/tool/dataprivacy/tests/api_test.php +++ b/admin/tool/dataprivacy/tests/api_test.php @@ -1711,6 +1711,48 @@ class tool_dataprivacy_api_testcase extends advanced_testcase { $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true); } + /** + * Test that delete requests do not filter out protected purpose contexts if the the site is properly configured. + */ + public function test_add_request_contexts_with_status_delete_course_no_site_config() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + + $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]); + $coursecontext = \context_course::instance($course->id); + + $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); + list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum'); + $contextforum = \context_module::instance($forumcm->id); + + $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); + + // Create the initial contextlist. + $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id); + + $contextlist = new \core_privacy\local\request\contextlist(); + $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]); + $contextlist->set_component('tool_dataprivacy'); + $initialcollection->add_contextlist($contextlist); + + $contextlist = new \core_privacy\local\request\contextlist(); + $contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $contextforum->id]); + $contextlist->set_component('mod_forum'); + $initialcollection->add_contextlist($contextlist); + + $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE); + api::add_request_contexts_with_status( + $initialcollection, $request->get('id'), contextlist_context::STATUS_APPROVED); + + $newcollection = \tool_dataprivacy\request_contextlist::get_records(); + $this->assertCount(2, $newcollection); + foreach ($newcollection as $contextlist) { + $requests = contextlist_context::get_records(['contextlistid' => $contextlist->get('contextlistid')]); + $this->assertCount(1, $requests); + } + } + /** * Test that delete requests do not filter out protected purpose contexts if they are already expired. */ @@ -1807,6 +1849,64 @@ class tool_dataprivacy_api_testcase extends advanced_testcase { $this->assertCount(1, $requests); } + /** + * Test that delete requests do not filter out protected purpose contexts if the the site is properly configured. + */ + public function test_get_approved_contextlist_collection_for_request_delete_course_no_config() { + $this->resetAfterTest(); + + $user = $this->getDataGenerator()->create_user(); + $course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]); + $coursecontext = \context_course::instance($course->id); + + $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]); + list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum'); + $contextforum = \context_module::instance($forumcm->id); + + $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student'); + + // Create the initial contextlist. + $request = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE); + $initialcollection = new \core_privacy\local\request\contextlist_collection($user->id); + + $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'tool_dataprivacy']); + $contextlist->save(); + $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [ + 'contextid' => $coursecontext->id, + 'status' => contextlist_context::STATUS_APPROVED, + 'contextlistid' => $contextlist->get('id'), + ]); + $clcontext->save(); + $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [ + 'requestid' => $request->get('id'), + 'contextlistid' => $contextlist->get('id'), + ]); + $rcl->save(); + + $contextlist = new \tool_dataprivacy\contextlist(0, (object) ['component' => 'mod_forum']); + $contextlist->save(); + $clcontext = new \tool_dataprivacy\contextlist_context(0, (object) [ + 'contextid' => $contextforum->id, + 'status' => contextlist_context::STATUS_APPROVED, + 'contextlistid' => $contextlist->get('id'), + ]); + $clcontext->save(); + $rcl = new \tool_dataprivacy\request_contextlist(0, (object) [ + 'requestid' => $request->get('id'), + 'contextlistid' => $contextlist->get('id'), + ]); + $rcl->save(); + + $collection = api::get_approved_contextlist_collection_for_request($request); + $this->assertCount(2, $collection); + + $list = $collection->get_contextlist_for_component('tool_dataprivacy'); + $this->assertCount(1, $list); + + $list = $collection->get_contextlist_for_component('mod_forum'); + $this->assertCount(1, $list); + } + /** * Test that delete requests do not filter out protected purpose contexts if they are already expired. */