mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00

The methods assertContains() and assertNotContains() now perform strict (type and value) comparison, pretty much like assertSame() does. A couple of new assertContainsEquals() and assertNotContainsEquals() methods have been created to provide old (non-strict) behavior, pretty much like assertEquals() do. Apart from replacing the calls needing a relaxed comparison to those new methods, there are also a couple of alternative, about how to fix this, depending of every case: - If the test is making any array_values() conversion, then it's better to remove that conversion and use assertArrayHasKey(), that is not strict. - Sometimes if may be also possible to, simply, cast the expectation to the exact type coming in the array. I've not applied this technique to any of the cases in core. Link: https://github.com/sebastianbergmann/phpunit/issues/3426
2393 lines
110 KiB
PHP
2393 lines
110 KiB
PHP
<?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/>.
|
|
|
|
/**
|
|
* API tests.
|
|
*
|
|
* @package tool_dataprivacy
|
|
* @copyright 2018 Jun Pataleta
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
use core\invalid_persistent_exception;
|
|
use core\task\manager;
|
|
use tool_dataprivacy\context_instance;
|
|
use tool_dataprivacy\api;
|
|
use tool_dataprivacy\data_registry;
|
|
use tool_dataprivacy\expired_context;
|
|
use tool_dataprivacy\data_request;
|
|
use tool_dataprivacy\purpose;
|
|
use tool_dataprivacy\category;
|
|
use tool_dataprivacy\local\helper;
|
|
use tool_dataprivacy\task\process_data_request_task;
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
global $CFG;
|
|
|
|
/**
|
|
* API tests.
|
|
*
|
|
* @package tool_dataprivacy
|
|
* @copyright 2018 Jun Pataleta
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
class tool_dataprivacy_api_testcase extends advanced_testcase {
|
|
|
|
/**
|
|
* Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
|
|
* tested with the default context.
|
|
*/
|
|
public function test_check_can_manage_data_registry_admin() {
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
// Technically this actually returns void, but assertNull will suffice to avoid a pointless test.
|
|
$this->assertNull(api::check_can_manage_data_registry());
|
|
}
|
|
|
|
/**
|
|
* Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
|
|
* tested with the default context.
|
|
*/
|
|
public function test_check_can_manage_data_registry_without_cap_default() {
|
|
$this->resetAfterTest();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->setUser($user);
|
|
|
|
$this->expectException(required_capability_exception::class);
|
|
api::check_can_manage_data_registry();
|
|
}
|
|
|
|
/**
|
|
* Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
|
|
* tested with the default context.
|
|
*/
|
|
public function test_check_can_manage_data_registry_without_cap_system() {
|
|
$this->resetAfterTest();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->setUser($user);
|
|
|
|
$this->expectException(required_capability_exception::class);
|
|
api::check_can_manage_data_registry(\context_system::instance()->id);
|
|
}
|
|
|
|
/**
|
|
* Ensure that the check_can_manage_data_registry function fails cap testing when a user without capabilities is
|
|
* tested with the default context.
|
|
*/
|
|
public function test_check_can_manage_data_registry_without_cap_own_user() {
|
|
$this->resetAfterTest();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->setUser($user);
|
|
|
|
$this->expectException(required_capability_exception::class);
|
|
api::check_can_manage_data_registry(\context_user::instance($user->id)->id);
|
|
}
|
|
|
|
/**
|
|
* Test for api::update_request_status().
|
|
*/
|
|
public function test_update_request_status() {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$s1 = $generator->create_user();
|
|
$this->setUser($s1);
|
|
|
|
// Create the sample data request.
|
|
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
|
|
|
$requestid = $datarequest->get('id');
|
|
|
|
// Update with a comment.
|
|
$comment = 'This is an example of a comment';
|
|
$result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $comment);
|
|
$this->assertTrue($result);
|
|
$datarequest = new data_request($requestid);
|
|
$this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
|
|
|
|
// Update with a comment which will be trimmed.
|
|
$result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, ' ');
|
|
$this->assertTrue($result);
|
|
$datarequest = new data_request($requestid);
|
|
$this->assertStringEndsWith($comment, $datarequest->get('dpocomment'));
|
|
|
|
// Update with a comment.
|
|
$secondcomment = ' - More comments - ';
|
|
$result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0, $secondcomment);
|
|
$this->assertTrue($result);
|
|
$datarequest = new data_request($requestid);
|
|
$this->assertMatchesRegularExpression("/.*{$comment}.*{$secondcomment}/s", $datarequest->get('dpocomment'));
|
|
|
|
// Update with a valid status.
|
|
$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_DOWNLOAD_READY, $datarequest->get('status'));
|
|
|
|
// Update with an invalid status.
|
|
$this->expectException(invalid_persistent_exception::class);
|
|
api::update_request_status($requestid, -1);
|
|
}
|
|
|
|
/**
|
|
* Test for api::get_site_dpos() when there are no users with the DPO role.
|
|
*/
|
|
public function test_get_site_dpos_no_dpos() {
|
|
$this->resetAfterTest();
|
|
|
|
$admin = get_admin();
|
|
|
|
$dpos = api::get_site_dpos();
|
|
$this->assertCount(1, $dpos);
|
|
$dpo = reset($dpos);
|
|
$this->assertEquals($admin->id, $dpo->id);
|
|
}
|
|
|
|
/**
|
|
* Test for api::get_site_dpos() when there are no users with the DPO role.
|
|
*/
|
|
public function test_get_site_dpos() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$u1 = $generator->create_user();
|
|
$u2 = $generator->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 u1 as a manager.
|
|
role_assign($managerroleid, $u1->id, $context->id);
|
|
|
|
// Give the editing teacher role with the capability to manage data requests.
|
|
$editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
|
|
// Assign u1 as an editing teacher as well.
|
|
role_assign($editingteacherroleid, $u1->id, $context->id);
|
|
// Assign u2 as an editing teacher.
|
|
role_assign($editingteacherroleid, $u2->id, $context->id);
|
|
|
|
// Only map the manager role to the DPO role.
|
|
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
|
|
|
$dpos = api::get_site_dpos();
|
|
$this->assertCount(1, $dpos);
|
|
$dpo = reset($dpos);
|
|
$this->assertEquals($u1->id, $dpo->id);
|
|
}
|
|
|
|
/**
|
|
* Test for \tool_dataprivacy\api::get_assigned_privacy_officer_roles().
|
|
*/
|
|
public function test_get_assigned_privacy_officer_roles() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Erroneously set the manager roles as the PO, even if it doesn't have the managedatarequests capability yet.
|
|
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
|
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
|
// Get the assigned PO roles when nothing has been set yet.
|
|
$roleids = api::get_assigned_privacy_officer_roles();
|
|
// Confirm that the returned list is empty.
|
|
$this->assertEmpty($roleids);
|
|
|
|
$context = context_system::instance();
|
|
|
|
// Give the manager role with the capability to manage data requests.
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
|
|
|
// Give the editing teacher role with the capability to manage data requests.
|
|
$editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
|
|
|
|
// Get the non-editing teacher role ID.
|
|
$teacherroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
|
|
|
|
// Erroneously map the manager and the non-editing teacher roles to the PO role.
|
|
$badconfig = $managerroleid . ',' . $teacherroleid;
|
|
set_config('dporoles', $badconfig, 'tool_dataprivacy');
|
|
|
|
// Get the assigned PO roles.
|
|
$roleids = api::get_assigned_privacy_officer_roles();
|
|
|
|
// There should only be one PO role.
|
|
$this->assertCount(1, $roleids);
|
|
// Confirm it contains the manager role.
|
|
$this->assertContainsEquals($managerroleid, $roleids);
|
|
// And it does not contain the editing teacher role.
|
|
$this->assertNotContainsEquals($editingteacherroleid, $roleids);
|
|
}
|
|
|
|
/**
|
|
* Test for api::approve_data_request().
|
|
*/
|
|
public function test_approve_data_request() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$s1 = $generator->create_user();
|
|
$u1 = $generator->create_user();
|
|
|
|
$context = context_system::instance();
|
|
|
|
// Manager role.
|
|
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
|
// Give the manager role with the capability to manage data requests.
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
|
// Assign u1 as a manager.
|
|
role_assign($managerroleid, $u1->id, $context->id);
|
|
|
|
// Map the manager role to the DPO role.
|
|
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
|
|
|
// Create the sample data request.
|
|
$this->setUser($s1);
|
|
$datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
|
|
$requestid = $datarequest->get('id');
|
|
|
|
// Make this ready for approval.
|
|
api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
|
|
|
|
$this->setUser($u1);
|
|
$result = api::approve_data_request($requestid);
|
|
$this->assertTrue($result);
|
|
$datarequest = new data_request($requestid);
|
|
$this->assertEquals($u1->id, $datarequest->get('dpo'));
|
|
$this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
|
|
|
|
// Test adhoc task creation.
|
|
$adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
|
|
$this->assertCount(1, $adhoctasks);
|
|
}
|
|
|
|
/**
|
|
* Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
|
|
*/
|
|
public function test_approve_data_request_non_dpo_user() {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$student = $generator->create_user();
|
|
$teacher = $generator->create_user();
|
|
|
|
// Create the sample data request.
|
|
$this->setUser($student);
|
|
$datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
|
|
|
|
$requestid = $datarequest->get('id');
|
|
|
|
// Login as a user without DPO role.
|
|
$this->setUser($teacher);
|
|
$this->expectException(required_capability_exception::class);
|
|
api::approve_data_request($requestid);
|
|
}
|
|
|
|
/**
|
|
* Test that deletion requests for the primary admin are rejected
|
|
*/
|
|
public function test_reject_data_deletion_request_primary_admin() {
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
|
|
$datarequest = api::create_data_request(get_admin()->id, api::DATAREQUEST_TYPE_DELETE);
|
|
|
|
// Approve the request and execute the ad-hoc process task.
|
|
ob_start();
|
|
api::approve_data_request($datarequest->get('id'));
|
|
$this->runAdhocTasks('\tool_dataprivacy\task\process_data_request_task');
|
|
ob_end_clean();
|
|
|
|
$request = api::get_request($datarequest->get('id'));
|
|
$this->assertEquals(api::DATAREQUEST_STATUS_REJECTED, $request->get('status'));
|
|
|
|
// Confirm they weren't deleted.
|
|
$user = core_user::get_user($request->get('userid'));
|
|
core_user::require_active_user($user);
|
|
}
|
|
|
|
/**
|
|
* Test for api::can_contact_dpo()
|
|
*/
|
|
public function test_can_contact_dpo() {
|
|
$this->resetAfterTest();
|
|
|
|
// Default ('contactdataprotectionofficer' is disabled by default).
|
|
$this->assertFalse(api::can_contact_dpo());
|
|
|
|
// Enable.
|
|
set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
|
|
$this->assertTrue(api::can_contact_dpo());
|
|
|
|
// Disable again.
|
|
set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
|
|
$this->assertFalse(api::can_contact_dpo());
|
|
}
|
|
|
|
/**
|
|
* Test for api::can_manage_data_requests()
|
|
*/
|
|
public function test_can_manage_data_requests() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// No configured site DPOs yet.
|
|
$admin = get_admin();
|
|
$this->assertTrue(api::can_manage_data_requests($admin->id));
|
|
|
|
$generator = new testing_data_generator();
|
|
$dpo = $generator->create_user();
|
|
$nondpocapable = $generator->create_user();
|
|
$nondpoincapable = $generator->create_user();
|
|
|
|
$context = context_system::instance();
|
|
|
|
// Manager role.
|
|
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
|
// Give the manager role with the capability to manage data requests.
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
|
// Assign u1 as a manager.
|
|
role_assign($managerroleid, $dpo->id, $context->id);
|
|
|
|
// Editing teacher role.
|
|
$editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
// Give the editing teacher role with the capability to manage data requests.
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
|
// Assign u2 as an editing teacher.
|
|
role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
|
|
|
|
// Map only the manager role to the DPO role.
|
|
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
|
|
|
// User with capability and has DPO role.
|
|
$this->assertTrue(api::can_manage_data_requests($dpo->id));
|
|
// User with capability but has no DPO role.
|
|
$this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
|
|
// User without the capability and has no DPO role.
|
|
$this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
|
|
}
|
|
|
|
/**
|
|
* Test that a user who has no capability to make any data requests for children cannot create data requests for any
|
|
* other user.
|
|
*/
|
|
public function test_can_create_data_request_for_user_no() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$otheruser = $this->getDataGenerator()->create_user();
|
|
|
|
$this->setUser($parent);
|
|
$this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
|
|
}
|
|
|
|
/**
|
|
* Test that a user who has the capability to make any data requests for one other user cannot create data requests
|
|
* for any other user.
|
|
*/
|
|
public function test_can_create_data_request_for_user_some() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$child = $this->getDataGenerator()->create_user();
|
|
$otheruser = $this->getDataGenerator()->create_user();
|
|
|
|
$systemcontext = \context_system::instance();
|
|
$parentrole = $this->getDataGenerator()->create_role();
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
|
|
role_assign($parentrole, $parent->id, \context_user::instance($child->id));
|
|
|
|
$this->setUser($parent);
|
|
$this->assertFalse(api::can_create_data_request_for_user($otheruser->id));
|
|
}
|
|
|
|
/**
|
|
* Test that a user who has the capability to make any data requests for one other user cannot create data requests
|
|
* for any other user.
|
|
*/
|
|
public function test_can_create_data_request_for_user_own_child() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$child = $this->getDataGenerator()->create_user();
|
|
|
|
$systemcontext = \context_system::instance();
|
|
$parentrole = $this->getDataGenerator()->create_role();
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
|
|
role_assign($parentrole, $parent->id, \context_user::instance($child->id));
|
|
|
|
$this->setUser($parent);
|
|
$this->assertTrue(api::can_create_data_request_for_user($child->id));
|
|
}
|
|
|
|
/**
|
|
* Test that a user who has no capability to make any data requests for children cannot create data requests for any
|
|
* other user.
|
|
*/
|
|
public function test_require_can_create_data_request_for_user_no() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$otheruser = $this->getDataGenerator()->create_user();
|
|
|
|
$this->setUser($parent);
|
|
$this->expectException('required_capability_exception');
|
|
api::require_can_create_data_request_for_user($otheruser->id);
|
|
}
|
|
|
|
/**
|
|
* Test that a user who has the capability to make any data requests for one other user cannot create data requests
|
|
* for any other user.
|
|
*/
|
|
public function test_require_can_create_data_request_for_user_some() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$child = $this->getDataGenerator()->create_user();
|
|
$otheruser = $this->getDataGenerator()->create_user();
|
|
|
|
$systemcontext = \context_system::instance();
|
|
$parentrole = $this->getDataGenerator()->create_role();
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
|
|
role_assign($parentrole, $parent->id, \context_user::instance($child->id));
|
|
|
|
$this->setUser($parent);
|
|
$this->expectException('required_capability_exception');
|
|
api::require_can_create_data_request_for_user($otheruser->id);
|
|
}
|
|
|
|
/**
|
|
* Test that a user who has the capability to make any data requests for one other user cannot create data requests
|
|
* for any other user.
|
|
*/
|
|
public function test_require_can_create_data_request_for_user_own_child() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$child = $this->getDataGenerator()->create_user();
|
|
|
|
$systemcontext = \context_system::instance();
|
|
$parentrole = $this->getDataGenerator()->create_role();
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
|
|
role_assign($parentrole, $parent->id, \context_user::instance($child->id));
|
|
|
|
$this->setUser($parent);
|
|
$this->assertTrue(api::require_can_create_data_request_for_user($child->id));
|
|
}
|
|
|
|
/**
|
|
* Test for api::can_download_data_request_for_user()
|
|
*/
|
|
public function test_can_download_data_request_for_user() {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = $this->getDataGenerator();
|
|
|
|
// Three victims.
|
|
$victim1 = $generator->create_user();
|
|
$victim2 = $generator->create_user();
|
|
$victim3 = $generator->create_user();
|
|
|
|
// Assign a user as victim 1's parent.
|
|
$systemcontext = \context_system::instance();
|
|
$parentrole = $generator->create_role();
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentrole, $systemcontext);
|
|
$parent = $generator->create_user();
|
|
role_assign($parentrole, $parent->id, \context_user::instance($victim1->id));
|
|
|
|
// Assign another user as data access wonder woman.
|
|
$wonderrole = $generator->create_role();
|
|
assign_capability('tool/dataprivacy:downloadallrequests', CAP_ALLOW, $wonderrole, $systemcontext);
|
|
$staff = $generator->create_user();
|
|
role_assign($wonderrole, $staff->id, $systemcontext);
|
|
|
|
// Finally, victim 3 has been naughty; stop them accessing their own data.
|
|
$naughtyrole = $generator->create_role();
|
|
assign_capability('tool/dataprivacy:downloadownrequest', CAP_PROHIBIT, $naughtyrole, $systemcontext);
|
|
role_assign($naughtyrole, $victim3->id, $systemcontext);
|
|
|
|
// Victims 1 and 2 can access their own data, regardless of who requested it.
|
|
$this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $victim1->id));
|
|
$this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $victim2->id));
|
|
|
|
// Victim 3 cannot access his own data.
|
|
$this->assertFalse(api::can_download_data_request_for_user($victim3->id, $victim3->id, $victim3->id));
|
|
|
|
// Victims 1 and 2 cannot access another victim's data.
|
|
$this->assertFalse(api::can_download_data_request_for_user($victim2->id, $victim1->id, $victim1->id));
|
|
$this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $victim2->id));
|
|
|
|
// Staff can access everyone's data.
|
|
$this->assertTrue(api::can_download_data_request_for_user($victim1->id, $victim1->id, $staff->id));
|
|
$this->assertTrue(api::can_download_data_request_for_user($victim2->id, $staff->id, $staff->id));
|
|
$this->assertTrue(api::can_download_data_request_for_user($victim3->id, $staff->id, $staff->id));
|
|
|
|
// Parent can access victim 1's data only if they requested it.
|
|
$this->assertTrue(api::can_download_data_request_for_user($victim1->id, $parent->id, $parent->id));
|
|
$this->assertFalse(api::can_download_data_request_for_user($victim1->id, $staff->id, $parent->id));
|
|
$this->assertFalse(api::can_download_data_request_for_user($victim2->id, $parent->id, $parent->id));
|
|
}
|
|
|
|
/**
|
|
* Data provider for data request creation tests.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function data_request_creation_provider() {
|
|
return [
|
|
'Export request by user, automatic approval off' => [
|
|
false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
|
|
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
|
],
|
|
'Export request by user, automatic approval on' => [
|
|
false, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 0,
|
|
api::DATAREQUEST_STATUS_APPROVED, 1
|
|
],
|
|
'Export request by PO, automatic approval off' => [
|
|
true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', false, 0,
|
|
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
|
],
|
|
'Export request by PO, automatic approval on' => [
|
|
true, api::DATAREQUEST_TYPE_EXPORT, 'automaticdataexportapproval', true, 'dpo',
|
|
api::DATAREQUEST_STATUS_APPROVED, 1
|
|
],
|
|
'Delete request by user, automatic approval off' => [
|
|
false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
|
|
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
|
],
|
|
'Delete request by user, automatic approval on' => [
|
|
false, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 0,
|
|
api::DATAREQUEST_STATUS_APPROVED, 1
|
|
],
|
|
'Delete request by PO, automatic approval off' => [
|
|
true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', false, 0,
|
|
api::DATAREQUEST_STATUS_AWAITING_APPROVAL, 0
|
|
],
|
|
'Delete request by PO, automatic approval on' => [
|
|
true, api::DATAREQUEST_TYPE_DELETE, 'automaticdatadeletionapproval', true, 'dpo',
|
|
api::DATAREQUEST_STATUS_APPROVED, 1
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test for api::create_data_request()
|
|
*
|
|
* @dataProvider data_request_creation_provider
|
|
* @param bool $asprivacyofficer Whether the request is made as the Privacy Officer or the user itself.
|
|
* @param string $type The data request type.
|
|
* @param string $setting The automatic approval setting.
|
|
* @param bool $automaticapproval Whether automatic data request approval is turned on or not.
|
|
* @param int|string $expecteddpoval The expected value for the 'dpo' field. 'dpo' means we'd the expected value would be the
|
|
* user ID of the privacy officer which happens in the case where a PO requests on behalf of
|
|
* someone else and automatic data request approval is turned on.
|
|
* @param int $expectedstatus The expected status of the data request.
|
|
* @param int $expectedtaskcount The number of expected queued data requests tasks.
|
|
* @throws coding_exception
|
|
* @throws invalid_persistent_exception
|
|
*/
|
|
public function test_create_data_request($asprivacyofficer, $type, $setting, $automaticapproval, $expecteddpoval,
|
|
$expectedstatus, $expectedtaskcount) {
|
|
global $USER;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$user = $generator->create_user();
|
|
$comment = 'sample comment';
|
|
|
|
// Login.
|
|
if ($asprivacyofficer) {
|
|
$this->setAdminUser();
|
|
} else {
|
|
$this->setUser($user->id);
|
|
}
|
|
|
|
// Set the automatic data request approval setting value.
|
|
set_config($setting, $automaticapproval, 'tool_dataprivacy');
|
|
|
|
// If set to 'dpo' use the currently logged-in user's ID (which should be the admin user's ID).
|
|
if ($expecteddpoval === 'dpo') {
|
|
$expecteddpoval = $USER->id;
|
|
}
|
|
|
|
// Test data request creation.
|
|
$datarequest = api::create_data_request($user->id, $type, $comment);
|
|
$this->assertEquals($user->id, $datarequest->get('userid'));
|
|
$this->assertEquals($USER->id, $datarequest->get('requestedby'));
|
|
$this->assertEquals($expecteddpoval, $datarequest->get('dpo'));
|
|
$this->assertEquals($type, $datarequest->get('type'));
|
|
$this->assertEquals($expectedstatus, $datarequest->get('status'));
|
|
$this->assertEquals($comment, $datarequest->get('comments'));
|
|
$this->assertEquals($automaticapproval, $datarequest->get('systemapproved'));
|
|
|
|
// Test number of queued data request tasks.
|
|
$datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
|
|
$this->assertCount($expectedtaskcount, $datarequesttasks);
|
|
}
|
|
|
|
/**
|
|
* Test for api::create_data_request() made by a parent.
|
|
*/
|
|
public function test_create_data_request_by_parent() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$user = $generator->create_user();
|
|
$parent = $generator->create_user();
|
|
$comment = 'sample comment';
|
|
|
|
// Get the teacher role pretend it's the parent roles ;).
|
|
$systemcontext = context_system::instance();
|
|
$usercontext = context_user::instance($user->id);
|
|
$parentroleid = $DB->get_field('role', 'id', array('shortname' => 'teacher'));
|
|
// Give the manager role with the capability to manage data requests.
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW, $parentroleid, $systemcontext->id, true);
|
|
// Assign the parent to user.
|
|
role_assign($parentroleid, $parent->id, $usercontext->id);
|
|
|
|
// Login as the user's parent.
|
|
$this->setUser($parent);
|
|
|
|
// Test data request creation.
|
|
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
|
$this->assertEquals($user->id, $datarequest->get('userid'));
|
|
$this->assertEquals($parent->id, $datarequest->get('requestedby'));
|
|
$this->assertEquals(0, $datarequest->get('dpo'));
|
|
$this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
|
|
$this->assertEquals(api::DATAREQUEST_STATUS_AWAITING_APPROVAL, $datarequest->get('status'));
|
|
$this->assertEquals($comment, $datarequest->get('comments'));
|
|
}
|
|
|
|
/**
|
|
* Test for api::deny_data_request()
|
|
*/
|
|
public function test_deny_data_request() {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$user = $generator->create_user();
|
|
$comment = 'sample comment';
|
|
|
|
// Login as user.
|
|
$this->setUser($user->id);
|
|
|
|
// Test data request creation.
|
|
$datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
|
|
|
|
// Login as the admin (default DPO when no one is set).
|
|
$this->setAdminUser();
|
|
|
|
// Make this ready for approval.
|
|
api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
|
|
|
|
// Deny the data request.
|
|
$result = api::deny_data_request($datarequest->get('id'));
|
|
$this->assertTrue($result);
|
|
}
|
|
|
|
/**
|
|
* Data provider for \tool_dataprivacy_api_testcase::test_get_data_requests().
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_data_requests_provider() {
|
|
$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.
|
|
['user', false, $completeonly],
|
|
// Non-DPO fetching all requets.
|
|
['user', true, $completeonly],
|
|
// Admin fetching all completed and cancelled requests.
|
|
['dpo', true, $completeandcancelled],
|
|
// Admin fetching all completed requests.
|
|
['dpo', true, $completeonly],
|
|
// Guest fetching all requests.
|
|
['guest', true, $completeonly],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test for api::get_data_requests()
|
|
*
|
|
* @dataProvider get_data_requests_provider
|
|
* @param string $usertype The type of the user logging in.
|
|
* @param boolean $fetchall Whether to fetch all records.
|
|
* @param int[] $statuses Status filters.
|
|
*/
|
|
public function test_get_data_requests($usertype, $fetchall, $statuses) {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$user1 = $generator->create_user();
|
|
$user2 = $generator->create_user();
|
|
$user3 = $generator->create_user();
|
|
$user4 = $generator->create_user();
|
|
$user5 = $generator->create_user();
|
|
$users = [$user1, $user2, $user3, $user4, $user5];
|
|
|
|
switch ($usertype) {
|
|
case 'user':
|
|
$loggeduser = $user1;
|
|
break;
|
|
case 'dpo':
|
|
$loggeduser = get_admin();
|
|
break;
|
|
case 'guest':
|
|
$loggeduser = guest_user();
|
|
break;
|
|
}
|
|
|
|
$comment = 'Data %s request comment by user %d';
|
|
$exportstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_EXPORT);
|
|
$deletionstring = helper::get_shortened_request_type_string(api::DATAREQUEST_TYPE_DELETE);
|
|
// Make a data requests for the users.
|
|
foreach ($users as $user) {
|
|
$this->setUser($user);
|
|
api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $exportstring, $user->id));
|
|
api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, sprintf($comment, $deletionstring, $user->id));
|
|
}
|
|
|
|
// Log in as the target user.
|
|
$this->setUser($loggeduser);
|
|
// Get records count based on the filters.
|
|
$userid = $loggeduser->id;
|
|
if ($fetchall) {
|
|
$userid = 0;
|
|
}
|
|
$count = api::get_data_requests_count($userid);
|
|
if (api::is_site_dpo($loggeduser->id)) {
|
|
// DPOs should see all the requests.
|
|
$this->assertEquals(count($users) * 2, $count);
|
|
} else {
|
|
if (empty($userid)) {
|
|
// There should be no data requests for this user available.
|
|
$this->assertEquals(0, $count);
|
|
} else {
|
|
// There should be only one (request with pending status).
|
|
$this->assertEquals(2, $count);
|
|
}
|
|
}
|
|
// Get data requests.
|
|
$requests = api::get_data_requests($userid);
|
|
// The number of requests should match the count.
|
|
$this->assertCount($count, $requests);
|
|
|
|
// Test filtering by status.
|
|
if ($count && !empty($statuses)) {
|
|
$filteredcount = api::get_data_requests_count($userid, $statuses);
|
|
// There should be none as they are all pending.
|
|
$this->assertEquals(0, $filteredcount);
|
|
$filteredrequests = api::get_data_requests($userid, $statuses);
|
|
$this->assertCount($filteredcount, $filteredrequests);
|
|
|
|
$statuscounts = [];
|
|
foreach ($statuses as $stat) {
|
|
$statuscounts[$stat] = 0;
|
|
}
|
|
$numstatus = count($statuses);
|
|
// Get all requests with status filter and update statuses, randomly.
|
|
foreach ($requests as $request) {
|
|
if (rand(0, 1)) {
|
|
continue;
|
|
}
|
|
|
|
if ($numstatus > 1) {
|
|
$index = rand(0, $numstatus - 1);
|
|
$status = $statuses[$index];
|
|
} else {
|
|
$status = reset($statuses);
|
|
}
|
|
$statuscounts[$status]++;
|
|
api::update_request_status($request->get('id'), $status);
|
|
}
|
|
$total = array_sum($statuscounts);
|
|
$filteredcount = api::get_data_requests_count($userid, $statuses);
|
|
$this->assertEquals($total, $filteredcount);
|
|
$filteredrequests = api::get_data_requests($userid, $statuses);
|
|
$this->assertCount($filteredcount, $filteredrequests);
|
|
// Confirm the filtered requests match the status filter(s).
|
|
foreach ($filteredrequests as $request) {
|
|
$this->assertContainsEquals($request->get('status'), $statuses);
|
|
}
|
|
|
|
if ($numstatus > 1) {
|
|
// Fetch by individual status to check the numbers match.
|
|
foreach ($statuses as $status) {
|
|
$filteredcount = api::get_data_requests_count($userid, [$status]);
|
|
$this->assertEquals($statuscounts[$status], $filteredcount);
|
|
$filteredrequests = api::get_data_requests($userid, [$status]);
|
|
$this->assertCount($filteredcount, $filteredrequests);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Data provider for test_has_ongoing_request.
|
|
*/
|
|
public function status_provider() {
|
|
return [
|
|
[api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
|
|
[api::DATAREQUEST_STATUS_APPROVED, true],
|
|
[api::DATAREQUEST_STATUS_PROCESSING, true],
|
|
[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],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test for api::has_ongoing_request()
|
|
*
|
|
* @dataProvider status_provider
|
|
* @param int $status The request status.
|
|
* @param bool $expected The expected result.
|
|
*/
|
|
public function test_has_ongoing_request($status, $expected) {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$user1 = $generator->create_user();
|
|
|
|
// Make a data request as user 1.
|
|
$this->setUser($user1);
|
|
$request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
|
|
// Set the status.
|
|
api::update_request_status($request->get('id'), $status);
|
|
|
|
// Check if this request is ongoing.
|
|
$result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
/**
|
|
* Test for api::is_active()
|
|
*
|
|
* @dataProvider status_provider
|
|
* @param int $status The request status
|
|
* @param bool $expected The expected result
|
|
*/
|
|
public function test_is_active($status, $expected) {
|
|
// Check if this request is ongoing.
|
|
$result = api::is_active($status);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
/**
|
|
* Test for api::is_site_dpo()
|
|
*/
|
|
public function test_is_site_dpo() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// No configured site DPOs yet.
|
|
$admin = get_admin();
|
|
$this->assertTrue(api::is_site_dpo($admin->id));
|
|
|
|
$generator = new testing_data_generator();
|
|
$dpo = $generator->create_user();
|
|
$nondpo = $generator->create_user();
|
|
|
|
$context = context_system::instance();
|
|
|
|
// Manager role.
|
|
$managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
|
|
// Give the manager role with the capability to manage data requests.
|
|
assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
|
|
// Assign u1 as a manager.
|
|
role_assign($managerroleid, $dpo->id, $context->id);
|
|
|
|
// Map only the manager role to the DPO role.
|
|
set_config('dporoles', $managerroleid, 'tool_dataprivacy');
|
|
|
|
// User is a DPO.
|
|
$this->assertTrue(api::is_site_dpo($dpo->id));
|
|
// User is not a DPO.
|
|
$this->assertFalse(api::is_site_dpo($nondpo->id));
|
|
}
|
|
|
|
/**
|
|
* Data provider function for test_notify_dpo
|
|
*
|
|
* @return array
|
|
*/
|
|
public function notify_dpo_provider() {
|
|
return [
|
|
[false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
|
|
[false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
|
|
[false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
|
|
[true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test for api::notify_dpo()
|
|
*
|
|
* @dataProvider notify_dpo_provider
|
|
* @param bool $byadmin Whether the admin requests data on behalf of the user
|
|
* @param int $type The request type
|
|
* @param string $typestringid The request lang string identifier
|
|
* @param string $comments The requestor's message to the DPO.
|
|
*/
|
|
public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = new testing_data_generator();
|
|
$user1 = $generator->create_user();
|
|
// Let's just use admin as DPO (It's the default if not set).
|
|
$dpo = get_admin();
|
|
if ($byadmin) {
|
|
$this->setAdminUser();
|
|
$requestedby = $dpo;
|
|
} else {
|
|
$this->setUser($user1);
|
|
$requestedby = $user1;
|
|
}
|
|
|
|
// Make a data request for user 1.
|
|
$request = api::create_data_request($user1->id, $type, $comments);
|
|
|
|
$sink = $this->redirectMessages();
|
|
$messageid = api::notify_dpo($dpo, $request);
|
|
$this->assertNotFalse($messageid);
|
|
$messages = $sink->get_messages();
|
|
$this->assertCount(1, $messages);
|
|
$message = reset($messages);
|
|
|
|
// Check some of the message properties.
|
|
$this->assertEquals($requestedby->id, $message->useridfrom);
|
|
$this->assertEquals($dpo->id, $message->useridto);
|
|
$typestring = get_string($typestringid, 'tool_dataprivacy');
|
|
$subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
|
|
$this->assertEquals($subject, $message->subject);
|
|
$this->assertEquals('tool_dataprivacy', $message->component);
|
|
$this->assertEquals('contactdataprotectionofficer', $message->eventtype);
|
|
$this->assertStringContainsString(fullname($dpo), $message->fullmessage);
|
|
$this->assertStringContainsString(fullname($user1), $message->fullmessage);
|
|
}
|
|
|
|
/**
|
|
* Test data purposes CRUD actions.
|
|
*
|
|
* @return null
|
|
*/
|
|
public function test_purpose_crud() {
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
|
|
// Add.
|
|
$purpose = api::create_purpose((object)[
|
|
'name' => 'bbb',
|
|
'description' => '<b>yeah</b>',
|
|
'descriptionformat' => 1,
|
|
'retentionperiod' => 'PT1M',
|
|
'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
|
|
]);
|
|
$this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
|
|
$this->assertEquals('bbb', $purpose->get('name'));
|
|
$this->assertEquals('PT1M', $purpose->get('retentionperiod'));
|
|
$this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
|
|
|
|
// Update.
|
|
$purpose->set('retentionperiod', 'PT2M');
|
|
$purpose = api::update_purpose($purpose->to_record());
|
|
$this->assertEquals('PT2M', $purpose->get('retentionperiod'));
|
|
|
|
// Retrieve.
|
|
$purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
|
|
$purposes = api::get_purposes();
|
|
$this->assertCount(2, $purposes);
|
|
$this->assertEquals('aaa', $purposes[0]->get('name'));
|
|
$this->assertEquals('bbb', $purposes[1]->get('name'));
|
|
|
|
// Delete.
|
|
api::delete_purpose($purposes[0]->get('id'));
|
|
$this->assertCount(1, api::get_purposes());
|
|
api::delete_purpose($purposes[1]->get('id'));
|
|
$this->assertCount(0, api::get_purposes());
|
|
}
|
|
|
|
/**
|
|
* Test data categories CRUD actions.
|
|
*
|
|
* @return null
|
|
*/
|
|
public function test_category_crud() {
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
|
|
// Add.
|
|
$category = api::create_category((object)[
|
|
'name' => 'bbb',
|
|
'description' => '<b>yeah</b>',
|
|
'descriptionformat' => 1
|
|
]);
|
|
$this->assertInstanceOf('\tool_dataprivacy\category', $category);
|
|
$this->assertEquals('bbb', $category->get('name'));
|
|
|
|
// Update.
|
|
$category->set('name', 'bcd');
|
|
$category = api::update_category($category->to_record());
|
|
$this->assertEquals('bcd', $category->get('name'));
|
|
|
|
// Retrieve.
|
|
$category = api::create_category((object)['name' => 'aaa']);
|
|
$categories = api::get_categories();
|
|
$this->assertCount(2, $categories);
|
|
$this->assertEquals('aaa', $categories[0]->get('name'));
|
|
$this->assertEquals('bcd', $categories[1]->get('name'));
|
|
|
|
// Delete.
|
|
api::delete_category($categories[0]->get('id'));
|
|
$this->assertCount(1, api::get_categories());
|
|
api::delete_category($categories[1]->get('id'));
|
|
$this->assertCount(0, api::get_categories());
|
|
}
|
|
|
|
/**
|
|
* Test context instances.
|
|
*
|
|
* @return null
|
|
*/
|
|
public function test_context_instances() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
|
|
list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
|
|
|
|
$coursecontext1 = \context_course::instance($courses[0]->id);
|
|
$coursecontext2 = \context_course::instance($courses[1]->id);
|
|
|
|
$record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
|
|
'categoryid' => $categories[0]->get('id')];
|
|
$contextinstance1 = api::set_context_instance($record1);
|
|
|
|
$record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
|
|
'categoryid' => $categories[1]->get('id')];
|
|
$contextinstance2 = api::set_context_instance($record2);
|
|
|
|
$this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
|
|
|
|
api::unset_context_instance($contextinstance1);
|
|
$this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
|
|
|
|
$update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
|
|
'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
|
|
$contextinstance2 = api::set_context_instance($update);
|
|
$this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
|
|
}
|
|
|
|
/**
|
|
* Test contextlevel.
|
|
*
|
|
* @return null
|
|
*/
|
|
public function test_contextlevel() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
|
|
|
|
$record = (object)[
|
|
'purposeid' => $purposes[0]->get('id'),
|
|
'categoryid' => $categories[0]->get('id'),
|
|
'contextlevel' => CONTEXT_SYSTEM,
|
|
];
|
|
$contextlevel = api::set_contextlevel($record);
|
|
$this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
|
|
$this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
|
|
$this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
|
|
$this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
|
|
|
|
// Now update it.
|
|
$record->purposeid = $purposes[1]->get('id');
|
|
$contextlevel = api::set_contextlevel($record);
|
|
$this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
|
|
$this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
|
|
$this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
|
|
|
|
$record->contextlevel = CONTEXT_USER;
|
|
$contextlevel = api::set_contextlevel($record);
|
|
$this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
|
|
}
|
|
|
|
/**
|
|
* Test effective context levels purpose and category defaults.
|
|
*
|
|
* @return null
|
|
*/
|
|
public function test_effective_contextlevel_defaults() {
|
|
$this->setAdminUser();
|
|
|
|
$this->resetAfterTest();
|
|
|
|
list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
|
|
|
|
list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
|
|
$this->assertEquals(false, $purposeid);
|
|
$this->assertEquals(false, $categoryid);
|
|
|
|
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
|
|
\context_helper::get_class_for_level(CONTEXT_SYSTEM)
|
|
);
|
|
set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
|
|
|
|
list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
|
|
$this->assertEquals($purposes[0]->get('id'), $purposeid);
|
|
$this->assertEquals(false, $categoryid);
|
|
|
|
// Course defined values should have preference.
|
|
list($purposevar, $categoryvar) = data_registry::var_names_from_context(
|
|
\context_helper::get_class_for_level(CONTEXT_COURSE)
|
|
);
|
|
set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
|
|
set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
|
|
|
|
list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
|
|
$this->assertEquals($purposes[1]->get('id'), $purposeid);
|
|
$this->assertEquals($categories[0]->get('id'), $categoryid);
|
|
|
|
// Context level defaults are also allowed to be set to 'inherit'.
|
|
set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
|
|
}
|
|
|
|
/**
|
|
* Ensure that when nothing is configured, all values return false.
|
|
*/
|
|
public function test_get_effective_contextlevel_unset() {
|
|
// Before setup, get_effective_contextlevel_purpose will return false.
|
|
$this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
|
|
$this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
|
|
|
|
$this->assertFalse(api::get_effective_contextlevel_category(CONTEXT_USER));
|
|
$this->assertFalse(api::get_effective_contextlevel_purpose(CONTEXT_USER));
|
|
}
|
|
|
|
/**
|
|
* Ensure that when nothing is configured, all values return false.
|
|
*/
|
|
public function test_get_effective_context_unset() {
|
|
// Before setup, get_effective_contextlevel_purpose will return false.
|
|
$this->assertFalse(api::get_effective_context_category(\context_system::instance()));
|
|
$this->assertFalse(api::get_effective_context_purpose(\context_system::instance()));
|
|
}
|
|
|
|
/**
|
|
* Ensure that fetching the effective value for context levels is only available to system, and user context levels.
|
|
*
|
|
* @dataProvider invalid_effective_contextlevel_provider
|
|
* @param int $contextlevel
|
|
*/
|
|
public function test_set_contextlevel_invalid_contextlevels($contextlevel) {
|
|
|
|
$this->expectException(coding_exception::class);
|
|
api::set_contextlevel((object) [
|
|
'contextlevel' => $contextlevel,
|
|
]);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test effective contextlevel return.
|
|
*/
|
|
public function test_effective_contextlevel() {
|
|
$this->resetAfterTest();
|
|
|
|
// Set the initial purpose and category.
|
|
$purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
|
|
$category1 = api::create_category((object)['name' => 'a']);
|
|
api::set_contextlevel((object)[
|
|
'contextlevel' => CONTEXT_SYSTEM,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
|
|
$this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM));
|
|
$this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_SYSTEM));
|
|
|
|
// The user context inherits from the system context when not set.
|
|
$this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
|
|
$this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
|
|
|
|
// Forcing the behaviour to inherit will have the same result.
|
|
api::set_contextlevel((object) [
|
|
'contextlevel' => CONTEXT_USER,
|
|
'purposeid' => context_instance::INHERIT,
|
|
'categoryid' => context_instance::INHERIT,
|
|
]);
|
|
$this->assertEquals($purpose1, api::get_effective_contextlevel_purpose(CONTEXT_USER));
|
|
$this->assertEquals($category1, api::get_effective_contextlevel_category(CONTEXT_USER));
|
|
|
|
// Setting specific values will override the inheritance behaviour.
|
|
$purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_a']);
|
|
$category2 = api::create_category((object)['name' => 'b']);
|
|
// Set the system context level to purpose 1.
|
|
api::set_contextlevel((object) [
|
|
'contextlevel' => CONTEXT_USER,
|
|
'purposeid' => $purpose2->get('id'),
|
|
'categoryid' => $category2->get('id'),
|
|
]);
|
|
|
|
$this->assertEquals($purpose2, api::get_effective_contextlevel_purpose(CONTEXT_USER));
|
|
$this->assertEquals($category2, api::get_effective_contextlevel_category(CONTEXT_USER));
|
|
}
|
|
|
|
/**
|
|
* Ensure that fetching the effective value for context levels is only available to system, and user context levels.
|
|
*
|
|
* @dataProvider invalid_effective_contextlevel_provider
|
|
* @param int $contextlevel
|
|
*/
|
|
public function test_effective_contextlevel_invalid_contextlevels($contextlevel) {
|
|
$this->resetAfterTest();
|
|
|
|
$purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
|
|
$category1 = api::create_category((object)['name' => 'a']);
|
|
api::set_contextlevel((object)[
|
|
'contextlevel' => CONTEXT_SYSTEM,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
|
|
$this->expectException(coding_exception::class);
|
|
api::get_effective_contextlevel_purpose($contextlevel);
|
|
}
|
|
|
|
/**
|
|
* Data provider for invalid contextlevel fetchers.
|
|
*/
|
|
public function invalid_effective_contextlevel_provider() {
|
|
return [
|
|
[CONTEXT_COURSECAT],
|
|
[CONTEXT_COURSE],
|
|
[CONTEXT_MODULE],
|
|
[CONTEXT_BLOCK],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Ensure that context inheritance works up the context tree.
|
|
*/
|
|
public function test_effective_context_inheritance() {
|
|
$this->resetAfterTest();
|
|
|
|
$systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
|
|
|
|
/*
|
|
* System
|
|
* - Cat
|
|
* - Subcat
|
|
* - Course
|
|
* - Forum
|
|
* - User
|
|
* - User block
|
|
*/
|
|
$cat = $this->getDataGenerator()->create_category();
|
|
$subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
|
|
$course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
|
|
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
|
list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
$contextsystem = \context_system::instance();
|
|
$contextcat = \context_coursecat::instance($cat->id);
|
|
$contextsubcat = \context_coursecat::instance($subcat->id);
|
|
$contextcourse = \context_course::instance($course->id);
|
|
$contextforum = \context_module::instance($forumcm->id);
|
|
$contextuser = \context_user::instance($user->id);
|
|
|
|
// Initially everything is set to Inherit.
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "0"));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextuser, "0"));
|
|
|
|
// When actively set, user will use the specified value.
|
|
$userdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_USER);
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse, "0"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "-1"));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
$this->assertEquals($userdata->purpose, api::get_effective_context_purpose($contextuser));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse, "0"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "-1"));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum, "0"));
|
|
$this->assertEquals($userdata->category, api::get_effective_context_category($contextuser));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextuser, "-1"));
|
|
|
|
// Set a context for the top category.
|
|
$catpurpose = new purpose(0, (object) [
|
|
'name' => 'Purpose',
|
|
'retentionperiod' => 'P1D',
|
|
'lawfulbases' => 'gdpr_art_6_1_a',
|
|
]);
|
|
$catpurpose->save();
|
|
$catcategory = new category(0, (object) ['name' => 'Category']);
|
|
$catcategory->save();
|
|
api::set_context_instance((object) [
|
|
'contextid' => $contextcat->id,
|
|
'purposeid' => $catpurpose->get('id'),
|
|
'categoryid' => $catcategory->get('id'),
|
|
]);
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcourse, "0"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "0"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcourse, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcourse, "0"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextforum));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextforum, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextforum, "0"));
|
|
|
|
// Set a context for the sub category.
|
|
$subcatpurpose = new purpose(0, (object) [
|
|
'name' => 'Purpose',
|
|
'retentionperiod' => 'P1D',
|
|
'lawfulbases' => 'gdpr_art_6_1_a',
|
|
]);
|
|
$subcatpurpose->save();
|
|
$subcatcategory = new category(0, (object) ['name' => 'Category']);
|
|
$subcatcategory->save();
|
|
api::set_context_instance((object) [
|
|
'contextid' => $contextsubcat->id,
|
|
'purposeid' => $subcatpurpose->get('id'),
|
|
'categoryid' => $subcatcategory->get('id'),
|
|
]);
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "0"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum, "-1"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "0"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum, "-1"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextforum, "0"));
|
|
|
|
// Set a context for the course.
|
|
$coursepurpose = new purpose(0, (object) [
|
|
'name' => 'Purpose',
|
|
'retentionperiod' => 'P1D',
|
|
'lawfulbases' => 'gdpr_art_6_1_a',
|
|
]);
|
|
$coursepurpose->save();
|
|
$coursecategory = new category(0, (object) ['name' => 'Category']);
|
|
$coursecategory->save();
|
|
api::set_context_instance((object) [
|
|
'contextid' => $contextcourse->id,
|
|
'purposeid' => $coursepurpose->get('id'),
|
|
'categoryid' => $coursecategory->get('id'),
|
|
]);
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse, "0"));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "-1"));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse, "0"));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextforum));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "-1"));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "0"));
|
|
|
|
// Set a context for the forum.
|
|
$forumpurpose = new purpose(0, (object) [
|
|
'name' => 'Purpose',
|
|
'retentionperiod' => 'P1D',
|
|
'lawfulbases' => 'gdpr_art_6_1_a',
|
|
]);
|
|
$forumpurpose->save();
|
|
$forumcategory = new category(0, (object) ['name' => 'Category']);
|
|
$forumcategory->save();
|
|
api::set_context_instance((object) [
|
|
'contextid' => $contextforum->id,
|
|
'purposeid' => $forumpurpose->get('id'),
|
|
'categoryid' => $forumcategory->get('id'),
|
|
]);
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat, "-1"));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextcat, "0"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($catpurpose, api::get_effective_context_purpose($contextsubcat, "-1"));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextsubcat, "0"));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($subcatpurpose, api::get_effective_context_purpose($contextcourse, "-1"));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextcourse, "0"));
|
|
$this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum));
|
|
$this->assertEquals($coursepurpose, api::get_effective_context_purpose($contextforum, "-1"));
|
|
$this->assertEquals($forumpurpose, api::get_effective_context_purpose($contextforum, "0"));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat, "-1"));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextcat, "0"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($catcategory, api::get_effective_context_category($contextsubcat, "-1"));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextsubcat, "0"));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($subcatcategory, api::get_effective_context_category($contextcourse, "-1"));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextcourse, "0"));
|
|
$this->assertEquals($forumcategory, api::get_effective_context_category($contextforum));
|
|
$this->assertEquals($coursecategory, api::get_effective_context_category($contextforum, "-1"));
|
|
$this->assertEquals($forumcategory, api::get_effective_context_category($contextforum, "0"));
|
|
}
|
|
|
|
/**
|
|
* Ensure that context inheritance works up the context tree when inherit values are explicitly set at the
|
|
* contextlevel.
|
|
*
|
|
* Although it should not be possible to set hard INHERIT values at this level, there may be legacy data which still
|
|
* contains this.
|
|
*/
|
|
public function test_effective_context_inheritance_explicitly_set() {
|
|
$this->resetAfterTest();
|
|
|
|
$systemdata = $this->create_and_set_purpose_for_contextlevel('PT1S', CONTEXT_SYSTEM);
|
|
|
|
/*
|
|
* System
|
|
* - Cat
|
|
* - Subcat
|
|
* - Course
|
|
* - Forum
|
|
* - User
|
|
* - User block
|
|
*/
|
|
$cat = $this->getDataGenerator()->create_category();
|
|
$subcat = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
|
|
$course = $this->getDataGenerator()->create_course(['category' => $subcat->id]);
|
|
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
|
list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
|
|
|
|
$contextsystem = \context_system::instance();
|
|
$contextcat = \context_coursecat::instance($cat->id);
|
|
$contextsubcat = \context_coursecat::instance($subcat->id);
|
|
$contextcourse = \context_course::instance($course->id);
|
|
$contextforum = \context_module::instance($forumcm->id);
|
|
|
|
// Initially everything is set to Inherit.
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
|
|
|
|
// Set a default value of inherit for CONTEXT_COURSECAT.
|
|
$classname = \context_helper::get_class_for_level(CONTEXT_COURSECAT);
|
|
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
|
|
set_config($purposevar, '-1', 'tool_dataprivacy');
|
|
set_config($categoryvar, '-1', 'tool_dataprivacy');
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
|
|
|
|
// Set a default value of inherit for CONTEXT_COURSE.
|
|
$classname = \context_helper::get_class_for_level(CONTEXT_COURSE);
|
|
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
|
|
set_config($purposevar, '-1', 'tool_dataprivacy');
|
|
set_config($categoryvar, '-1', 'tool_dataprivacy');
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
|
|
|
|
// Set a default value of inherit for CONTEXT_MODULE.
|
|
$classname = \context_helper::get_class_for_level(CONTEXT_MODULE);
|
|
list($purposevar, $categoryvar) = data_registry::var_names_from_context($classname);
|
|
set_config($purposevar, '-1', 'tool_dataprivacy');
|
|
set_config($categoryvar, '-1', 'tool_dataprivacy');
|
|
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsystem));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextsubcat));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextcourse));
|
|
$this->assertEquals($systemdata->purpose, api::get_effective_context_purpose($contextforum));
|
|
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsystem));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextsubcat));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextcourse));
|
|
$this->assertEquals($systemdata->category, api::get_effective_context_category($contextforum));
|
|
}
|
|
|
|
/**
|
|
* Creates test purposes and categories.
|
|
*
|
|
* @return null
|
|
*/
|
|
protected function add_purposes_and_categories() {
|
|
$this->resetAfterTest();
|
|
|
|
$purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
|
|
$purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
|
|
$purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
|
|
|
|
$cat1 = api::create_category((object)['name' => 'a']);
|
|
$cat2 = api::create_category((object)['name' => 'b']);
|
|
$cat3 = api::create_category((object)['name' => 'c']);
|
|
|
|
$course1 = $this->getDataGenerator()->create_course();
|
|
$course2 = $this->getDataGenerator()->create_course();
|
|
|
|
$module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
|
|
$module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
|
|
|
|
return [
|
|
[$purpose1, $purpose2, $purpose3],
|
|
[$cat1, $cat2, $cat3],
|
|
[$course1, $course2],
|
|
[$module1, $module2]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test that delete requests do not filter out protected purpose contexts if the the site is properly configured.
|
|
*/
|
|
public function test_get_approved_contextlist_collection_for_collection_delete_course_no_site_config() {
|
|
$this->resetAfterTest();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
|
|
$coursecontext = \context_course::instance($course->id);
|
|
|
|
$forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
|
|
list(, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
|
|
$contextforum = \context_module::instance($forumcm->id);
|
|
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
|
|
|
// Create the initial contextlist.
|
|
$initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
|
|
|
$contextlist = new \core_privacy\local\request\contextlist();
|
|
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
|
|
$contextlist->set_component('tool_dataprivacy');
|
|
$initialcollection->add_contextlist($contextlist);
|
|
|
|
$contextlist = new \core_privacy\local\request\contextlist();
|
|
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $contextforum->id]);
|
|
$contextlist->set_component('mod_forum');
|
|
$initialcollection->add_contextlist($contextlist);
|
|
|
|
$collection = api::get_approved_contextlist_collection_for_collection(
|
|
$initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
|
|
|
|
$this->assertCount(2, $collection);
|
|
|
|
$list = $collection->get_contextlist_for_component('tool_dataprivacy');
|
|
$this->assertCount(1, $list);
|
|
|
|
$list = $collection->get_contextlist_for_component('mod_forum');
|
|
$this->assertCount(1, $list);
|
|
}
|
|
|
|
/**
|
|
* Test that delete requests do not filter out protected purpose contexts if they are already expired.
|
|
*/
|
|
public function test_get_approved_contextlist_collection_for_collection_delete_course_expired_protected() {
|
|
$this->resetAfterTest();
|
|
|
|
$purposes = $this->setup_basics('PT1H', 'PT1H', 'PT1H');
|
|
$purposes->course->purpose->set('protected', 1)->save();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time() - YEARSECS]);
|
|
$coursecontext = \context_course::instance($course->id);
|
|
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
|
|
|
// Create the initial contextlist.
|
|
$contextlist = new \core_privacy\local\request\contextlist();
|
|
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
|
|
$contextlist->set_component('tool_dataprivacy');
|
|
|
|
$initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
|
$initialcollection->add_contextlist($contextlist);
|
|
|
|
$purposes->course->purpose->set('protected', 1)->save();
|
|
$collection = api::get_approved_contextlist_collection_for_collection(
|
|
$initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
|
|
|
|
$this->assertCount(1, $collection);
|
|
|
|
$list = $collection->get_contextlist_for_component('tool_dataprivacy');
|
|
$this->assertCount(1, $list);
|
|
}
|
|
|
|
/**
|
|
* Test that delete requests does filter out protected purpose contexts which are not expired.
|
|
*/
|
|
public function test_get_approved_contextlist_collection_for_collection_delete_course_unexpired_protected() {
|
|
$this->resetAfterTest();
|
|
|
|
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
|
|
$purposes->course->purpose->set('protected', 1)->save();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
|
|
$coursecontext = \context_course::instance($course->id);
|
|
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
|
|
|
// Create the initial contextlist.
|
|
$contextlist = new \core_privacy\local\request\contextlist();
|
|
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
|
|
$contextlist->set_component('tool_dataprivacy');
|
|
|
|
$initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
|
$initialcollection->add_contextlist($contextlist);
|
|
|
|
$purposes->course->purpose->set('protected', 1)->save();
|
|
$collection = api::get_approved_contextlist_collection_for_collection(
|
|
$initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
|
|
|
|
$this->assertCount(0, $collection);
|
|
|
|
$list = $collection->get_contextlist_for_component('tool_dataprivacy');
|
|
$this->assertEmpty($list);
|
|
}
|
|
|
|
/**
|
|
* Test that delete requests do not filter out unexpired contexts if they are not protected.
|
|
*/
|
|
public function test_get_approved_contextlist_collection_for_collection_delete_course_unexpired_unprotected() {
|
|
$this->resetAfterTest();
|
|
|
|
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1Y');
|
|
$purposes->course->purpose->set('protected', 1)->save();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$course = $this->getDataGenerator()->create_course(['startdate' => time() - YEARSECS, 'enddate' => time()]);
|
|
$coursecontext = \context_course::instance($course->id);
|
|
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
|
|
|
|
// Create the initial contextlist.
|
|
$contextlist = new \core_privacy\local\request\contextlist();
|
|
$contextlist->add_from_sql('SELECT id FROM {context} WHERE id = :contextid', ['contextid' => $coursecontext->id]);
|
|
$contextlist->set_component('tool_dataprivacy');
|
|
|
|
$initialcollection = new \core_privacy\local\request\contextlist_collection($user->id);
|
|
$initialcollection->add_contextlist($contextlist);
|
|
|
|
$purposes->course->purpose->set('protected', 0)->save();
|
|
$collection = api::get_approved_contextlist_collection_for_collection(
|
|
$initialcollection, $user, api::DATAREQUEST_TYPE_DELETE);
|
|
|
|
$this->assertCount(1, $collection);
|
|
|
|
$list = $collection->get_contextlist_for_component('tool_dataprivacy');
|
|
$this->assertCount(1, $list);
|
|
}
|
|
|
|
/**
|
|
* Data provider for \tool_dataprivacy_api_testcase::test_set_context_defaults
|
|
*/
|
|
public function set_context_defaults_provider() {
|
|
$contextlevels = [
|
|
[CONTEXT_COURSECAT],
|
|
[CONTEXT_COURSE],
|
|
[CONTEXT_MODULE],
|
|
[CONTEXT_BLOCK],
|
|
];
|
|
$paramsets = [
|
|
[true, true, false, false], // Inherit category and purpose, Not for activity, Don't override.
|
|
[true, false, false, false], // Inherit category but not purpose, Not for activity, Don't override.
|
|
[false, true, false, false], // Inherit purpose but not category, Not for activity, Don't override.
|
|
[false, false, false, false], // Don't inherit both category and purpose, Not for activity, Don't override.
|
|
[false, false, false, true], // Don't inherit both category and purpose, Not for activity, Override instances.
|
|
];
|
|
$data = [];
|
|
foreach ($contextlevels as $level) {
|
|
foreach ($paramsets as $set) {
|
|
$data[] = array_merge($level, $set);
|
|
}
|
|
if ($level == CONTEXT_MODULE) {
|
|
// Add a combination where defaults for activity is being set.
|
|
$data[] = [CONTEXT_MODULE, false, false, true, false];
|
|
$data[] = [CONTEXT_MODULE, false, false, true, true];
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Test for \tool_dataprivacy\api::set_context_defaults()
|
|
*
|
|
* @dataProvider set_context_defaults_provider
|
|
* @param int $contextlevel The context level
|
|
* @param bool $inheritcategory Whether to set category value as INHERIT.
|
|
* @param bool $inheritpurpose Whether to set purpose value as INHERIT.
|
|
* @param bool $foractivity Whether to set defaults for an activity.
|
|
* @param bool $override Whether to override instances.
|
|
*/
|
|
public function test_set_context_defaults($contextlevel, $inheritcategory, $inheritpurpose, $foractivity, $override) {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = $this->getDataGenerator();
|
|
|
|
// Generate course cat, course, block, assignment, forum instances.
|
|
$coursecat = $generator->create_category();
|
|
$course = $generator->create_course(['category' => $coursecat->id]);
|
|
$block = $generator->create_block('online_users');
|
|
$assign = $generator->create_module('assign', ['course' => $course->id]);
|
|
$forum = $generator->create_module('forum', ['course' => $course->id]);
|
|
|
|
$coursecatcontext = context_coursecat::instance($coursecat->id);
|
|
$coursecontext = context_course::instance($course->id);
|
|
$blockcontext = context_block::instance($block->id);
|
|
|
|
list($course, $assigncm) = get_course_and_cm_from_instance($assign->id, 'assign');
|
|
list($course, $forumcm) = get_course_and_cm_from_instance($forum->id, 'forum');
|
|
$assigncontext = context_module::instance($assigncm->id);
|
|
$forumcontext = context_module::instance($forumcm->id);
|
|
|
|
// Generate purposes and categories.
|
|
$category1 = api::create_category((object)['name' => 'Test category 1']);
|
|
$category2 = api::create_category((object)['name' => 'Test category 2']);
|
|
$purpose1 = api::create_purpose((object)[
|
|
'name' => 'Test purpose 1', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
|
|
]);
|
|
$purpose2 = api::create_purpose((object)[
|
|
'name' => 'Test purpose 2', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a'
|
|
]);
|
|
|
|
// Assign purposes and categories to contexts.
|
|
$coursecatctxinstance = api::set_context_instance((object) [
|
|
'contextid' => $coursecatcontext->id,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
$coursectxinstance = api::set_context_instance((object) [
|
|
'contextid' => $coursecontext->id,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
$blockctxinstance = api::set_context_instance((object) [
|
|
'contextid' => $blockcontext->id,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
$assignctxinstance = api::set_context_instance((object) [
|
|
'contextid' => $assigncontext->id,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
$forumctxinstance = api::set_context_instance((object) [
|
|
'contextid' => $forumcontext->id,
|
|
'purposeid' => $purpose1->get('id'),
|
|
'categoryid' => $category1->get('id'),
|
|
]);
|
|
|
|
$categoryid = $inheritcategory ? context_instance::INHERIT : $category2->get('id');
|
|
$purposeid = $inheritpurpose ? context_instance::INHERIT : $purpose2->get('id');
|
|
$activity = '';
|
|
if ($contextlevel == CONTEXT_MODULE && $foractivity) {
|
|
$activity = 'assign';
|
|
}
|
|
$result = api::set_context_defaults($contextlevel, $categoryid, $purposeid, $activity, $override);
|
|
$this->assertTrue($result);
|
|
|
|
$targetctxinstance = false;
|
|
switch ($contextlevel) {
|
|
case CONTEXT_COURSECAT:
|
|
$targetctxinstance = $coursecatctxinstance;
|
|
break;
|
|
case CONTEXT_COURSE:
|
|
$targetctxinstance = $coursectxinstance;
|
|
break;
|
|
case CONTEXT_MODULE:
|
|
$targetctxinstance = $assignctxinstance;
|
|
break;
|
|
case CONTEXT_BLOCK:
|
|
$targetctxinstance = $blockctxinstance;
|
|
break;
|
|
}
|
|
$this->assertNotFalse($targetctxinstance);
|
|
|
|
// Check the context instances.
|
|
$instanceexists = context_instance::record_exists($targetctxinstance->get('id'));
|
|
if ($override) {
|
|
// If overridden, context instances on this context level would have been deleted.
|
|
$this->assertFalse($instanceexists);
|
|
|
|
// Check forum context instance.
|
|
$forumctxexists = context_instance::record_exists($forumctxinstance->get('id'));
|
|
if ($contextlevel != CONTEXT_MODULE || $foractivity) {
|
|
// The forum context instance won't be affected in this test if:
|
|
// - The overridden defaults are not for context modules.
|
|
// - Only the defaults for assign have been set.
|
|
$this->assertTrue($forumctxexists);
|
|
} else {
|
|
// If we're overriding for the whole course module context level,
|
|
// then this forum context instance will be deleted as well.
|
|
$this->assertFalse($forumctxexists);
|
|
}
|
|
} else {
|
|
// Otherwise, the context instance record remains.
|
|
$this->assertTrue($instanceexists);
|
|
}
|
|
|
|
// Check defaults.
|
|
list($defaultpurpose, $defaultcategory) = data_registry::get_defaults($contextlevel, $activity);
|
|
if (!$inheritpurpose) {
|
|
$this->assertEquals($purposeid, $defaultpurpose);
|
|
}
|
|
if (!$inheritcategory) {
|
|
$this->assertEquals($categoryid, $defaultcategory);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup the basics with the specified retention period.
|
|
*
|
|
* @param string $system Retention policy for the system.
|
|
* @param string $user Retention policy for users.
|
|
* @param string $course Retention policy for courses.
|
|
* @param string $activity Retention policy for activities.
|
|
*/
|
|
protected function setup_basics(string $system, string $user, string $course = null, string $activity = null) : \stdClass {
|
|
$this->resetAfterTest();
|
|
|
|
$purposes = (object) [
|
|
'system' => $this->create_and_set_purpose_for_contextlevel($system, CONTEXT_SYSTEM),
|
|
'user' => $this->create_and_set_purpose_for_contextlevel($user, CONTEXT_USER),
|
|
];
|
|
|
|
if (null !== $course) {
|
|
$purposes->course = $this->create_and_set_purpose_for_contextlevel($course, CONTEXT_COURSE);
|
|
}
|
|
|
|
if (null !== $activity) {
|
|
$purposes->activity = $this->create_and_set_purpose_for_contextlevel($activity, CONTEXT_MODULE);
|
|
}
|
|
|
|
return $purposes;
|
|
}
|
|
|
|
/**
|
|
* Create a retention period and set it for the specified context level.
|
|
*
|
|
* @param string $retention
|
|
* @param int $contextlevel
|
|
*/
|
|
protected function create_and_set_purpose_for_contextlevel(string $retention, int $contextlevel) {
|
|
$purpose = new purpose(0, (object) [
|
|
'name' => 'Test purpose ' . rand(1, 1000),
|
|
'retentionperiod' => $retention,
|
|
'lawfulbases' => 'gdpr_art_6_1_a',
|
|
]);
|
|
$purpose->create();
|
|
|
|
$cat = new category(0, (object) ['name' => 'Test category']);
|
|
$cat->create();
|
|
|
|
if ($contextlevel <= CONTEXT_USER) {
|
|
$record = (object) [
|
|
'purposeid' => $purpose->get('id'),
|
|
'categoryid' => $cat->get('id'),
|
|
'contextlevel' => $contextlevel,
|
|
];
|
|
api::set_contextlevel($record);
|
|
} else {
|
|
list($purposevar, ) = data_registry::var_names_from_context(
|
|
\context_helper::get_class_for_level(CONTEXT_COURSE)
|
|
);
|
|
set_config($purposevar, $purpose->get('id'), 'tool_dataprivacy');
|
|
}
|
|
|
|
return (object) [
|
|
'purpose' => $purpose,
|
|
'category' => $cat,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Ensure that the find_ongoing_request_types_for_users only returns requests which are active.
|
|
*/
|
|
public function test_find_ongoing_request_types_for_users() {
|
|
$this->resetAfterTest();
|
|
|
|
// Create users and their requests:.
|
|
// - u1 has no requests of any type.
|
|
// - u2 has one rejected export request.
|
|
// - u3 has one rejected other request.
|
|
// - u4 has one rejected delete request.
|
|
// - u5 has one active and one rejected export request.
|
|
// - u6 has one active and one rejected other request.
|
|
// - u7 has one active and one rejected delete request.
|
|
// - u8 has one active export, and one active delete request.
|
|
$u1 = $this->getDataGenerator()->create_user();
|
|
$u1expect = (object) [];
|
|
|
|
$u2 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u2->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_REJECTED);
|
|
$u2expect = (object) [];
|
|
|
|
$u3 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u3->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_REJECTED);
|
|
$u3expect = (object) [];
|
|
|
|
$u4 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u4->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_REJECTED);
|
|
$u4expect = (object) [];
|
|
|
|
$u5 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u5->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_REJECTED);
|
|
$this->create_request_with_type_and_status($u5->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_APPROVED);
|
|
$u5expect = (object) [
|
|
api::DATAREQUEST_TYPE_EXPORT => true,
|
|
];
|
|
|
|
$u6 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u6->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_REJECTED);
|
|
$this->create_request_with_type_and_status($u6->id, api::DATAREQUEST_TYPE_OTHERS, api::DATAREQUEST_STATUS_APPROVED);
|
|
$u6expect = (object) [
|
|
api::DATAREQUEST_TYPE_OTHERS => true,
|
|
];
|
|
|
|
$u7 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u7->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_REJECTED);
|
|
$this->create_request_with_type_and_status($u7->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_APPROVED);
|
|
$u7expect = (object) [
|
|
api::DATAREQUEST_TYPE_DELETE => true,
|
|
];
|
|
|
|
$u8 = $this->getDataGenerator()->create_user();
|
|
$this->create_request_with_type_and_status($u8->id, api::DATAREQUEST_TYPE_EXPORT, api::DATAREQUEST_STATUS_APPROVED);
|
|
$this->create_request_with_type_and_status($u8->id, api::DATAREQUEST_TYPE_DELETE, api::DATAREQUEST_STATUS_APPROVED);
|
|
$u8expect = (object) [
|
|
api::DATAREQUEST_TYPE_EXPORT => true,
|
|
api::DATAREQUEST_TYPE_DELETE => true,
|
|
];
|
|
|
|
// Test with no users specified.
|
|
$result = api::find_ongoing_request_types_for_users([]);
|
|
$this->assertEquals([], $result);
|
|
|
|
// Fetch a subset of the users.
|
|
$result = api::find_ongoing_request_types_for_users([$u3->id, $u4->id, $u5->id]);
|
|
$this->assertEquals([
|
|
$u3->id => $u3expect,
|
|
$u4->id => $u4expect,
|
|
$u5->id => $u5expect,
|
|
], $result);
|
|
|
|
// Fetch the empty user.
|
|
$result = api::find_ongoing_request_types_for_users([$u1->id]);
|
|
$this->assertEquals([
|
|
$u1->id => $u1expect,
|
|
], $result);
|
|
|
|
// Fetch all.
|
|
$result = api::find_ongoing_request_types_for_users(
|
|
[$u1->id, $u2->id, $u3->id, $u4->id, $u5->id, $u6->id, $u7->id, $u8->id]);
|
|
$this->assertEquals([
|
|
$u1->id => $u1expect,
|
|
$u2->id => $u2expect,
|
|
$u3->id => $u3expect,
|
|
$u4->id => $u4expect,
|
|
$u5->id => $u5expect,
|
|
$u6->id => $u6expect,
|
|
$u7->id => $u7expect,
|
|
$u8->id => $u8expect,
|
|
], $result);
|
|
}
|
|
|
|
/**
|
|
* Create a new data request for the user with the type and status specified.
|
|
*
|
|
* @param int $userid
|
|
* @param int $type
|
|
* @param int $status
|
|
* @return \tool_dataprivacy\data_request
|
|
*/
|
|
protected function create_request_with_type_and_status(int $userid, int $type, int $status) : \tool_dataprivacy\data_request {
|
|
$request = new \tool_dataprivacy\data_request(0, (object) [
|
|
'userid' => $userid,
|
|
'type' => $type,
|
|
'status' => $status,
|
|
]);
|
|
|
|
$request->save();
|
|
|
|
return $request;
|
|
}
|
|
|
|
/**
|
|
* Test user cannot create data deletion request for themselves if they don't have
|
|
* "tool/dataprivacy:requestdelete" capability.
|
|
*
|
|
* @throws coding_exception
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_self_no() {
|
|
$this->resetAfterTest();
|
|
$userid = $this->getDataGenerator()->create_user()->id;
|
|
$roleid = $this->getDataGenerator()->create_role();
|
|
assign_capability('tool/dataprivacy:requestdelete', CAP_PROHIBIT, $roleid, context_user::instance($userid));
|
|
role_assign($roleid, $userid, context_user::instance($userid));
|
|
$this->setUser($userid);
|
|
$this->assertFalse(api::can_create_data_deletion_request_for_self());
|
|
}
|
|
|
|
/**
|
|
* Test primary admin cannot create data deletion request for themselves
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_self_primary_admin() {
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
$this->assertFalse(api::can_create_data_deletion_request_for_self());
|
|
}
|
|
|
|
/**
|
|
* Test secondary admin can create data deletion request for themselves
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_self_secondary_admin() {
|
|
$this->resetAfterTest();
|
|
|
|
$admin1 = $this->getDataGenerator()->create_user();
|
|
$admin2 = $this->getDataGenerator()->create_user();
|
|
|
|
// The primary admin is the one listed first in the 'siteadmins' config.
|
|
set_config('siteadmins', implode(',', [$admin1->id, $admin2->id]));
|
|
|
|
// Set the current user as the second admin (non-primary).
|
|
$this->setUser($admin2);
|
|
|
|
$this->assertTrue(api::can_create_data_deletion_request_for_self());
|
|
}
|
|
|
|
/**
|
|
* Test user can create data deletion request for themselves if they have
|
|
* "tool/dataprivacy:requestdelete" capability.
|
|
*
|
|
* @throws coding_exception
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_self_yes() {
|
|
$this->resetAfterTest();
|
|
$userid = $this->getDataGenerator()->create_user()->id;
|
|
$this->setUser($userid);
|
|
$this->assertTrue(api::can_create_data_deletion_request_for_self());
|
|
}
|
|
|
|
/**
|
|
* Test user cannot create data deletion request for another user if they
|
|
* don't have "tool/dataprivacy:requestdeleteforotheruser" capability.
|
|
*
|
|
* @throws coding_exception
|
|
* @throws dml_exception
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_other_no() {
|
|
$this->resetAfterTest();
|
|
$userid = $this->getDataGenerator()->create_user()->id;
|
|
$this->setUser($userid);
|
|
$this->assertFalse(api::can_create_data_deletion_request_for_other());
|
|
}
|
|
|
|
/**
|
|
* Test user can create data deletion request for another user if they
|
|
* don't have "tool/dataprivacy:requestdeleteforotheruser" capability.
|
|
*
|
|
* @throws coding_exception
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_other_yes() {
|
|
$this->resetAfterTest();
|
|
$userid = $this->getDataGenerator()->create_user()->id;
|
|
$roleid = $this->getDataGenerator()->create_role();
|
|
$contextsystem = context_system::instance();
|
|
assign_capability('tool/dataprivacy:requestdeleteforotheruser', CAP_ALLOW, $roleid, $contextsystem);
|
|
role_assign($roleid, $userid, $contextsystem);
|
|
$this->setUser($userid);
|
|
$this->assertTrue(api::can_create_data_deletion_request_for_other($userid));
|
|
}
|
|
|
|
/**
|
|
* Check parents can create data deletion request for their children (unless the child is the primary admin),
|
|
* but not other users.
|
|
*
|
|
* @throws coding_exception
|
|
* @throws dml_exception
|
|
*/
|
|
public function test_can_create_data_deletion_request_for_children() {
|
|
$this->resetAfterTest();
|
|
|
|
$parent = $this->getDataGenerator()->create_user();
|
|
$child = $this->getDataGenerator()->create_user();
|
|
$otheruser = $this->getDataGenerator()->create_user();
|
|
|
|
$contextsystem = \context_system::instance();
|
|
$parentrole = $this->getDataGenerator()->create_role();
|
|
assign_capability('tool/dataprivacy:makedatarequestsforchildren', CAP_ALLOW,
|
|
$parentrole, $contextsystem);
|
|
assign_capability('tool/dataprivacy:makedatadeletionrequestsforchildren', CAP_ALLOW,
|
|
$parentrole, $contextsystem);
|
|
role_assign($parentrole, $parent->id, \context_user::instance($child->id));
|
|
|
|
$this->setUser($parent);
|
|
$this->assertTrue(api::can_create_data_deletion_request_for_children($child->id));
|
|
$this->assertFalse(api::can_create_data_deletion_request_for_children($otheruser->id));
|
|
|
|
// Now make child the primary admin, confirm parent can't make deletion request.
|
|
set_config('siteadmins', $child->id);
|
|
$this->assertFalse(api::can_create_data_deletion_request_for_children($child->id));
|
|
}
|
|
|
|
/**
|
|
* Data provider function for testing \tool_dataprivacy\api::queue_data_request_task().
|
|
*
|
|
* @return array
|
|
*/
|
|
public function queue_data_request_task_provider() {
|
|
return [
|
|
'With user ID provided' => [true],
|
|
'Without user ID provided' => [false],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test for \tool_dataprivacy\api::queue_data_request_task().
|
|
*
|
|
* @dataProvider queue_data_request_task_provider
|
|
* @param bool $withuserid
|
|
*/
|
|
public function test_queue_data_request_task(bool $withuserid) {
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
|
|
if ($withuserid) {
|
|
$user = $this->getDataGenerator()->create_user();
|
|
api::queue_data_request_task(1, $user->id);
|
|
$expecteduserid = $user->id;
|
|
} else {
|
|
api::queue_data_request_task(1);
|
|
$expecteduserid = null;
|
|
}
|
|
|
|
// Test number of queued data request tasks.
|
|
$datarequesttasks = manager::get_adhoc_tasks(process_data_request_task::class);
|
|
$this->assertCount(1, $datarequesttasks);
|
|
$requesttask = reset($datarequesttasks);
|
|
$this->assertEquals($expecteduserid, $requesttask->get_userid());
|
|
}
|
|
|
|
/**
|
|
* Data provider for test_is_automatic_request_approval_on().
|
|
*/
|
|
public function automatic_request_approval_setting_provider() {
|
|
return [
|
|
'Data export, not set' => [
|
|
'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, null, false
|
|
],
|
|
'Data export, turned on' => [
|
|
'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, true, true
|
|
],
|
|
'Data export, turned off' => [
|
|
'automaticdataexportapproval', api::DATAREQUEST_TYPE_EXPORT, false, false
|
|
],
|
|
'Data deletion, not set' => [
|
|
'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, null, false
|
|
],
|
|
'Data deletion, turned on' => [
|
|
'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, true, true
|
|
],
|
|
'Data deletion, turned off' => [
|
|
'automaticdatadeletionapproval', api::DATAREQUEST_TYPE_DELETE, false, false
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test for \tool_dataprivacy\api::is_automatic_request_approval_on().
|
|
*
|
|
* @dataProvider automatic_request_approval_setting_provider
|
|
* @param string $setting The automatic approval setting.
|
|
* @param int $type The data request type.
|
|
* @param bool $value The setting's value.
|
|
* @param bool $expected The expected result.
|
|
*/
|
|
public function test_is_automatic_request_approval_on($setting, $type, $value, $expected) {
|
|
$this->resetAfterTest();
|
|
|
|
if ($value !== null) {
|
|
set_config($setting, $value, 'tool_dataprivacy');
|
|
}
|
|
|
|
$this->assertEquals($expected, api::is_automatic_request_approval_on($type));
|
|
}
|
|
}
|