From 8760b7335ba3532fb0fd869f93b47d98f1cc86e7 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Tue, 15 May 2018 14:17:18 +0800 Subject: [PATCH] MDL-62134 tool_dataprivacy: Add a manager_observer --- .../classes/expired_contexts_manager.php | 3 +- .../{manager.php => manager_observer.php} | 37 ++---- .../dataprivacy/classes/metadata_registry.php | 4 +- .../task/initiate_data_request_task.php | 4 +- .../task/process_data_request_task.php | 9 +- .../tests/manager_observer_test.php | 117 ++++++++++++++++++ 6 files changed, 139 insertions(+), 35 deletions(-) rename admin/tool/dataprivacy/classes/{manager.php => manager_observer.php} (64%) create mode 100644 admin/tool/dataprivacy/tests/manager_observer_test.php diff --git a/admin/tool/dataprivacy/classes/expired_contexts_manager.php b/admin/tool/dataprivacy/classes/expired_contexts_manager.php index 6c083ba3f5c..3d20e5d0cc6 100644 --- a/admin/tool/dataprivacy/classes/expired_contexts_manager.php +++ b/admin/tool/dataprivacy/classes/expired_contexts_manager.php @@ -90,7 +90,8 @@ abstract class expired_contexts_manager { return $numprocessed; } - $privacymanager = new manager(); + $privacymanager = new \core_privacy\manager(); + $privacymanager->set_observer(new \tool_dataprivacy\manager_observer()); foreach ($this->get_context_levels() as $level) { diff --git a/admin/tool/dataprivacy/classes/manager.php b/admin/tool/dataprivacy/classes/manager_observer.php similarity index 64% rename from admin/tool/dataprivacy/classes/manager.php rename to admin/tool/dataprivacy/classes/manager_observer.php index 16c46a68fe7..578a7d6ced4 100644 --- a/admin/tool/dataprivacy/classes/manager.php +++ b/admin/tool/dataprivacy/classes/manager_observer.php @@ -13,6 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . + /** * Class \tool_dataprivacy\manager * @@ -25,51 +26,29 @@ namespace tool_dataprivacy; defined('MOODLE_INTERNAL') || die(); /** - * Wrapper for \core_privacy\manager that sends notifications about exceptions to DPO + * A failure observer for the \core_privacy\manager. * * @package tool_dataprivacy * @copyright 2018 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class manager extends \core_privacy\manager { - +class manager_observer implements \core_privacy\manager_observer { /** - * Call the named method with the specified params on the supplied component if it implements the relevant interface on its provider. - * - * @param string $component The component to call - * @param string $interface The interface to implement - * @param string $methodname The method to call - * @param array $params The params to call - * @return mixed - */ - public static function component_class_callback(string $component, string $interface, string $methodname, array $params) { - try { - return parent::component_class_callback($component, $interface, $methodname, $params); - } catch (\Throwable $e) { - debugging($e->getMessage(), DEBUG_DEVELOPER, $e->getTrace()); - self::notify_dpo($e, $component, $interface, $methodname, $params); - } - return null; - } - - /** - * Notifies all DPOs about exception occurred + * Notifies all DPOs that an exception occurred. * * @param \Throwable $e * @param string $component * @param string $interface * @param string $methodname * @param array $params - * @return mixed */ - protected static function notify_dpo(\Throwable $e, string $component, string $interface, string $methodname, array $params) { - + public function handle_component_failure($e, $component, $interface, $methodname, array $params) { // Get the list of the site Data Protection Officers. $dpos = api::get_site_dpos(); $messagesubject = get_string('exceptionnotificationsubject', 'tool_dataprivacy'); $a = (object)[ - 'fullmethodname' => static::get_provider_classname_for_component($component) . '::' . $methodname, + 'fullmethodname' => \core_privacy\manager::get_provider_classname_for_component($component) . '::' . $methodname, 'component' => $component, 'message' => $e->getMessage(), 'backtrace' => $e->getTraceAsString() @@ -91,7 +70,7 @@ class manager extends \core_privacy\manager { $message->fullmessage = html_to_text($messagebody); // Send message. - return message_send($message); + message_send($message); } } -} \ No newline at end of file +} diff --git a/admin/tool/dataprivacy/classes/metadata_registry.php b/admin/tool/dataprivacy/classes/metadata_registry.php index b101f4238bd..6282cc41291 100644 --- a/admin/tool/dataprivacy/classes/metadata_registry.php +++ b/admin/tool/dataprivacy/classes/metadata_registry.php @@ -39,7 +39,9 @@ class metadata_registry { * @return array An array with all of the plugin types / plugins and the user data they store. */ public function get_registry_metadata() { - $manager = new manager(); + $manager = new \core_privacy\manager(); + $manager->set_observer(new \tool_dataprivacy\manager_observer()); + $pluginman = \core_plugin_manager::instance(); $contributedplugins = $this->get_contrib_list(); $metadata = $manager->get_metadata_for_components(); diff --git a/admin/tool/dataprivacy/classes/task/initiate_data_request_task.php b/admin/tool/dataprivacy/classes/task/initiate_data_request_task.php index 6554894beff..ad5a933090e 100644 --- a/admin/tool/dataprivacy/classes/task/initiate_data_request_task.php +++ b/admin/tool/dataprivacy/classes/task/initiate_data_request_task.php @@ -97,7 +97,9 @@ class initiate_data_request_task extends adhoc_task { api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING); // Add the list of relevant contexts to the request, and mark all as pending approval. - $privacymanager = new manager(); + $privacymanager = new \core_privacy\manager(); + $privacymanager->set_observer(new \tool_dataprivacy\manager_observer()); + $contextlistcollection = $privacymanager->get_contexts_for_userid($datarequest->get('userid')); api::add_request_contexts_with_status($contextlistcollection, $requestid, contextlist_context::STATUS_PENDING); 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 2ef0b7d3685..6a93217b4cc 100644 --- a/admin/tool/dataprivacy/classes/task/process_data_request_task.php +++ b/admin/tool/dataprivacy/classes/task/process_data_request_task.php @@ -33,7 +33,6 @@ use moodle_exception; use moodle_url; use tool_dataprivacy\api; use tool_dataprivacy\data_request; -use tool_dataprivacy\manager; defined('MOODLE_INTERNAL') || die(); @@ -88,7 +87,9 @@ class process_data_request_task extends adhoc_task { $approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent); // Export the data. - $manager = new manager(); + $manager = new \core_privacy\manager(); + $manager->set_observer(new \tool_dataprivacy\manager_observer()); + $exportedcontent = $manager->export_user_data($approvedclcollection); $fs = get_file_storage(); @@ -110,7 +111,9 @@ class process_data_request_task extends adhoc_task { $approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent); // Delete the data. - $manager = new manager(); + $manager = new \core_privacy\manager(); + $manager->set_observer(new \tool_dataprivacy\manager_observer()); + $manager->delete_data_for_user($approvedclcollection); } diff --git a/admin/tool/dataprivacy/tests/manager_observer_test.php b/admin/tool/dataprivacy/tests/manager_observer_test.php new file mode 100644 index 00000000000..6c710277501 --- /dev/null +++ b/admin/tool/dataprivacy/tests/manager_observer_test.php @@ -0,0 +1,117 @@ +. + +/** + * Tests for the manager observer. + * + * @package tool_dataprivacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * API tests. + * + * @package tool_dataprivacy + * @copyright 2018 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class tool_dataprivacy_manager_observer_testcase extends advanced_testcase { + + /** + * Helper to set andn return two users who are DPOs. + */ + protected function setup_site_dpos() { + global $DB; + $this->resetAfterTest(); + + $generator = new testing_data_generator(); + $u1 = $this->getDataGenerator()->create_user(); + $u2 = $this->getDataGenerator()->create_user(); + + $context = context_system::instance(); + + // Give the manager role with the capability to manage data requests. + $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager')); + assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true); + + // Assign both users as manager. + role_assign($managerroleid, $u1->id, $context->id); + role_assign($managerroleid, $u2->id, $context->id); + + // Only map the manager role to the DPO role. + set_config('dporoles', $managerroleid, 'tool_dataprivacy'); + + return \tool_dataprivacy\api::get_site_dpos(); + } + + /** + * Ensure that when users are configured as DPO, they are sent an message upon failure. + */ + public function test_handle_component_failure() { + $this->resetAfterTest(); + + // Create another user who is not a DPO. + $this->getDataGenerator()->create_user(); + + // Create the DPOs. + $dpos = $this->setup_site_dpos(); + + $observer = new \tool_dataprivacy\manager_observer(); + + // Handle the failure, catching messages. + $mailsink = $this->redirectMessages(); + $mailsink->clear(); + $observer->handle_component_failure(new \Exception('error'), 'foo', 'bar', 'baz', ['foobarbaz', 'bum']); + + // Messages should be sent to both DPOs only. + $this->assertEquals(2, $mailsink->count()); + + $messages = $mailsink->get_messages(); + $messageusers = array_map(function($message) { + return $message->useridto; + }, $messages); + + $this->assertEquals(array_keys($dpos), $messageusers, '', 0.0, 0, true); + } + + /** + * Ensure that when no user is configured as DPO, the message is sent to admin instead. + */ + public function test_handle_component_failure_no_dpo() { + $this->resetAfterTest(); + + // Create another user who is not a DPO or admin. + $this->getDataGenerator()->create_user(); + + $observer = new \tool_dataprivacy\manager_observer(); + + $mailsink = $this->redirectMessages(); + $mailsink->clear(); + $observer->handle_component_failure(new \Exception('error'), 'foo', 'bar', 'baz', ['foobarbaz', 'bum']); + + // Messages should have been sent only to the admin. + $this->assertEquals(1, $mailsink->count()); + + $messages = $mailsink->get_messages(); + $message = reset($messages); + + $admin = \core_user::get_user_by_username('admin'); + $this->assertEquals($admin->id, $message->useridto); + } +}