mirror of
https://github.com/moodle/moodle.git
synced 2025-03-22 08:30:04 +01:00
Merge branch 'MDL-62660-master' of git://github.com/junpataleta/moodle
This commit is contained in:
commit
440dad627f
@ -76,7 +76,7 @@ class api {
|
||||
/** The request is now being processed. */
|
||||
const DATAREQUEST_STATUS_PROCESSING = 4;
|
||||
|
||||
/** Data request completed. */
|
||||
/** Information/other request completed. */
|
||||
const DATAREQUEST_STATUS_COMPLETE = 5;
|
||||
|
||||
/** Data request cancelled by the user. */
|
||||
@ -85,6 +85,15 @@ class api {
|
||||
/** Data request rejected by the DPO. */
|
||||
const DATAREQUEST_STATUS_REJECTED = 7;
|
||||
|
||||
/** Data request download ready. */
|
||||
const DATAREQUEST_STATUS_DOWNLOAD_READY = 8;
|
||||
|
||||
/** Data request expired. */
|
||||
const DATAREQUEST_STATUS_EXPIRED = 9;
|
||||
|
||||
/** Data delete request completed, account is removed. */
|
||||
const DATAREQUEST_STATUS_DELETED = 10;
|
||||
|
||||
/**
|
||||
* Determines whether the user can contact the site's Data Protection Officer via Moodle.
|
||||
*
|
||||
@ -319,6 +328,18 @@ class api {
|
||||
}
|
||||
}
|
||||
|
||||
// If any are due to expire, expire them and re-fetch updated data.
|
||||
if (empty($statuses)
|
||||
|| in_array(self::DATAREQUEST_STATUS_DOWNLOAD_READY, $statuses)
|
||||
|| in_array(self::DATAREQUEST_STATUS_EXPIRED, $statuses)) {
|
||||
$expiredrequests = data_request::get_expired_requests($userid);
|
||||
|
||||
if (!empty($expiredrequests)) {
|
||||
data_request::expire($expiredrequests);
|
||||
$results = self::get_data_requests($userid, $statuses, $types, $sort, $offset, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
@ -400,6 +421,9 @@ class api {
|
||||
self::DATAREQUEST_STATUS_COMPLETE,
|
||||
self::DATAREQUEST_STATUS_CANCELLED,
|
||||
self::DATAREQUEST_STATUS_REJECTED,
|
||||
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
self::DATAREQUEST_STATUS_EXPIRED,
|
||||
self::DATAREQUEST_STATUS_DELETED,
|
||||
];
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($nonpendingstatuses, SQL_PARAMS_NAMED);
|
||||
$select = 'type = :type AND userid = :userid AND status NOT ' . $insql;
|
||||
@ -423,6 +447,9 @@ class api {
|
||||
self::DATAREQUEST_STATUS_COMPLETE,
|
||||
self::DATAREQUEST_STATUS_CANCELLED,
|
||||
self::DATAREQUEST_STATUS_REJECTED,
|
||||
self::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
self::DATAREQUEST_STATUS_EXPIRED,
|
||||
self::DATAREQUEST_STATUS_DELETED,
|
||||
];
|
||||
|
||||
return !in_array($status, $finalstatuses);
|
||||
|
@ -85,6 +85,9 @@ class data_request extends persistent {
|
||||
api::DATAREQUEST_STATUS_COMPLETE,
|
||||
api::DATAREQUEST_STATUS_CANCELLED,
|
||||
api::DATAREQUEST_STATUS_REJECTED,
|
||||
api::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
api::DATAREQUEST_STATUS_EXPIRED,
|
||||
api::DATAREQUEST_STATUS_DELETED,
|
||||
],
|
||||
'type' => PARAM_INT
|
||||
],
|
||||
@ -110,4 +113,101 @@ class data_request extends persistent {
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a completed data export request has expired.
|
||||
* The response will be valid regardless of the expiry scheduled task having run.
|
||||
*
|
||||
* @param data_request $request the data request object whose expiry will be checked.
|
||||
* @return bool true if the request has expired.
|
||||
*/
|
||||
public static function is_expired(data_request $request) {
|
||||
$result = false;
|
||||
|
||||
// Only export requests expire.
|
||||
if ($request->get('type') == api::DATAREQUEST_TYPE_EXPORT) {
|
||||
switch ($request->get('status')) {
|
||||
// Expired requests are obviously expired.
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$result = true;
|
||||
break;
|
||||
// Complete requests are expired if the expiry time has elapsed.
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
if ($expiryseconds > 0 && time() >= ($request->get('timemodified') + $expiryseconds)) {
|
||||
$result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetch completed data requests which are due to expire.
|
||||
*
|
||||
* @param int $userid Optional user ID to filter by.
|
||||
*
|
||||
* @return array Details of completed requests which are due to expire.
|
||||
*/
|
||||
public static function get_expired_requests($userid = 0) {
|
||||
global $DB;
|
||||
|
||||
$expiryseconds = get_config('tool_dataprivacy', 'privacyrequestexpiry');
|
||||
$expirytime = strtotime("-{$expiryseconds} second");
|
||||
$table = self::TABLE;
|
||||
$sqlwhere = 'type = :export_type AND status = :completestatus AND timemodified <= :expirytime';
|
||||
$params = array(
|
||||
'export_type' => api::DATAREQUEST_TYPE_EXPORT,
|
||||
'completestatus' => api::DATAREQUEST_STATUS_DOWNLOAD_READY,
|
||||
'expirytime' => $expirytime,
|
||||
);
|
||||
$sort = 'id';
|
||||
$fields = 'id, userid';
|
||||
|
||||
// Filter by user ID if specified.
|
||||
if ($userid > 0) {
|
||||
$sqlwhere .= ' AND (userid = :userid OR requestedby = :requestedby)';
|
||||
$params['userid'] = $userid;
|
||||
$params['requestedby'] = $userid;
|
||||
}
|
||||
|
||||
return $DB->get_records_select_menu($table, $sqlwhere, $params, $sort, $fields, 0, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire a given set of data requests.
|
||||
* Update request status and delete the files.
|
||||
*
|
||||
* @param array $expiredrequests [requestid => userid]
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function expire($expiredrequests) {
|
||||
global $DB;
|
||||
|
||||
$ids = array_keys($expiredrequests);
|
||||
|
||||
if (count($ids) > 0) {
|
||||
list($insql, $inparams) = $DB->get_in_or_equal($ids);
|
||||
$initialparams = array(api::DATAREQUEST_STATUS_EXPIRED, time());
|
||||
$params = array_merge($initialparams, $inparams);
|
||||
|
||||
$update = "UPDATE {" . self::TABLE . "}
|
||||
SET status = ?, timemodified = ?
|
||||
WHERE id $insql";
|
||||
|
||||
if ($DB->execute($update, $params)) {
|
||||
$fs = get_file_storage();
|
||||
|
||||
foreach ($expiredrequests as $id => $userid) {
|
||||
$usercontext = \context_user::instance($userid);
|
||||
$fs->delete_area_files($usercontext->id, 'tool_dataprivacy', 'export', $id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class data_request_exporter extends persistent_exporter {
|
||||
|
||||
switch ($this->persistent->get('status')) {
|
||||
case api::DATAREQUEST_STATUS_PENDING:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
// Request can be manually completed for general enquiry requests.
|
||||
$values['canmarkcomplete'] = $requesttype == api::DATAREQUEST_TYPE_OTHERS;
|
||||
break;
|
||||
@ -181,6 +181,8 @@ class data_request_exporter extends persistent_exporter {
|
||||
$values['statuslabelclass'] = 'label-info';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_COMPLETE:
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
case api::DATAREQUEST_STATUS_DELETED:
|
||||
$values['statuslabelclass'] = 'label-success';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
@ -189,6 +191,9 @@ class data_request_exporter extends persistent_exporter {
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
$values['statuslabelclass'] = 'label-important';
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$values['statuslabelclass'] = 'label-default';
|
||||
break;
|
||||
}
|
||||
|
||||
return $values;
|
||||
|
@ -117,6 +117,7 @@ class helper {
|
||||
if (!isset($statuses[$status])) {
|
||||
throw new moodle_exception('errorinvalidrequeststatus', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
return $statuses[$status];
|
||||
}
|
||||
|
||||
@ -133,8 +134,11 @@ class helper {
|
||||
api::DATAREQUEST_STATUS_APPROVED => get_string('statusapproved', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_PROCESSING => get_string('statusprocessing', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_COMPLETE => get_string('statuscomplete', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_DOWNLOAD_READY => get_string('statusready', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_EXPIRED => get_string('statusexpired', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_CANCELLED => get_string('statuscancelled', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_REJECTED => get_string('statusrejected', 'tool_dataprivacy'),
|
||||
api::DATAREQUEST_STATUS_DELETED => get_string('statusdeleted', 'tool_dataprivacy'),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ class data_requests_table extends table_sql {
|
||||
/** @var bool Whether this table is being rendered for managing data requests. */
|
||||
protected $manage = false;
|
||||
|
||||
/** @var stdClass[] Array of data request persistents. */
|
||||
/** @var \tool_dataprivacy\data_request[] Array of data request persistents. */
|
||||
protected $datarequests = [];
|
||||
|
||||
/**
|
||||
@ -206,14 +206,14 @@ class data_requests_table extends table_sql {
|
||||
$actiontext = get_string('denyrequest', 'tool_dataprivacy');
|
||||
$actions[] = new action_menu_link_secondary($actionurl, null, $actiontext, $actiondata);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($status == api::DATAREQUEST_STATUS_COMPLETE) {
|
||||
$userid = $data->foruser->id;
|
||||
$usercontext = \context_user::instance($userid, IGNORE_MISSING);
|
||||
if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
|
||||
$actions[] = api::get_download_link($usercontext, $requestid);
|
||||
}
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$userid = $data->foruser->id;
|
||||
$usercontext = \context_user::instance($userid, IGNORE_MISSING);
|
||||
// If user has permission to view download link, show relevant action item.
|
||||
if ($usercontext && api::can_download_data_request_for_user($userid, $data->requestedbyuser->id)) {
|
||||
$actions[] = api::get_download_link($usercontext, $requestid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$actionsmenu = new action_menu($actions);
|
||||
@ -236,19 +236,25 @@ class data_requests_table extends table_sql {
|
||||
public function query_db($pagesize, $useinitialsbar = true) {
|
||||
global $PAGE;
|
||||
|
||||
// Count data requests from the given conditions.
|
||||
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types);
|
||||
$this->pagesize($pagesize, $total);
|
||||
// Set dummy page total until we fetch full result set.
|
||||
$this->pagesize($pagesize, $pagesize + 1);
|
||||
|
||||
$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());
|
||||
|
||||
// Count data requests from the given conditions.
|
||||
$total = api::get_data_requests_count($this->userid, $this->statuses, $this->types);
|
||||
$this->pagesize($pagesize, $total);
|
||||
|
||||
$this->rawdata = [];
|
||||
$context = \context_system::instance();
|
||||
$renderer = $PAGE->get_renderer('tool_dataprivacy');
|
||||
|
||||
foreach ($datarequests as $persistent) {
|
||||
$this->datarequests[$persistent->get('id')] = $persistent;
|
||||
$exporter = new data_request_exporter($persistent, ['context' => $context]);
|
||||
$this->rawdata[] = $exporter->export($renderer);
|
||||
}
|
||||
|
@ -109,13 +109,29 @@ class my_data_requests_page implements renderable, templatable {
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statuscomplete', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
// Show download links only for export-type data requests.
|
||||
$candownload = $type == api::DATAREQUEST_TYPE_EXPORT;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DOWNLOAD_READY:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statusready', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
$candownload = true;
|
||||
|
||||
if ($usercontext) {
|
||||
$candownload = api::can_download_data_request_for_user(
|
||||
$request->get('userid'), $request->get('requestedby'));
|
||||
}
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_DELETED:
|
||||
$item->statuslabelclass = 'label-success';
|
||||
$item->statuslabel = get_string('statusdeleted', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_EXPIRED:
|
||||
$item->statuslabelclass = 'label-default';
|
||||
$item->statuslabel = get_string('statusexpired', 'tool_dataprivacy');
|
||||
$item->statuslabeltitle = get_string('downloadexpireduser', 'tool_dataprivacy');
|
||||
$cancancel = false;
|
||||
break;
|
||||
case api::DATAREQUEST_STATUS_CANCELLED:
|
||||
case api::DATAREQUEST_STATUS_REJECTED:
|
||||
$cancancel = false;
|
||||
|
@ -0,0 +1,67 @@
|
||||
<?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 delete files and update statuses of expired data requests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace tool_dataprivacy\task;
|
||||
|
||||
use coding_exception;
|
||||
use core\task\scheduled_task;
|
||||
use tool_dataprivacy\api;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/dataprivacy/lib.php');
|
||||
|
||||
/**
|
||||
* Scheduled task to delete files and update request statuses once they expire.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class delete_expired_requests extends scheduled_task {
|
||||
|
||||
/**
|
||||
* Returns the task name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('deleteexpireddatarequeststask', 'tool_dataprivacy');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the task to delete expired data request files and update request statuses.
|
||||
*
|
||||
*/
|
||||
public function execute() {
|
||||
$expiredrequests = \tool_dataprivacy\data_request::get_expired_requests();
|
||||
$deletecount = count($expiredrequests);
|
||||
|
||||
if ($deletecount > 0) {
|
||||
\tool_dataprivacy\data_request::expire($expiredrequests);
|
||||
|
||||
mtrace($deletecount . ' expired completed data requests have been deleted');
|
||||
}
|
||||
}
|
||||
}
|
@ -81,6 +81,7 @@ class process_data_request_task extends adhoc_task {
|
||||
// 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 collection of approved_contextlist objects needed for core_privacy data export.
|
||||
@ -105,7 +106,7 @@ class process_data_request_task extends adhoc_task {
|
||||
$filerecord->author = fullname($foruser);
|
||||
// Save somewhere.
|
||||
$thing = $fs->create_file_from_pathname($filerecord, $exportedcontent);
|
||||
|
||||
$completestatus = api::DATAREQUEST_STATUS_DOWNLOAD_READY;
|
||||
} else if ($request->type == api::DATAREQUEST_TYPE_DELETE) {
|
||||
// Get the collection of approved_contextlist objects needed for core_privacy data deletion.
|
||||
$approvedclcollection = api::get_approved_contextlist_collection_for_request($requestpersistent);
|
||||
@ -115,10 +116,11 @@ class process_data_request_task extends adhoc_task {
|
||||
$manager->set_observer(new \tool_dataprivacy\manager_observer());
|
||||
|
||||
$manager->delete_data_for_user($approvedclcollection);
|
||||
$completestatus = api::DATAREQUEST_STATUS_DELETED;
|
||||
}
|
||||
|
||||
// When the preparation of the metadata finishes, update the request status to awaiting approval.
|
||||
api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
|
||||
api::update_request_status($requestid, $completestatus);
|
||||
mtrace('The processing of the user data request has been completed...');
|
||||
|
||||
// Create message to notify the user regarding the processing results.
|
||||
|
@ -42,5 +42,13 @@ $tasks = array(
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
), array(
|
||||
'classname' => 'tool_dataprivacy\task\delete_expired_requests',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => 'R',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
),
|
||||
);
|
||||
|
@ -145,5 +145,31 @@ function xmldb_tool_dataprivacy_upgrade($oldversion) {
|
||||
upgrade_plugin_savepoint(true, 2018051405, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
if ($oldversion < 2018051406) {
|
||||
// Update completed delete requests to new delete status.
|
||||
$query = "UPDATE {tool_dataprivacy_request}
|
||||
SET status = :setstatus
|
||||
WHERE type = :type
|
||||
AND status = :wherestatus";
|
||||
$params = array(
|
||||
'setstatus' => 10, // Request deleted.
|
||||
'type' => 2, // Delete type.
|
||||
'wherestatus' => 5, // Request completed.
|
||||
);
|
||||
|
||||
$DB->execute($query, $params);
|
||||
|
||||
// Update completed data export requests to new download ready status.
|
||||
$params = array(
|
||||
'setstatus' => 8, // Request download ready.
|
||||
'type' => 1, // export type.
|
||||
'wherestatus' => 5, // Request completed.
|
||||
);
|
||||
|
||||
$DB->execute($query, $params);
|
||||
|
||||
upgrade_plugin_savepoint(true, 2018051406, 'tool', 'dataprivacy');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -80,12 +80,14 @@ $string['defaultsinfo'] = 'Default categories and purposes are applied to all ne
|
||||
$string['deletecategory'] = 'Delete "{$a}" category';
|
||||
$string['deletecategorytext'] = 'Are you sure you want to delete "{$a}" category?';
|
||||
$string['deleteexpiredcontextstask'] = 'Delete expired contexts';
|
||||
$string['deleteexpireddatarequeststask'] = 'Delete files from completed data requests that have expired';
|
||||
$string['deletepurpose'] = 'Delete "{$a}" purpose';
|
||||
$string['deletepurposetext'] = 'Are you sure you want to delete "{$a}" purpose?';
|
||||
$string['defaultssaved'] = 'Defaults saved';
|
||||
$string['deny'] = 'Deny';
|
||||
$string['denyrequest'] = 'Deny request';
|
||||
$string['download'] = 'Download';
|
||||
$string['downloadexpireduser'] = 'Download has expired. Submit a new request if you wish to export your personal data.';
|
||||
$string['dporolemapping'] = 'Privacy officer role mapping';
|
||||
$string['dporolemapping_desc'] = 'The privacy officer can manage data requests. The capability tool/dataprivacy:managedatarequests must be allowed for a role to be listed as a privacy officer role mapping option.';
|
||||
$string['editcategories'] = 'Edit categories';
|
||||
@ -192,6 +194,8 @@ $string['privacy:metadata:request:userid'] = 'The ID of the user to whom the req
|
||||
$string['privacy:metadata:request:requestedby'] = 'The ID of the user making the request, if made on behalf of another user.';
|
||||
$string['privacy:metadata:request:dpocomment'] = 'Any comments made by the site\'s privacy officer regarding the request.';
|
||||
$string['privacy:metadata:request:timecreated'] = 'The timestamp indicating when the request was made by the user.';
|
||||
$string['privacyrequestexpiry'] = 'Data request expiry';
|
||||
$string['privacyrequestexpiry_desc'] = 'The amount of time that approved data requests will be available for download before expiring. 0 means no time limit.';
|
||||
$string['protected'] = 'Protected';
|
||||
$string['protectedlabel'] = 'The retention of this data has a higher legal precedent over a user\'s request to be forgotten. This data will only be deleted after the retention period has expired.';
|
||||
$string['purpose'] = 'Purpose';
|
||||
@ -241,7 +245,10 @@ $string['statusapproved'] = 'Approved';
|
||||
$string['statusawaitingapproval'] = 'Awaiting approval';
|
||||
$string['statuscancelled'] = 'Cancelled';
|
||||
$string['statuscomplete'] = 'Complete';
|
||||
$string['statusready'] = 'Download ready';
|
||||
$string['statusdeleted'] = 'Deleted';
|
||||
$string['statusdetail'] = 'Status:';
|
||||
$string['statusexpired'] = 'Expired';
|
||||
$string['statuspreprocessing'] = 'Pre-processing';
|
||||
$string['statusprocessing'] = 'Processing';
|
||||
$string['statuspending'] = 'Pending';
|
||||
|
@ -199,6 +199,11 @@ function tool_dataprivacy_pluginfile($course, $cm, $context, $filearea, $args, $
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make the file unavailable if it has expired.
|
||||
if (\tool_dataprivacy\data_request::is_expired($datarequest)) {
|
||||
send_file_not_found();
|
||||
}
|
||||
|
||||
// All good. Serve the exported data.
|
||||
$fs = get_file_storage();
|
||||
$relativepath = implode('/', $args);
|
||||
|
@ -34,6 +34,12 @@ if ($hassiteconfig) {
|
||||
new lang_string('contactdataprotectionofficer_desc', 'tool_dataprivacy'), 0)
|
||||
);
|
||||
|
||||
// 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'),
|
||||
new lang_string('privacyrequestexpiry_desc', 'tool_dataprivacy'),
|
||||
WEEKSECS, 1));
|
||||
|
||||
// Fetch roles that are assignable.
|
||||
$assignableroles = get_assignable_roles(context_system::instance());
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
"typename" : "Data deletion",
|
||||
"comments": "Please delete all of my son's personal data.",
|
||||
"statuslabelclass": "label-success",
|
||||
"statuslabel": "Complete",
|
||||
"statuslabel": "Deleted",
|
||||
"timecreated" : 1517902087,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
@ -90,6 +90,19 @@
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"typename" : "Data export",
|
||||
"comments": "Please let me download my data",
|
||||
"statuslabelclass": "label",
|
||||
"statuslabel": "Expired",
|
||||
"statuslabeltitle": "Download has expired. Submit a new request if you wish to export your personal data.",
|
||||
"timecreated" : 1517902087,
|
||||
"requestedbyuser" : {
|
||||
"fullname": "Martha Smith",
|
||||
"profileurl": "#"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -127,7 +140,7 @@
|
||||
<td>{{#userdate}} {{timecreated}}, {{#str}} strftimedatetime {{/str}} {{/userdate}}</td>
|
||||
<td><a href="{{requestedbyuser.profileurl}}" title="{{#str}}viewprofile{{/str}}">{{requestedbyuser.fullname}}</a></td>
|
||||
<td>
|
||||
<span class="label {{statuslabelclass}}">{{statuslabel}}</span>
|
||||
<span class="label {{statuslabelclass}}" title="{{statuslabeltitle}}">{{statuslabel}}</span>
|
||||
</td>
|
||||
<td>{{comments}}</td>
|
||||
<td>
|
||||
|
@ -66,12 +66,12 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
// Update with a valid status.
|
||||
$result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
|
||||
$result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_DOWNLOAD_READY);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Fetch the request record again.
|
||||
$datarequest = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_COMPLETE, $datarequest->get('status'));
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $datarequest->get('status'));
|
||||
|
||||
// Update with an invalid status.
|
||||
$this->expectException(invalid_persistent_exception::class);
|
||||
@ -468,8 +468,8 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
* @return array
|
||||
*/
|
||||
public function get_data_requests_provider() {
|
||||
$completeonly = [api::DATAREQUEST_STATUS_COMPLETE];
|
||||
$completeandcancelled = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_CANCELLED];
|
||||
$completeonly = [api::DATAREQUEST_STATUS_COMPLETE, api::DATAREQUEST_STATUS_DOWNLOAD_READY, api::DATAREQUEST_STATUS_DELETED];
|
||||
$completeandcancelled = array_merge($completeonly, [api::DATAREQUEST_STATUS_CANCELLED]);
|
||||
|
||||
return [
|
||||
// Own data requests.
|
||||
@ -612,6 +612,9 @@ class tool_dataprivacy_api_testcase extends advanced_testcase {
|
||||
[api::DATAREQUEST_STATUS_COMPLETE, false],
|
||||
[api::DATAREQUEST_STATUS_CANCELLED, false],
|
||||
[api::DATAREQUEST_STATUS_REJECTED, false],
|
||||
[api::DATAREQUEST_STATUS_DOWNLOAD_READY, false],
|
||||
[api::DATAREQUEST_STATUS_EXPIRED, false],
|
||||
[api::DATAREQUEST_STATUS_DELETED, false],
|
||||
];
|
||||
}
|
||||
|
||||
|
119
admin/tool/dataprivacy/tests/behat/datadelete.feature
Normal file
119
admin/tool/dataprivacy/tests/behat/datadelete.feature
Normal file
@ -0,0 +1,119 @@
|
||||
@tool @tool_dataprivacy
|
||||
Feature: Data delete from the privacy API
|
||||
In order to delete data for users and meet legal requirements
|
||||
As an admin, user, or parent
|
||||
I need to be able to request a user and their data data be deleted
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| victim | Victim User | 1 |
|
||||
| parent | Long-suffering | Parent |
|
||||
And the following "roles" exist:
|
||||
| shortname | name | archetype |
|
||||
| tired | Tired | |
|
||||
And the following "permission overrides" exist:
|
||||
| capability | permission | role | contextlevel | reference |
|
||||
| tool/dataprivacy:makedatarequestsforchildren | Allow | tired | System | |
|
||||
And the following "role assigns" exist:
|
||||
| user | role | contextlevel | reference |
|
||||
| parent | tired | User | victim |
|
||||
And the following config values are set as admin:
|
||||
| contactdataprotectionofficer | 1 | tool_dataprivacy |
|
||||
|
||||
@javascript
|
||||
Scenario: As admin, delete a user and their data
|
||||
Given I log in as "victim"
|
||||
And I should see "Victim User 1"
|
||||
And I log out
|
||||
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "New request"
|
||||
And I set the field "Requesting for" to "Victim User 1"
|
||||
And I set the field "Type" to "Delete all of my personal data"
|
||||
And I press "Save changes"
|
||||
Then I should see "Victim User 1"
|
||||
And I should see "Pending" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
|
||||
And I follow "Actions"
|
||||
And I follow "Approve request"
|
||||
And I press "Approve request"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Deleted" in the "Victim User 1" "table_row"
|
||||
|
||||
And I log out
|
||||
And I log in as "victim"
|
||||
And I should see "Invalid login"
|
||||
|
||||
@javascript
|
||||
Scenario: As a student, request deletion of account and data
|
||||
Given I log in as "victim"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Data requests"
|
||||
And I follow "New request"
|
||||
And I set the field "Type" to "Delete all of my personal data"
|
||||
And I press "Save changes"
|
||||
Then I should see "Delete all of my personal data"
|
||||
And I should see "Pending" in the "Delete all of my personal data" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Awaiting approval" in the "Delete all of my personal data" "table_row"
|
||||
|
||||
And I log out
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "Actions"
|
||||
And I follow "Approve request"
|
||||
And I press "Approve request"
|
||||
|
||||
And I log out
|
||||
And I log in as "victim"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Data requests"
|
||||
And I should see "Approved" in the "Delete all of my personal data" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Your session has timed out"
|
||||
And I log in as "victim"
|
||||
And I should see "Invalid login"
|
||||
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I should see "Deleted"
|
||||
|
||||
@javascript
|
||||
Scenario: As a parent, request account and data deletion for my child
|
||||
Given I log in as "parent"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Data requests"
|
||||
And I follow "New request"
|
||||
And I set the field "Requesting for" to "Victim User 1"
|
||||
And I set the field "Type" to "Delete all of my personal data"
|
||||
And I press "Save changes"
|
||||
Then I should see "Victim User 1"
|
||||
And I should see "Pending" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Awaiting approval" in the "Victim User 1" "table_row"
|
||||
|
||||
And I log out
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "Actions"
|
||||
And I follow "Approve request"
|
||||
And I press "Approve request"
|
||||
|
||||
And I log out
|
||||
And I log in as "parent"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Data requests"
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "You don't have any personal data requests"
|
@ -19,10 +19,11 @@ Feature: Data export from the privacy API
|
||||
| user | role | contextlevel | reference |
|
||||
| parent | tired | User | victim |
|
||||
And the following config values are set as admin:
|
||||
| contactdataprotectionofficer | 1 | tool_dataprivacy |
|
||||
| contactdataprotectionofficer | 1 | tool_dataprivacy |
|
||||
| privacyrequestexpiry | 55 | tool_dataprivacy |
|
||||
|
||||
@javascript
|
||||
Scenario: As admin, export data for a user and download it
|
||||
Scenario: As admin, export data for a user and download it, unless it has expired
|
||||
Given I log in as "admin"
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I follow "New request"
|
||||
@ -39,12 +40,19 @@ Feature: Data export from the privacy API
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Complete" in the "Victim User 1" "table_row"
|
||||
And I should see "Download ready" in the "Victim User 1" "table_row"
|
||||
And I follow "Actions"
|
||||
And following "Download" should download between "1" and "100000" bytes
|
||||
And the following config values are set as admin:
|
||||
| privacyrequestexpiry | 1 | tool_dataprivacy |
|
||||
And I wait "1" seconds
|
||||
And I navigate to "Users > Privacy and policies > Data requests" in site administration
|
||||
And I should see "Expired" in the "Victim User 1" "table_row"
|
||||
And I follow "Actions"
|
||||
And I should not see "Download"
|
||||
|
||||
@javascript
|
||||
Scenario: As a student, request data export and then download it when approved
|
||||
Scenario: As a student, request data export and then download it when approved, unless it has expired
|
||||
Given I log in as "victim"
|
||||
And I follow "Profile" in the user menu
|
||||
And I follow "Data requests"
|
||||
@ -70,10 +78,18 @@ Feature: Data export from the privacy API
|
||||
And I should see "Approved" in the "Export all of my personal data" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Complete" in the "Export all of my personal data" "table_row"
|
||||
And I should see "Download ready" in the "Export all of my personal data" "table_row"
|
||||
And I follow "Actions"
|
||||
And following "Download" should download between "1" and "100000" bytes
|
||||
|
||||
And the following config values are set as admin:
|
||||
| privacyrequestexpiry | 1 | tool_dataprivacy |
|
||||
And I wait "1" seconds
|
||||
And I reload the page
|
||||
|
||||
And I should see "Expired" in the "Export all of my personal data" "table_row"
|
||||
And I should not see "Actions"
|
||||
|
||||
@javascript
|
||||
Scenario: As a parent, request data export for my child because I don't trust the little blighter
|
||||
Given I log in as "parent"
|
||||
@ -102,6 +118,14 @@ Feature: Data export from the privacy API
|
||||
And I should see "Approved" in the "Victim User 1" "table_row"
|
||||
And I run all adhoc tasks
|
||||
And I reload the page
|
||||
And I should see "Complete" in the "Victim User 1" "table_row"
|
||||
And I should see "Download ready" in the "Victim User 1" "table_row"
|
||||
And I follow "Actions"
|
||||
And following "Download" should download between "1" and "100000" bytes
|
||||
|
||||
And the following config values are set as admin:
|
||||
| privacyrequestexpiry | 1 | tool_dataprivacy |
|
||||
And I wait "1" seconds
|
||||
And I reload the page
|
||||
|
||||
And I should see "Expired" in the "Victim User 1" "table_row"
|
||||
And I should not see "Actions"
|
||||
|
64
admin/tool/dataprivacy/tests/data_privacy_testcase.php
Normal file
64
admin/tool/dataprivacy/tests/data_privacy_testcase.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Parent class for tests which need data privacy functionality.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Parent class for tests which need data privacy functionality.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class data_privacy_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Assign one or more user IDs as site DPO
|
||||
*
|
||||
* @param stdClass|array $users User ID or array of user IDs to be assigned as site DPO
|
||||
* @return void
|
||||
*/
|
||||
protected function assign_site_dpo($users) {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
|
||||
if (!is_array($users)) {
|
||||
$users = array($users);
|
||||
}
|
||||
|
||||
$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 user(s) as manager.
|
||||
foreach ($users as $user) {
|
||||
role_assign($managerroleid, $user->id, $context->id);
|
||||
}
|
||||
|
||||
// Only map the manager role to the DPO role.
|
||||
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
||||
}
|
||||
}
|
173
admin/tool/dataprivacy/tests/expired_data_requests_test.php
Normal file
173
admin/tool/dataprivacy/tests/expired_data_requests_test.php
Normal file
@ -0,0 +1,173 @@
|
||||
<?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 data requests tests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
use tool_dataprivacy\api;
|
||||
use tool_dataprivacy\data_request;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
|
||||
require_once('data_privacy_testcase.php');
|
||||
|
||||
/**
|
||||
* Expired data requests tests.
|
||||
*
|
||||
* @package tool_dataprivacy
|
||||
* @copyright 2018 Michael Hawkins
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class tool_dataprivacy_expired_data_requests_testcase extends data_privacy_testcase {
|
||||
|
||||
/**
|
||||
* Test tearDown.
|
||||
*/
|
||||
public function tearDown() {
|
||||
\core_privacy\local\request\writer::reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test finding and deleting expired data requests
|
||||
*/
|
||||
public function test_data_request_expiry() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
\core_privacy\local\request\writer::setup_real_writer_instance();
|
||||
|
||||
// Set up test users.
|
||||
$this->setAdminUser();
|
||||
$studentuser = $this->getDataGenerator()->create_user();
|
||||
$studentusercontext = context_user::instance($studentuser->id);
|
||||
|
||||
$dpouser = $this->getDataGenerator()->create_user();
|
||||
$this->assign_site_dpo($dpouser);
|
||||
|
||||
// Set request expiry to 5 minutes.
|
||||
set_config('privacyrequestexpiry', 300, 'tool_dataprivacy');
|
||||
|
||||
// Create and approve data request.
|
||||
$this->setUser($studentuser->id);
|
||||
$datarequest = api::create_data_request($studentuser->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$this->setAdminUser();
|
||||
ob_start();
|
||||
$this->runAdhocTasks('\tool_dataprivacy\task\initiate_data_request_task');
|
||||
$requestid = $datarequest->get('id');
|
||||
$this->setUser($dpouser->id);
|
||||
api::approve_data_request($requestid);
|
||||
$this->setAdminUser();
|
||||
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
// Confirm approved and exported.
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $request->get('status'));
|
||||
$fileconditions = array(
|
||||
'userid' => $studentuser->id,
|
||||
'component' => 'tool_dataprivacy',
|
||||
'filearea' => 'export',
|
||||
'itemid' => $requestid,
|
||||
'contextid' => $studentusercontext->id,
|
||||
);
|
||||
$this->assertEquals(2, $DB->count_records('files', $fileconditions));
|
||||
|
||||
// Run expiry deletion - should not affect test export.
|
||||
$expiredrequests = data_request::get_expired_requests();
|
||||
$this->assertEquals(0, count($expiredrequests));
|
||||
data_request::expire($expiredrequests);
|
||||
|
||||
// Confirm test export was not deleted.
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $request->get('status'));
|
||||
$this->assertEquals(2, $DB->count_records('files', $fileconditions));
|
||||
|
||||
// Change request expiry to 1 second and allow it to elapse.
|
||||
set_config('privacyrequestexpiry', 1, 'tool_dataprivacy');
|
||||
$this->waitForSecond();
|
||||
|
||||
// Re-run expiry deletion, confirm the request expires and export is deleted.
|
||||
$expiredrequests = data_request::get_expired_requests();
|
||||
$this->assertEquals(1, count($expiredrequests));
|
||||
data_request::expire($expiredrequests);
|
||||
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_EXPIRED, $request->get('status'));
|
||||
$this->assertEquals(0, $DB->count_records('files', $fileconditions));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test for \tool_dataprivacy\data_request::is_expired()
|
||||
* Tests for the expected request status to protect from false positive/negative,
|
||||
* then tests is_expired() is returning the expected response.
|
||||
*/
|
||||
public function test_is_expired() {
|
||||
$this->resetAfterTest();
|
||||
\core_privacy\local\request\writer::setup_real_writer_instance();
|
||||
|
||||
// Set request expiry beyond this test.
|
||||
set_config('privacyrequestexpiry', 20, 'tool_dataprivacy');
|
||||
|
||||
$admin = get_admin();
|
||||
$this->setAdminUser();
|
||||
|
||||
// Create export request.
|
||||
$datarequest = api::create_data_request($admin->id, api::DATAREQUEST_TYPE_EXPORT);
|
||||
$requestid = $datarequest->get('id');
|
||||
|
||||
// Approve the request.
|
||||
ob_start();
|
||||
$this->runAdhocTasks('\tool_dataprivacy\task\initiate_data_request_task');
|
||||
$this->setAdminUser();
|
||||
api::approve_data_request($requestid);
|
||||
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
|
||||
ob_end_clean();
|
||||
|
||||
// Test Download ready (not expired) response.
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $request->get('status'));
|
||||
$result = data_request::is_expired($request);
|
||||
$this->assertFalse($result);
|
||||
|
||||
// Let request expiry time lapse.
|
||||
set_config('privacyrequestexpiry', 1, 'tool_dataprivacy');
|
||||
$this->waitForSecond();
|
||||
|
||||
// Test Download ready (time expired) response.
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_DOWNLOAD_READY, $request->get('status'));
|
||||
$result = data_request::is_expired($request);
|
||||
$this->assertTrue($result);
|
||||
|
||||
// Run the expiry task to properly expire the request.
|
||||
ob_start();
|
||||
$task = \core\task\manager::get_scheduled_task('\tool_dataprivacy\task\delete_expired_requests');
|
||||
$task->execute();
|
||||
ob_end_clean();
|
||||
|
||||
// Test Expired response status response.
|
||||
$request = new data_request($requestid);
|
||||
$this->assertEquals(api::DATAREQUEST_STATUS_EXPIRED, $request->get('status'));
|
||||
$result = data_request::is_expired($request);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
require_once('data_privacy_testcase.php');
|
||||
|
||||
/**
|
||||
* API tests.
|
||||
@ -31,35 +32,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @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();
|
||||
}
|
||||
|
||||
class tool_dataprivacy_manager_observer_testcase extends data_privacy_testcase {
|
||||
/**
|
||||
* Ensure that when users are configured as DPO, they are sent an message upon failure.
|
||||
*/
|
||||
@ -69,8 +42,11 @@ class tool_dataprivacy_manager_observer_testcase extends advanced_testcase {
|
||||
// Create another user who is not a DPO.
|
||||
$this->getDataGenerator()->create_user();
|
||||
|
||||
// Create the DPOs.
|
||||
$dpos = $this->setup_site_dpos();
|
||||
// Create two DPOs.
|
||||
$dpo1 = $this->getDataGenerator()->create_user();
|
||||
$dpo2 = $this->getDataGenerator()->create_user();
|
||||
$this->assign_site_dpo(array($dpo1, $dpo2));
|
||||
$dpos = \tool_dataprivacy\api::get_site_dpos();
|
||||
|
||||
$observer = new \tool_dataprivacy\manager_observer();
|
||||
|
||||
|
@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die;
|
||||
|
||||
$plugin->version = 2018051405;
|
||||
$plugin->version = 2018051406;
|
||||
$plugin->requires = 2018050800; // Moodle 3.5dev (Build 2018031600) and upwards.
|
||||
$plugin->component = 'tool_dataprivacy';
|
||||
|
@ -663,4 +663,62 @@ abstract class advanced_testcase extends base_testcase {
|
||||
usleep(50000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run adhoc tasks, optionally matching the specified classname.
|
||||
*
|
||||
* @param string $matchclass The name of the class to match on.
|
||||
* @param int $matchuserid The userid to match.
|
||||
*/
|
||||
protected function runAdhocTasks($matchclass = '', $matchuserid = null) {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->libdir.'/cronlib.php');
|
||||
|
||||
$params = [];
|
||||
if (!empty($matchclass)) {
|
||||
if (strpos($matchclass, '\\') !== 0) {
|
||||
$matchclass = '\\' . $matchclass;
|
||||
}
|
||||
$params['classname'] = $matchclass;
|
||||
}
|
||||
|
||||
if (!empty($matchuserid)) {
|
||||
$params['userid'] = $matchuserid;
|
||||
}
|
||||
|
||||
$lock = $this->createMock(\core\lock\lock::class);
|
||||
$cronlock = $this->createMock(\core\lock\lock::class);
|
||||
|
||||
$tasks = $DB->get_recordset('task_adhoc', $params);
|
||||
foreach ($tasks as $record) {
|
||||
// Note: This is for cron only.
|
||||
// We do not lock the tasks.
|
||||
$task = \core\task\manager::adhoc_task_from_record($record);
|
||||
|
||||
$user = null;
|
||||
if ($userid = $task->get_userid()) {
|
||||
// This task has a userid specified.
|
||||
$user = \core_user::get_user($userid);
|
||||
|
||||
// User found. Check that they are suitable.
|
||||
\core_user::require_active_user($user, true, true);
|
||||
}
|
||||
|
||||
$task->set_lock($lock);
|
||||
if (!$task->is_blocking()) {
|
||||
$cronlock->release();
|
||||
} else {
|
||||
$task->set_cron_lock($cronlock);
|
||||
}
|
||||
|
||||
cron_prepare_core_renderer();
|
||||
$this->setUser($user);
|
||||
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
|
||||
unset($task);
|
||||
}
|
||||
$tasks->close();
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,24 @@ class writer {
|
||||
return $this->realwriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a real content_writer for use by PHPUnit tests,
|
||||
* where a mock writer will not suffice.
|
||||
*
|
||||
* @return content_writer
|
||||
*/
|
||||
public static function setup_real_writer_instance() {
|
||||
if (!PHPUNIT_TEST) {
|
||||
throw new coding_exception('setup_real_writer_instance() is only for use with PHPUnit tests.');
|
||||
}
|
||||
|
||||
$instance = static::instance();
|
||||
|
||||
if (null === $instance->realwriter) {
|
||||
$instance->realwriter = new moodle_content_writer(static::instance());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance of
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user