Merge branch 'MDL-62564-integration-master-1' of git://github.com/mihailges/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2018-11-08 16:38:38 +01:00
commit 3aa0cfe33e
16 changed files with 644 additions and 29 deletions

View File

@ -41,6 +41,7 @@ use tool_dataprivacy\external\data_request_exporter;
use tool_dataprivacy\local\helper;
use tool_dataprivacy\task\initiate_data_request_task;
use tool_dataprivacy\task\process_data_request_task;
use tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
@ -253,6 +254,8 @@ class api {
$datarequest->set('type', $type);
// Set request comments.
$datarequest->set('comments', $comments);
// Set the creation method.
$datarequest->set('creationmethod', $creationmethod);
// Store subject access request.
$datarequest->create();
@ -275,6 +278,7 @@ class api {
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param int[] $creationmethods The request creation method filters.
* @param string $sort The order by clause.
* @param int $offset Amount of records to skip.
* @param int $limit Amount of records to fetch.
@ -282,7 +286,8 @@ class api {
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $sort = '', $offset = 0, $limit = 0) {
public static function get_data_requests($userid = 0, $statuses = [], $types = [], $creationmethods = [],
$sort = '', $offset = 0, $limit = 0) {
global $DB, $USER;
$results = [];
$sqlparams = [];
@ -306,6 +311,13 @@ class api {
$sqlparams = array_merge($sqlparams, $typeparams);
}
// Set request creation method filter.
if (!empty($creationmethods)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($creationmethods, SQL_PARAMS_NAMED);
$sqlconditions[] = "creationmethod $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
@ -348,7 +360,7 @@ class api {
if (!empty($expiredrequests)) {
data_request::expire($expiredrequests);
$results = self::get_data_requests($userid, $statuses, $types, $sort, $offset, $limit);
$results = self::get_data_requests($userid, $statuses, $types, $creationmethods, $sort, $offset, $limit);
}
}
@ -361,11 +373,12 @@ class api {
* @param int $userid The User ID.
* @param int[] $statuses The status filters.
* @param int[] $types The request type filters.
* @param int[] $creationmethods The request creation method filters.
* @return int
* @throws coding_exception
* @throws dml_exception
*/
public static function get_data_requests_count($userid = 0, $statuses = [], $types = []) {
public static function get_data_requests_count($userid = 0, $statuses = [], $types = [], $creationmethods = []) {
global $DB, $USER;
$count = 0;
$sqlparams = [];
@ -379,6 +392,11 @@ class api {
$sqlconditions[] = "type $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if (!empty($creationmethods)) {
list($typeinsql, $typeparams) = $DB->get_in_or_equal($creationmethods, SQL_PARAMS_NAMED);
$sqlconditions[] = "creationmethod $typeinsql";
$sqlparams = array_merge($sqlparams, $typeparams);
}
if ($userid) {
// Get the data requests for the user or data requests made by the user.
$sqlconditions[] = "(userid = :userid OR requestedby = :requestedby)";
@ -965,6 +983,34 @@ class api {
return data_registry::get_effective_contextlevel_value($contextlevel, 'purpose', $forcedvalue);
}
/**
* Creates an expired context record for the provided context id.
*
* @param int $contextid
* @return \tool_dataprivacy\expired_context
*/
public static function create_expired_context($contextid) {
$record = (object)[
'contextid' => $contextid,
'status' => expired_context::STATUS_EXPIRED,
];
$expiredctx = new expired_context(0, $record);
$expiredctx->save();
return $expiredctx;
}
/**
* Deletes an expired context record.
*
* @param int $id The tool_dataprivacy_ctxexpire id.
* @return bool True on success.
*/
public static function delete_expired_context($id) {
$expiredcontext = new expired_context($id);
return $expiredcontext->delete();
}
/**
* Updates the status of an expired context.
*

View File

@ -21,7 +21,9 @@
* @copyright 2018 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
defined('MOODLE_INTERNAL') || die();
use core\persistent;

View File

@ -0,0 +1,63 @@
<?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/>.
/**
* Event observers supported by this module.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy\event;
use \tool_dataprivacy\api;
use \tool_dataprivacy\data_request;
defined('MOODLE_INTERNAL') || die();
/**
* Event observers supported by this module.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class user_deleted_observer {
/**
* Create user data deletion request when the user is deleted.
*
* @param \core\event\user_deleted $event
*/
public static function create_delete_data_request(\core\event\user_deleted $event) {
// Automatic creation of deletion requests must be enabled.
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
$requesttypes = [api::DATAREQUEST_TYPE_DELETE];
$requeststatuses = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DELETED];
$hasongoingdeleterequests = api::has_ongoing_request($event->objectid, $requesttypes[0]);
$hascompleteddeleterequest = (api::get_data_requests_count($event->objectid, $requeststatuses,
$requesttypes) > 0) ? true : false;
if (!$hasongoingdeleterequests && !$hascompleteddeleterequest) {
api::create_data_request($event->objectid, $requesttypes[0],
get_string('datarequestcreatedupondelete', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO);
}
}
}
}

View File

@ -0,0 +1,149 @@
<?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/>.
/**
* Expired contexts manager for CONTEXT_USER.
*
* @package tool_dataprivacy
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_dataprivacy;
use core_privacy\manager;
defined('MOODLE_INTERNAL') || die();
/**
* Expired contexts manager for CONTEXT_USER.
*
* @copyright 2018 David Monllao
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class expired_user_contexts extends \tool_dataprivacy\expired_contexts_manager {
/**
* Only user level.
*
* @return int[]
*/
protected function get_context_levels() {
return [CONTEXT_USER];
}
/**
* Returns the user context instances that are expired.
*
* @return \stdClass[]
*/
protected function get_expired_contexts() {
global $DB;
// Including context info + last login timestamp.
$fields = 'ctx.id AS id, ' . \context_helper::get_preload_record_columns_sql('ctx');
$purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
// Calculate what is considered expired according to the context level effective purpose (= now + retention period).
$expiredtime = new \DateTime();
$retention = new \DateInterval($purpose->get('retentionperiod'));
$expiredtime->sub($retention);
$sql = "SELECT $fields FROM {context} ctx
JOIN {user} u ON ctx.contextlevel = ? AND ctx.instanceid = u.id
LEFT JOIN {tool_dataprivacy_ctxexpired} expiredctx ON ctx.id = expiredctx.contextid
WHERE u.lastaccess <= ? AND u.lastaccess > 0 AND expiredctx.id IS NULL
ORDER BY ctx.path, ctx.contextlevel ASC";
$possiblyexpired = $DB->get_recordset_sql($sql, [CONTEXT_USER, $expiredtime->getTimestamp()]);
$expiredcontexts = [];
foreach ($possiblyexpired as $record) {
\context_helper::preload_from_record($record);
// No strict checking as the context may already be deleted (e.g. we just deleted a course,
// module contexts below it will not exist).
$context = \context::instance_by_id($record->id, false);
if (!$context) {
continue;
}
if (is_siteadmin($context->instanceid)) {
continue;
}
$courses = enrol_get_users_courses($context->instanceid, false, ['enddate']);
foreach ($courses as $course) {
if (!$course->enddate) {
// We can not know it what is going on here, so we prefer to be conservative.
continue 2;
}
if ($course->enddate >= time()) {
// Future or ongoing course.
continue 2;
}
}
$expiredcontexts[$context->id] = $context;
}
return $expiredcontexts;
}
/**
* Deletes user data from the provided context.
*
* Overwritten to delete the user.
*
* @param manager $privacymanager
* @param expired_context $expiredctx
* @return \context|false
*/
protected function delete_expired_context(manager $privacymanager, expired_context $expiredctx) {
$context = \context::instance_by_id($expiredctx->get('contextid'), IGNORE_MISSING);
if (!$context) {
return false;
}
if (!PHPUNIT_TEST) {
mtrace('Deleting context ' . $context->id . ' - ' .
shorten_text($context->get_context_name(true, true)));
}
// To ensure that all user data is deleted, instead of deleting by context, we run through and collect any stray
// contexts for the user that may still exist and call delete_data_for_user().
$user = \core_user::get_user($context->instanceid, '*', MUST_EXIST);
$approvedlistcollection = new \core_privacy\local\request\contextlist_collection($user->id);
$contextlistcollection = $privacymanager->get_contexts_for_userid($user->id);
foreach ($contextlistcollection as $contextlist) {
$approvedlistcollection->add_contextlist(new \core_privacy\local\request\approved_contextlist(
$user,
$contextlist->get_component(),
$contextlist->get_contextids()
));
}
$privacymanager->delete_data_for_user($approvedlistcollection);
api::set_expired_context_status($expiredctx, expired_context::STATUS_CLEANED);
// Delete the user.
delete_user($user);
return $context;
}
}

View File

@ -27,6 +27,7 @@ defined('MOODLE_INTERNAL') || die();
use coding_exception;
use moodle_exception;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;
/**
* Class containing helper functions for the data privacy tool.
@ -44,6 +45,9 @@ class helper {
/** Filter constant associated with the request status filter. */
const FILTER_STATUS = 2;
/** Filter constant associated with the request creation filter. */
const FILTER_CREATION = 3;
/** The request filters preference key. */
const PREF_REQUEST_FILTERS = 'tool_dataprivacy_request-filters';
@ -145,6 +149,34 @@ class helper {
];
}
/**
* Retrieves the human-readable value of a data request creation method.
*
* @param int $creation The request creation method.
* @return string
* @throws moodle_exception
*/
public static function get_request_creation_method_string($creation) {
$creationmethods = self::get_request_creation_methods();
if (!isset($creationmethods[$creation])) {
throw new moodle_exception('errorinvalidrequestcreationmethod', 'tool_dataprivacy');
}
return $creationmethods[$creation];
}
/**
* Returns the key value-pairs of request creation method code and string value.
*
* @return array
*/
public static function get_request_creation_methods() {
return [
data_request::DATAREQUEST_CREATION_MANUAL => get_string('creationmanual', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO => get_string('creationauto', 'tool_dataprivacy'),
];
}
/**
* Get the users that a user can make data request for.
*
@ -199,6 +231,10 @@ class helper {
'name' => get_string('requeststatus', 'tool_dataprivacy'),
'options' => self::get_request_statuses()
],
self::FILTER_CREATION => (object)[
'name' => get_string('requestcreation', 'tool_dataprivacy'),
'options' => self::get_request_creation_methods()
],
];
$options = [];
foreach ($filters as $category => $filtercategory) {

View File

@ -74,15 +74,17 @@ class data_requests_table extends table_sql {
* @param int $userid The user ID
* @param int[] $statuses
* @param int[] $types
* @param int[] $creationmethods
* @param bool $manage
* @throws coding_exception
*/
public function __construct($userid = 0, $statuses = [], $types = [], $manage = false) {
public function __construct($userid = 0, $statuses = [], $types = [], $creationmethods = [], $manage = false) {
parent::__construct('data-requests-table');
$this->userid = $userid;
$this->statuses = $statuses;
$this->types = $types;
$this->creationmethods = $creationmethods;
$this->manage = $manage;
$checkboxattrs = [
@ -273,11 +275,12 @@ class data_requests_table extends table_sql {
$sort = $this->get_sql_sort();
// Get data requests from the given conditions.
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types, $sort,
$this->get_page_start(), $this->get_page_size());
$datarequests = api::get_data_requests($this->userid, $this->statuses, $this->types,
$this->creationmethods, $sort, $this->get_page_start(), $this->get_page_size());
// Count data requests from the given conditions.
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types);
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types,
$this->creationmethods);
$this->pagesize($pagesize, $total);
$this->rawdata = [];

View File

@ -167,6 +167,8 @@ class provider implements
$data->type = tool_helper::get_shortened_request_type_string($record->type);
// Status.
$data->status = tool_helper::get_request_status_string($record->status);
// Creation method.
$data->creationmethod = tool_helper::get_request_creation_method_string($record->creationmethod);
// Comments.
$data->comments = $record->comments;
// The DPO's comment about this request.
@ -234,6 +236,10 @@ class provider implements
$option->category = get_string('requeststatus', 'tool_dataprivacy');
$option->name = tool_helper::get_request_status_string($value);
break;
case tool_helper::FILTER_CREATION:
$option->category = get_string('requestcreation', 'tool_dataprivacy');
$option->name = tool_helper::get_request_creation_method_string($value);
break;
}
$descriptions[] = get_string('filteroption', 'tool_dataprivacy', $option);
}

View File

@ -57,8 +57,10 @@ class delete_existing_deleted_users extends scheduled_task {
public function execute() {
global $DB;
// Select all deleted users that do not have any delete data requests created for them.
$sql = "SELECT DISTINCT(u.id)
// Automatic creation of deletion requests must be enabled.
if (get_config('tool_dataprivacy', 'automaticdeletionrequests')) {
// Select all deleted users that do not have any delete data requests created for them.
$sql = "SELECT DISTINCT(u.id)
FROM {user} u
LEFT JOIN {tool_dataprivacy_request} r
ON u.id = r.userid
@ -66,23 +68,24 @@ class delete_existing_deleted_users extends scheduled_task {
AND (r.id IS NULL
OR r.type != ?)";
$params = [
1,
api::DATAREQUEST_TYPE_DELETE
];
$params = [
1,
api::DATAREQUEST_TYPE_DELETE
];
$deletedusers = $DB->get_records_sql($sql, $params);
$createdrequests = 0;
$deletedusers = $DB->get_records_sql($sql, $params);
$createdrequests = 0;
foreach ($deletedusers as $user) {
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE,
foreach ($deletedusers as $user) {
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE,
get_string('datarequestcreatedfromscheduledtask', 'tool_dataprivacy'),
data_request::DATAREQUEST_CREATION_AUTO);
$createdrequests++;
}
$createdrequests++;
}
if ($createdrequests > 0) {
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
if ($createdrequests > 0) {
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
}
}
}
}

View File

@ -55,6 +55,7 @@ if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
$types = [];
$statuses = [];
$creationmethods = [];
foreach ($filtersapplied as $filter) {
list($category, $value) = explode(':', $filter);
switch($category) {
@ -64,10 +65,13 @@ if (\tool_dataprivacy\api::is_site_dpo($USER->id)) {
case \tool_dataprivacy\local\helper::FILTER_STATUS:
$statuses[] = $value;
break;
case \tool_dataprivacy\local\helper::FILTER_CREATION:
$creationmethods[] = $value;
break;
}
}
$table = new \tool_dataprivacy\output\data_requests_table(0, $statuses, $types, true);
$table = new \tool_dataprivacy\output\data_requests_table(0, $statuses, $types, $creationmethods, true);
if (!empty($perpage)) {
set_user_preference(\tool_dataprivacy\local\helper::PREF_REQUEST_PERPAGE, $perpage);
} else {

View File

@ -0,0 +1,32 @@
<?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/>.
/**
* This file defines observers needed by the plugin.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$observers = [
[
'eventname' => '\core\event\user_deleted',
'callback' => '\tool_dataprivacy\event\user_deleted_observer::create_delete_data_request',
],
];

View File

@ -252,10 +252,12 @@ function xmldb_tool_dataprivacy_upgrade($oldversion) {
// Define field sensitivedatareasons to be added to tool_dataprivacy_purpose.
$table = new xmldb_table('tool_dataprivacy_request');
$field = new xmldb_field('creationmethod', XMLDB_TYPE_INTEGER, 10, null, XMLDB_NOTNULL, null, 0, 'timemodified');
// Conditionally launch add field sensitivedatareasons.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Dataprivacy savepoint reached.
upgrade_plugin_savepoint(true, 2018100406, 'tool', 'dataprivacy');
}

View File

@ -32,6 +32,8 @@ $string['addnewdefaults'] = 'Add a new module default';
$string['addpurpose'] = 'Add purpose';
$string['approve'] = 'Approve';
$string['approverequest'] = 'Approve request';
$string['automaticdeletionrequests'] = 'Create automatic data deletion requests';
$string['automaticdeletionrequests_desc'] = 'If enabled, automatic delete data request will be created upon user deletion or for each existing deleted user which data was not fully deleted.';
$string['bulkapproverequests'] = 'Approve requests';
$string['bulkdenyrequests'] = 'Deny requests';
$string['cachedef_purpose'] = 'Data purposes';
@ -68,6 +70,8 @@ $string['contactdpoviaprivacypolicy'] = 'Please contact the privacy officer as d
$string['createcategory'] = 'Create data category';
$string['createnewdatarequest'] = 'Create a new data request';
$string['createpurpose'] = 'Create data purpose';
$string['creationauto'] = 'Automatically';
$string['creationmanual'] = 'Manually';
$string['datadeletion'] = 'Data deletion';
$string['datadeletionpagehelp'] = 'Data for which the retention period has expired are listed here. Please review and confirm data deletion, which will then be executed by the "Delete expired contexts" scheduled task.';
$string['dataprivacy:makedatarequestsforchildren'] = 'Make data requests for minors';
@ -82,6 +86,7 @@ $string['dataretentionsummary'] = 'Data retention summary';
$string['datarequestcreatedforuser'] = 'Data request created for {$a}';
$string['datarequestcreatedfromscheduledtask'] = 'Automatically created from a scheduled task (pre-existing deleted user).';
$string['datarequestemailsubject'] = 'Data request: {$a}';
$string['datarequestcreatedupondelete'] = 'Automatically created upon user deletion.';
$string['datarequests'] = 'Data requests';
$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
$string['daterequested'] = 'Date requested';
@ -117,6 +122,7 @@ $string['editpurposes'] = 'Edit purposes';
$string['effectiveretentionperiodcourse'] = '{$a} (after the course end date)';
$string['effectiveretentionperioduser'] = '{$a} (since the last time the user accessed the site)';
$string['emailsalutation'] = 'Dear {$a},';
$string['errorinvalidrequestcreationmethod'] = 'Invalid request creation method!';
$string['errorinvalidrequeststatus'] = 'Invalid request status!';
$string['errorinvalidrequesttype'] = 'Invalid request type!';
$string['errornocapabilitytorequestforothers'] = 'User {$a->requestedby} doesn\'t have the capability to make a data request on behalf of user {$a->userid}';
@ -235,6 +241,7 @@ $string['requestby'] = 'Requested by';
$string['requestbydetail'] = 'Requested by:';
$string['requestcomments'] = 'Comments';
$string['requestcomments_help'] = 'This box enables you to enter any further details about your data request.';
$string['requestcreation'] = 'Creation';
$string['requestdenied'] = 'The request has been denied';
$string['requestemailintro'] = 'You have received a data request:';
$string['requestfor'] = 'User';

View File

@ -55,7 +55,7 @@ $PAGE->set_title($title);
echo $OUTPUT->header();
echo $OUTPUT->heading($title);
$requests = tool_dataprivacy\api::get_data_requests($USER->id, [], [], 'timecreated DESC');
$requests = tool_dataprivacy\api::get_data_requests($USER->id, [], [], [], 'timecreated DESC');
$requestlist = new tool_dataprivacy\output\my_data_requests_page($requests);
$requestlistoutput = $PAGE->get_renderer('tool_dataprivacy');
echo $requestlistoutput->render($requestlist);

View File

@ -34,6 +34,14 @@ if ($hassiteconfig) {
new lang_string('contactdataprotectionofficer_desc', 'tool_dataprivacy'), 0)
);
// Automatically create delete data request for users upon user deletion.
// Automatically create delete data request for pre-existing deleted users.
// Enabled by default.
$privacysettings->add(new admin_setting_configcheckbox('tool_dataprivacy/automaticdeletionrequests',
new lang_string('automaticdeletionrequests', 'tool_dataprivacy'),
new lang_string('automaticdeletionrequests_desc', 'tool_dataprivacy'), 1)
);
// Set days approved data requests will be accessible. 1 week default.
$privacysettings->add(new admin_setting_configduration('tool_dataprivacy/privacyrequestexpiry',
new lang_string('privacyrequestexpiry', 'tool_dataprivacy'),

View File

@ -53,6 +53,10 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
$this->resetAfterTest();
$this->setAdminUser();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create a user.
$user = $this->getDataGenerator()->create_user();
// Mark the user as deleted.
@ -69,6 +73,35 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
[api::DATAREQUEST_TYPE_DELETE]));
}
/**
* Ensure that a delete data request for pre-existing deleted users
* is not being created when automatic creation of delete data requests is disabled.
*/
public function test_delete_existing_deleted_users_task_automatic_creation_disabled() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Disable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 0, 'tool_dataprivacy');
// Create a user.
$user = $this->getDataGenerator()->create_user();
// Mark the user as deleted.
$user->deleted = 1;
$DB->update_record('user', $user);
// The user should not have a delete data request.
$this->assertCount(0, api::get_data_requests($user->id, [],
[api::DATAREQUEST_TYPE_DELETE]));
$this->execute_task('tool_dataprivacy\task\delete_existing_deleted_users');
// After running the scheduled task, the deleted user should still not have a delete data request.
$this->assertCount(0, api::get_data_requests($user->id, [],
[api::DATAREQUEST_TYPE_DELETE]));
}
/**
* Ensure that a delete data request for pre-existing deleted users
* is created when there are existing non-delete data requests
@ -79,6 +112,10 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
$this->resetAfterTest();
$this->setAdminUser();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create a user.
$user = $this->getDataGenerator()->create_user();
// Create export data request for the user.
@ -106,8 +143,14 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
* for that particular user.
*/
public function test_delete_existing_deleted_users_task_existing_ongoing_delete_data_requests() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create a user.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
@ -120,9 +163,10 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
$this->assertCount(1, api::get_data_requests($user->id,
[api::DATAREQUEST_STATUS_AWAITING_APPROVAL], [api::DATAREQUEST_TYPE_DELETE]));
$this->setAdminUser();
// Delete the user.
delete_user($user);
// Mark the user as deleted.
$user->deleted = 1;
$DB->update_record('user', $user);
// The user should still have the existing ongoing delete data request.
$this->assertCount(1, \tool_dataprivacy\api::get_data_requests($user->id,
[api::DATAREQUEST_STATUS_AWAITING_APPROVAL], [api::DATAREQUEST_TYPE_DELETE]));
@ -142,8 +186,14 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
* for that particular user.
*/
public function test_delete_existing_deleted_users_task_existing_finished_delete_data_requests() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create a user.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
@ -158,9 +208,10 @@ class tool_dataprivacy_task_testcase extends data_privacy_testcase {
// The user should not have an ongoing data requests.
$this->assertFalse(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
$this->setAdminUser();
// Delete the user.
delete_user($user);
// Mark the user as deleted.
$user->deleted = 1;
$DB->update_record('user', $user);
// The user should still have the existing finished delete data request.
$this->assertCount(1, \tool_dataprivacy\api::get_data_requests($user->id,
[api::DATAREQUEST_STATUS_CANCELLED], [api::DATAREQUEST_TYPE_DELETE]));

View File

@ -0,0 +1,203 @@
<?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/>.
/**
* Tests for the event observer.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use \tool_dataprivacy\event\user_deleted_observer;
use \tool_dataprivacy\api;
/**
* Event observer test.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski <mihail@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tool_dataprivacy_user_deleted_observer_testcase extends advanced_testcase {
/**
* Ensure that a delete data request is created upon user deletion.
*/
public function test_create_delete_data_request() {
$this->resetAfterTest();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create another user who is not a DPO.
$user = $this->getDataGenerator()->create_user();
$event = $this->trigger_delete_user_event($user);
user_deleted_observer::create_delete_data_request($event);
// Validate that delete data request has been created.
$this->assertTrue(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
}
/**
* Ensure that a delete data request is not created upon user deletion if automatic creation of
* delete data requests is disabled.
*/
public function test_create_delete_data_request_automatic_creation_disabled() {
$this->resetAfterTest();
// Disable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 0, 'tool_dataprivacy');
// Create another user who is not a DPO.
$user = $this->getDataGenerator()->create_user();
$event = $this->trigger_delete_user_event($user);
user_deleted_observer::create_delete_data_request($event);
// Validate that delete data request has been created.
$this->assertFalse(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
}
/**
* Ensure that a delete data request is being created upon user deletion
* if an ongoing export data request (or any other except delete data request) for that user already exists.
*/
public function test_create_delete_data_request_export_data_request_preexists() {
$this->resetAfterTest();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create another user who is not a DPO.
$user = $this->getDataGenerator()->create_user();
// Create a delete data request for $user.
api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT);
// Validate that delete data request has been created.
$this->assertTrue(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_EXPORT));
$this->assertEquals(0, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
$event = $this->trigger_delete_user_event($user);
user_deleted_observer::create_delete_data_request($event);
// Validate that delete data request has been created.
$this->assertEquals(1, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
}
/**
* Ensure that a delete data request is not being created upon user deletion
* if an ongoing delete data request for that user already exists.
*/
public function test_create_delete_data_request_ongoing_delete_data_request_preexists() {
$this->resetAfterTest();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create another user who is not a DPO.
$user = $this->getDataGenerator()->create_user();
// Create a delete data request for $user.
api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
// Validate that delete data request has been created.
$this->assertTrue(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
$event = $this->trigger_delete_user_event($user);
user_deleted_observer::create_delete_data_request($event);
// Validate that additional delete data request has not been created.
$this->assertEquals(1, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
}
/**
* Ensure that a delete data request is being created upon user deletion
* if a finished delete data request (excluding complete) for that user already exists.
*/
public function test_create_delete_data_request_canceled_delete_data_request_preexists() {
$this->resetAfterTest();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create another user who is not a DPO.
$user = $this->getDataGenerator()->create_user();
// Create a delete data request for $user.
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
$requestid = $datarequest->get('id');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_CANCELLED);
// Validate that delete data request has been created and the status has been updated to 'Canceled'.
$this->assertEquals(1, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
$this->assertFalse(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
$event = $this->trigger_delete_user_event($user);
user_deleted_observer::create_delete_data_request($event);
// Validate that additional delete data request has been created.
$this->assertEquals(2, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
$this->assertTrue(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
}
/**
* Ensure that a delete data request is being created upon user deletion
* if a completed delete data request for that user already exists.
*/
public function test_create_delete_data_request_completed_delete_data_request_preexists() {
$this->resetAfterTest();
// Enable automatic creation of delete data requests.
set_config('automaticdeletionrequests', 1, 'tool_dataprivacy');
// Create another user who is not a DPO.
$user = $this->getDataGenerator()->create_user();
// Create a delete data request for $user.
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_DELETE);
$requestid = $datarequest->get('id');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
// Validate that delete data request has been created and the status has been updated to 'Completed'.
$this->assertEquals(1, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
$this->assertFalse(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
$event = $this->trigger_delete_user_event($user);
user_deleted_observer::create_delete_data_request($event);
// Validate that additional delete data request has not been created.
$this->assertEquals(1, api::get_data_requests_count($user->id, [], [api::DATAREQUEST_TYPE_DELETE]));
$this->assertFalse(api::has_ongoing_request($user->id, api::DATAREQUEST_TYPE_DELETE));
}
/**
* Helper to trigger and capture the delete user event.
*
* @param object $user The user object.
* @return \core\event\user_deleted $event The returned event.
*/
private function trigger_delete_user_event($user) {
$sink = $this->redirectEvents();
delete_user($user);
$events = $sink->get_events();
$sink->close();
$event = reset($events);
// Validate event data.
$this->assertInstanceOf('\core\event\user_deleted', $event);
return $event;
}
}