Skip to content

Commit

Permalink
MDL-62563 privacy: Create delete data request for existing deleted users
Browse files Browse the repository at this point in the history
  • Loading branch information
Mihail Geshoski committed Nov 5, 2018
1 parent e3eb339 commit d2481db
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 37 deletions.
19 changes: 9 additions & 10 deletions admin/tool/dataprivacy/classes/api.php
Expand Up @@ -227,25 +227,24 @@ public static function is_site_dpo($userid) {
* @param int $foruser The user whom the request is being made for.
* @param int $type The request type.
* @param string $comments Request comments.
* @param int $creationmethod The creation method of the data request.
* @return data_request
* @throws invalid_persistent_exception
* @throws coding_exception
*/
public static function create_data_request($foruser, $type, $comments = '') {
global $USER;
public static function create_data_request($foruser, $type, $comments = '',
$creationmethod = data_request::DATAREQUEST_CREATION_MANUAL) {
global $USER, $ADMIN;

$datarequest = new data_request();
// The user the request is being made for.
$datarequest->set('userid', $foruser);

$requestinguser = $USER->id;
// Check when the user is making a request on behalf of another.
if ($requestinguser != $foruser) {
if (self::is_site_dpo($requestinguser)) {
// The user making the request is a DPO. Should be fine.
$datarequest->set('dpo', $requestinguser);
}
}
// The cron is considered to be a guest user when it creates a data request.
// NOTE: This should probably be changed. We should leave the default value for $requestinguser if
// the request is not explicitly created by a specific user.
$requestinguser = (isguestuser() && $creationmethod == data_request::DATAREQUEST_CREATION_AUTO) ?
$ADMIN->id : $USER->id;
// The user making the request.
$datarequest->set('requestedby', $requestinguser);
// Set status.
Expand Down
14 changes: 14 additions & 0 deletions admin/tool/dataprivacy/classes/data_request.php
Expand Up @@ -37,6 +37,12 @@ class data_request extends persistent {
/** The table name this persistent object maps to. */
const TABLE = 'tool_dataprivacy_request';

/** Data request created manually. */
const DATAREQUEST_CREATION_MANUAL = 0;

/** Data request created automatically. */
const DATAREQUEST_CREATION_AUTO = 1;

/**
* Return the definition of the properties of this model.
*
Expand Down Expand Up @@ -111,6 +117,14 @@ protected static function define_properties() {
'type' => PARAM_INT,
'default' => FORMAT_PLAIN
],
'creationmethod' => [
'default' => self::DATAREQUEST_CREATION_MANUAL,
'choices' => [
self::DATAREQUEST_CREATION_MANUAL,
self::DATAREQUEST_CREATION_AUTO
],
'type' => PARAM_INT
],
];
}

Expand Down
@@ -0,0 +1,88 @@
<?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/>.

/**
* Scheduled task to create delete data request for pre-existing deleted users.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace tool_dataprivacy\task;

use core\task\scheduled_task;
use tool_dataprivacy\api;
use tool_dataprivacy\data_request;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');

/**
* Scheduled task to create delete data request for pre-existing deleted users.
*
* @package tool_dataprivacy
* @copyright 2018 Mihail Geshoski
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delete_existing_deleted_users extends scheduled_task {

/**
* Returns the task name.
*
* @return string
*/
public function get_name() {
return get_string('deleteexistingdeleteduserstask', 'tool_dataprivacy');
}

/**
* Run the task to delete expired data request files and update request statuses.
*
*/
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)
FROM {user} u
LEFT JOIN {tool_dataprivacy_request} r
ON u.id = r.userid
WHERE u.deleted = ?
AND (r.id IS NULL
OR r.type != ?)";

$params = [
1,
api::DATAREQUEST_TYPE_DELETE
];

$deletedusers = $DB->get_records_sql($sql, $params);
$createdrequests = 0;

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++;
}

if ($createdrequests > 0) {
mtrace($createdrequests . ' delete data request(s) created for existing deleted users');
}
}
}
21 changes: 0 additions & 21 deletions admin/tool/dataprivacy/classes/task/initiate_data_request_task.php
Expand Up @@ -70,27 +70,6 @@ public function execute() {
return;
}

$requestedby = $datarequest->get('requestedby');
$valid = true;
$comment = '';
$foruser = $datarequest->get('userid');
if ($foruser != $requestedby) {
if (!$valid = api::can_create_data_request_for_user($foruser, $requestedby)) {
$params = (object)[
'requestedby' => $requestedby,
'userid' => $foruser
];
$comment = get_string('errornocapabilitytorequestforothers', 'tool_dataprivacy', $params);
mtrace($comment);
}
}
// Reject the request outright if it's invalid.
if (!$valid) {
$dpo = $datarequest->get('dpo');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_REJECTED, $dpo, $comment);
return;
}

// Update the status of this request as pre-processing.
mtrace('Generating the contexts containing personal data for the user...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PREPROCESSING);
Expand Down
22 changes: 18 additions & 4 deletions admin/tool/dataprivacy/classes/task/process_data_request_task.php
Expand Up @@ -76,14 +76,21 @@ public function execute() {

// Get the user details now. We might not be able to retrieve it later if it's a deletion processing.
$foruser = core_user::get_user($request->userid);
$usercontext = \context_user::instance($foruser->id);

// Update the status of this request as pre-processing.
mtrace('Processing request...');
api::update_request_status($requestid, api::DATAREQUEST_STATUS_PROCESSING);
$completestatus = api::DATAREQUEST_STATUS_COMPLETE;

if ($request->type == api::DATAREQUEST_TYPE_EXPORT) {
// Get the user context.
$usercontext = \context_user::instance($foruser->id, IGNORE_MISSING);
if (!$usercontext) {
mtrace("Request {$requestid} cannot be processed due to a missing user context instance for the user
with ID {$foruser->id}. Skipping...");
return;
}

// Get the collection of approved_contextlist objects needed for core_privacy data export.
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);

Expand Down Expand Up @@ -191,12 +198,19 @@ public function execute() {

// Send message to the user involved.
if ($notifyuser) {
$messagesent = false;
if ($emailonly) {
email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
// Do not sent an email if the user has been deleted. The user email has been previously deleted.
if (!$foruser->deleted) {
$messagesent = email_to_user($foruser, $dpo, $subject, $message->fullmessage, $messagehtml);
}
} else {
message_send($message);
$messagesent = message_send($message);
}

if ($messagesent) {
mtrace('Message sent to user: ' . $messagetextdata['username']);
}
mtrace('Message sent to user: ' . $messagetextdata['username']);
}

// Send to requester as well in some circumstances.
Expand Down
1 change: 1 addition & 0 deletions admin/tool/dataprivacy/db/install.xml
Expand Up @@ -19,6 +19,7 @@
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The user who created/modified this request object"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time this data request was created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The last time this data request was updated"/>
<FIELD NAME="creationmethod" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The type of the creation method of the data request"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
8 changes: 8 additions & 0 deletions admin/tool/dataprivacy/db/tasks.php
Expand Up @@ -50,5 +50,13 @@
'day' => '*',
'dayofweek' => '*',
'month' => '*'
), array(
'classname' => 'tool_dataprivacy\task\delete_existing_deleted_users',
'blocking' => 0,
'minute' => 'R',
'hour' => 'R',
'day' => '*',
'dayofweek' => '*',
'month' => '*'
),
);
12 changes: 12 additions & 0 deletions admin/tool/dataprivacy/db/upgrade.php
Expand Up @@ -248,5 +248,17 @@ function xmldb_tool_dataprivacy_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2018051411, 'tool', 'dataprivacy');
}

if ($oldversion < 2018051413) {
// 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, 2018051413, 'tool', 'dataprivacy');
}

return true;
}
2 changes: 2 additions & 0 deletions admin/tool/dataprivacy/lang/en/tool_dataprivacy.php
Expand Up @@ -80,6 +80,7 @@
$string['dataretentionexplanation'] = 'This summary shows the default categories and purposes for retaining user data. Certain areas may have more specific categories and purposes than those listed here.';
$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['datarequests'] = 'Data requests';
$string['datecomment'] = '[{$a->date}]: ' . PHP_EOL . ' {$a->comment}';
Expand All @@ -93,6 +94,7 @@
$string['deletedefaultsconfirmation'] = 'Are you sure you want to delete the default category and purpose for {$a} modules?';
$string['deleteexpiredcontextstask'] = 'Delete expired contexts';
$string['deleteexpireddatarequeststask'] = 'Delete expired data request export files';
$string['deleteexistingdeleteduserstask'] = 'Create delete data request for pre-existing deleted users';
$string['deletemyaccount'] = 'Delete my account';
$string['deletepurpose'] = 'Delete purpose';
$string['deletepurposetext'] = 'Are you sure you want to delete the purpose \'{$a}\'?';
Expand Down
1 change: 0 additions & 1 deletion admin/tool/dataprivacy/tests/api_test.php
Expand Up @@ -608,7 +608,6 @@ public function test_create_data_request_by_dpo() {
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
$this->assertEquals($user->id, $datarequest->get('userid'));
$this->assertEquals($USER->id, $datarequest->get('requestedby'));
$this->assertEquals($USER->id, $datarequest->get('dpo'));
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
$this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
$this->assertEquals($comment, $datarequest->get('comments'));
Expand Down
2 changes: 1 addition & 1 deletion admin/tool/dataprivacy/version.php
Expand Up @@ -24,6 +24,6 @@

defined('MOODLE_INTERNAL') || die;

$plugin->version = 2018051412;
$plugin->version = 2018051413;
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
$plugin->component = 'tool_dataprivacy';

0 comments on commit d2481db

Please sign in to comment.