Merge branch 'MDL-62277-master' of git://github.com/bmbrands/moodle
[moodle.git] / admin / tool / dataprivacy / tests / api_test.php
CommitLineData
5efc1f9e
DM
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * API tests.
19 *
20 * @package tool_dataprivacy
21 * @copyright 2018 Jun Pataleta
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25use core\invalid_persistent_exception;
26use core\task\manager;
27use tool_dataprivacy\context_instance;
28use tool_dataprivacy\api;
29use tool_dataprivacy\data_registry;
30use tool_dataprivacy\expired_context;
31use tool_dataprivacy\data_request;
32use tool_dataprivacy\task\initiate_data_request_task;
33use tool_dataprivacy\task\process_data_request_task;
34
35defined('MOODLE_INTERNAL') || die();
36global $CFG;
37
38/**
39 * API tests.
40 *
41 * @package tool_dataprivacy
42 * @copyright 2018 Jun Pataleta
43 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
44 */
45class tool_dataprivacy_api_testcase extends advanced_testcase {
46
47 /**
48 * setUp.
49 */
50 public function setUp() {
51 $this->resetAfterTest();
52 }
53
54 /**
55 * Test for api::update_request_status().
56 */
57 public function test_update_request_status() {
58 $generator = new testing_data_generator();
59 $s1 = $generator->create_user();
60
61 // Create the sample data request.
62 $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
63
64 $requestid = $datarequest->get('id');
65
66 // Update with a valid status.
67 $result = api::update_request_status($requestid, api::DATAREQUEST_STATUS_COMPLETE);
68 $this->assertTrue($result);
69
70 // Fetch the request record again.
71 $datarequest = new data_request($requestid);
72 $this->assertEquals(api::DATAREQUEST_STATUS_COMPLETE, $datarequest->get('status'));
73
74 // Update with an invalid status.
75 $this->expectException(invalid_persistent_exception::class);
76 api::update_request_status($requestid, -1);
77 }
78
79 /**
80 * Test for api::get_site_dpos() when there are no users with the DPO role.
81 */
82 public function test_get_site_dpos_no_dpos() {
83 $admin = get_admin();
84
85 $dpos = api::get_site_dpos();
86 $this->assertCount(1, $dpos);
87 $dpo = reset($dpos);
88 $this->assertEquals($admin->id, $dpo->id);
89 }
90
91 /**
92 * Test for api::get_site_dpos() when there are no users with the DPO role.
93 */
94 public function test_get_site_dpos() {
95 global $DB;
96 $generator = new testing_data_generator();
97 $u1 = $generator->create_user();
98 $u2 = $generator->create_user();
99
100 $context = context_system::instance();
101
102 // Give the manager role with the capability to manage data requests.
103 $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
104 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
105 // Assign u1 as a manager.
106 role_assign($managerroleid, $u1->id, $context->id);
107
108 // Give the editing teacher role with the capability to manage data requests.
109 $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
110 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $editingteacherroleid, $context->id, true);
111 // Assign u1 as an editing teacher as well.
112 role_assign($editingteacherroleid, $u1->id, $context->id);
113 // Assign u2 as an editing teacher.
114 role_assign($editingteacherroleid, $u2->id, $context->id);
115
116 // Only map the manager role to the DPO role.
117 set_config('dporoles', $managerroleid, 'tool_dataprivacy');
118
119 $dpos = api::get_site_dpos();
120 $this->assertCount(1, $dpos);
121 $dpo = reset($dpos);
122 $this->assertEquals($u1->id, $dpo->id);
123 }
124
125 /**
126 * Test for api::approve_data_request().
127 */
128 public function test_approve_data_request() {
129 global $DB;
130
131 $generator = new testing_data_generator();
132 $s1 = $generator->create_user();
133 $u1 = $generator->create_user();
134
135 $context = context_system::instance();
136
137 // Manager role.
138 $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
139 // Give the manager role with the capability to manage data requests.
140 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
141 // Assign u1 as a manager.
142 role_assign($managerroleid, $u1->id, $context->id);
143
144 // Map the manager role to the DPO role.
145 set_config('dporoles', $managerroleid, 'tool_dataprivacy');
146
147 // Create the sample data request.
148 $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
149 $requestid = $datarequest->get('id');
150
151 // Make this ready for approval.
152 api::update_request_status($requestid, api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
153
154 $this->setUser($u1);
155 $result = api::approve_data_request($requestid);
156 $this->assertTrue($result);
157 $datarequest = new data_request($requestid);
158 $this->assertEquals($u1->id, $datarequest->get('dpo'));
159 $this->assertEquals(api::DATAREQUEST_STATUS_APPROVED, $datarequest->get('status'));
160
161 // Test adhoc task creation.
162 $adhoctasks = manager::get_adhoc_tasks(process_data_request_task::class);
163 $this->assertCount(1, $adhoctasks);
164 }
165
166 /**
167 * Test for api::approve_data_request() with the request not yet waiting for approval.
168 */
169 public function test_approve_data_request_not_yet_ready() {
170 global $DB;
171
172 $generator = new testing_data_generator();
173 $s1 = $generator->create_user();
174 $u1 = $generator->create_user();
175
176 $context = context_system::instance();
177
178 // Manager role.
179 $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
180 // Give the manager role with the capability to manage data requests.
181 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
182 // Assign u1 as a manager.
183 role_assign($managerroleid, $u1->id, $context->id);
184
185 // Map the manager role to the DPO role.
186 set_config('dporoles', $managerroleid, 'tool_dataprivacy');
187
188 // Create the sample data request.
189 $datarequest = api::create_data_request($s1->id, api::DATAREQUEST_TYPE_EXPORT);
190 $requestid = $datarequest->get('id');
191
192 $this->setUser($u1);
193 $this->expectException(moodle_exception::class);
194 api::approve_data_request($requestid);
195 }
196
197 /**
198 * Test for api::approve_data_request() when called by a user who doesn't have the DPO role.
199 */
200 public function test_approve_data_request_non_dpo_user() {
201 $generator = new testing_data_generator();
202 $student = $generator->create_user();
203 $teacher = $generator->create_user();
204
205 // Create the sample data request.
206 $datarequest = api::create_data_request($student->id, api::DATAREQUEST_TYPE_EXPORT);
207
208 $requestid = $datarequest->get('id');
209
210 // Login as a user without DPO role.
211 $this->setUser($teacher);
212 $this->expectException(required_capability_exception::class);
213 api::approve_data_request($requestid);
214 }
215
216 /**
217 * Test for api::can_contact_dpo()
218 */
219 public function test_can_contact_dpo() {
ba5b59c0 220 // Default ('contactdataprotectionofficer' is disabled by default).
5efc1f9e
DM
221 $this->assertFalse(api::can_contact_dpo());
222
ba5b59c0 223 // Enable.
5efc1f9e
DM
224 set_config('contactdataprotectionofficer', 1, 'tool_dataprivacy');
225 $this->assertTrue(api::can_contact_dpo());
ba5b59c0
JP
226
227 // Disable again.
228 set_config('contactdataprotectionofficer', 0, 'tool_dataprivacy');
229 $this->assertFalse(api::can_contact_dpo());
5efc1f9e
DM
230 }
231
232 /**
233 * Test for api::can_manage_data_requests()
234 */
235 public function test_can_manage_data_requests() {
236 global $DB;
237
238 // No configured site DPOs yet.
239 $admin = get_admin();
240 $this->assertTrue(api::can_manage_data_requests($admin->id));
241
242 $generator = new testing_data_generator();
243 $dpo = $generator->create_user();
244 $nondpocapable = $generator->create_user();
245 $nondpoincapable = $generator->create_user();
246
247 $context = context_system::instance();
248
249 // Manager role.
250 $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
251 // Give the manager role with the capability to manage data requests.
252 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
253 // Assign u1 as a manager.
254 role_assign($managerroleid, $dpo->id, $context->id);
255
256 // Editing teacher role.
257 $editingteacherroleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
258 // Give the editing teacher role with the capability to manage data requests.
259 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
260 // Assign u2 as an editing teacher.
261 role_assign($editingteacherroleid, $nondpocapable->id, $context->id);
262
263 // Map only the manager role to the DPO role.
264 set_config('dporoles', $managerroleid, 'tool_dataprivacy');
265
266 // User with capability and has DPO role.
267 $this->assertTrue(api::can_manage_data_requests($dpo->id));
268 // User with capability but has no DPO role.
269 $this->assertFalse(api::can_manage_data_requests($nondpocapable->id));
270 // User without the capability and has no DPO role.
271 $this->assertFalse(api::can_manage_data_requests($nondpoincapable->id));
272 }
273
274 /**
275 * Test for api::create_data_request()
276 */
277 public function test_create_data_request() {
278 $generator = new testing_data_generator();
279 $user = $generator->create_user();
280 $comment = 'sample comment';
281
282 // Login as user.
283 $this->setUser($user->id);
284
285 // Test data request creation.
286 $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
287 $this->assertEquals($user->id, $datarequest->get('userid'));
288 $this->assertEquals($user->id, $datarequest->get('requestedby'));
289 $this->assertEquals(api::DATAREQUEST_TYPE_EXPORT, $datarequest->get('type'));
290 $this->assertEquals(api::DATAREQUEST_STATUS_PENDING, $datarequest->get('status'));
291 $this->assertEquals($comment, $datarequest->get('comments'));
292
293 // Test adhoc task creation.
294 $adhoctasks = manager::get_adhoc_tasks(initiate_data_request_task::class);
295 $this->assertCount(1, $adhoctasks);
296 }
297
298 /**
299 * Test for api::deny_data_request()
300 */
301 public function test_deny_data_request() {
302 $generator = new testing_data_generator();
303 $user = $generator->create_user();
304 $comment = 'sample comment';
305
306 // Login as user.
307 $this->setUser($user->id);
308
309 // Test data request creation.
310 $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
311
312 // Login as the admin (default DPO when no one is set).
313 $this->setAdminUser();
314
315 // Make this ready for approval.
316 api::update_request_status($datarequest->get('id'), api::DATAREQUEST_STATUS_AWAITING_APPROVAL);
317
318 // Deny the data request.
319 $result = api::deny_data_request($datarequest->get('id'));
320 $this->assertTrue($result);
321 }
322
323 /**
324 * Test for api::deny_data_request()
325 */
326 public function test_deny_data_request_without_permissions() {
327 $generator = new testing_data_generator();
328 $user = $generator->create_user();
329 $comment = 'sample comment';
330
331 // Login as user.
332 $this->setUser($user->id);
333
334 // Test data request creation.
335 $datarequest = api::create_data_request($user->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
336
337 // Login as a non-DPO user and try to call deny_data_request.
338 $user2 = $generator->create_user();
339 $this->setUser($user2);
340 $this->expectException(required_capability_exception::class);
341 api::deny_data_request($datarequest->get('id'));
342 }
343
344 /**
345 * Test for api::get_data_requests()
346 */
347 public function test_get_data_requests() {
348 $generator = new testing_data_generator();
349 $user1 = $generator->create_user();
350 $user2 = $generator->create_user();
351 $comment = 'sample comment';
352
353 // Make a data request as user 1.
354 $d1 = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
355 // Make a data request as user 2.
356 $d2 = api::create_data_request($user2->id, api::DATAREQUEST_TYPE_EXPORT, $comment);
357
358 // Fetching data requests of specific users.
359 $requests = api::get_data_requests($user1->id);
360 $this->assertCount(1, $requests);
361 $datarequest = reset($requests);
362 $this->assertEquals($d1->to_record(), $datarequest->to_record());
363
364 $requests = api::get_data_requests($user2->id);
365 $this->assertCount(1, $requests);
366 $datarequest = reset($requests);
367 $this->assertEquals($d2->to_record(), $datarequest->to_record());
368
369 // Fetching data requests of all users.
370 // As guest.
371 $this->setGuestUser();
372 $requests = api::get_data_requests();
373 $this->assertEmpty($requests);
374
375 // As DPO (admin in this case, which is default if no site DPOs are set).
376 $this->setAdminUser();
377 $requests = api::get_data_requests();
378 $this->assertCount(2, $requests);
379 }
380
381 /**
382 * Data provider for test_has_ongoing_request.
383 */
384 public function status_provider() {
385 return [
386 [api::DATAREQUEST_STATUS_PENDING, true],
387 [api::DATAREQUEST_STATUS_PREPROCESSING, true],
388 [api::DATAREQUEST_STATUS_AWAITING_APPROVAL, true],
389 [api::DATAREQUEST_STATUS_APPROVED, true],
390 [api::DATAREQUEST_STATUS_PROCESSING, true],
391 [api::DATAREQUEST_STATUS_COMPLETE, false],
392 [api::DATAREQUEST_STATUS_CANCELLED, false],
393 [api::DATAREQUEST_STATUS_REJECTED, false],
394 ];
395 }
396
397 /**
398 * Test for api::has_ongoing_request()
399 *
400 * @dataProvider status_provider
401 * @param int $status The request status.
402 * @param bool $expected The expected result.
403 */
404 public function test_has_ongoing_request($status, $expected) {
405 $generator = new testing_data_generator();
406 $user1 = $generator->create_user();
407
408 // Make a data request as user 1.
409 $request = api::create_data_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
410 // Set the status.
411 api::update_request_status($request->get('id'), $status);
412
413 // Check if this request is ongoing.
414 $result = api::has_ongoing_request($user1->id, api::DATAREQUEST_TYPE_EXPORT);
415 $this->assertEquals($expected, $result);
416 }
417
418 /**
419 * Test for api::is_active()
420 *
421 * @dataProvider status_provider
422 * @param int $status The request status
423 * @param bool $expected The expected result
424 */
425 public function test_is_active($status, $expected) {
426 // Check if this request is ongoing.
427 $result = api::is_active($status);
428 $this->assertEquals($expected, $result);
429 }
430
431 /**
432 * Test for api::is_site_dpo()
433 */
434 public function test_is_site_dpo() {
435 global $DB;
436
437 // No configured site DPOs yet.
438 $admin = get_admin();
439 $this->assertTrue(api::is_site_dpo($admin->id));
440
441 $generator = new testing_data_generator();
442 $dpo = $generator->create_user();
443 $nondpo = $generator->create_user();
444
445 $context = context_system::instance();
446
447 // Manager role.
448 $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
449 // Give the manager role with the capability to manage data requests.
450 assign_capability('tool/dataprivacy:managedatarequests', CAP_ALLOW, $managerroleid, $context->id, true);
451 // Assign u1 as a manager.
452 role_assign($managerroleid, $dpo->id, $context->id);
453
454 // Map only the manager role to the DPO role.
455 set_config('dporoles', $managerroleid, 'tool_dataprivacy');
456
457 // User is a DPO.
458 $this->assertTrue(api::is_site_dpo($dpo->id));
459 // User is not a DPO.
460 $this->assertFalse(api::is_site_dpo($nondpo->id));
461 }
462
463 /**
464 * Data provider function for test_notify_dpo
465 *
466 * @return array
467 */
468 public function notify_dpo_provider() {
469 return [
ba5b59c0
JP
470 [false, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Export my user data'],
471 [false, api::DATAREQUEST_TYPE_DELETE, 'requesttypedelete', 'Delete my user data'],
472 [false, api::DATAREQUEST_TYPE_OTHERS, 'requesttypeothers', 'Nothing. Just wanna say hi'],
473 [true, api::DATAREQUEST_TYPE_EXPORT, 'requesttypeexport', 'Admin export data of another user'],
5efc1f9e
DM
474 ];
475 }
476
477 /**
478 * Test for api::notify_dpo()
479 *
480 * @dataProvider notify_dpo_provider
ba5b59c0 481 * @param bool $byadmin Whether the admin requests data on behalf of the user
5efc1f9e
DM
482 * @param int $type The request type
483 * @param string $typestringid The request lang string identifier
ba5b59c0 484 * @param string $comments The requestor's message to the DPO.
5efc1f9e 485 */
ba5b59c0 486 public function test_notify_dpo($byadmin, $type, $typestringid, $comments) {
5efc1f9e
DM
487 $generator = new testing_data_generator();
488 $user1 = $generator->create_user();
ba5b59c0
JP
489 // Let's just use admin as DPO (It's the default if not set).
490 $dpo = get_admin();
491 if ($byadmin) {
492 $this->setAdminUser();
493 $requestedby = $dpo;
494 } else {
495 $this->setUser($user1);
496 $requestedby = $user1;
497 }
5efc1f9e 498
ba5b59c0
JP
499 // Make a data request for user 1.
500 $request = api::create_data_request($user1->id, $type, $comments);
5efc1f9e
DM
501
502 $sink = $this->redirectMessages();
5efc1f9e
DM
503 $messageid = api::notify_dpo($dpo, $request);
504 $this->assertNotFalse($messageid);
505 $messages = $sink->get_messages();
506 $this->assertCount(1, $messages);
507 $message = reset($messages);
508
509 // Check some of the message properties.
ba5b59c0 510 $this->assertEquals($requestedby->id, $message->useridfrom);
5efc1f9e
DM
511 $this->assertEquals($dpo->id, $message->useridto);
512 $typestring = get_string($typestringid, 'tool_dataprivacy');
513 $subject = get_string('datarequestemailsubject', 'tool_dataprivacy', $typestring);
514 $this->assertEquals($subject, $message->subject);
515 $this->assertEquals('tool_dataprivacy', $message->component);
516 $this->assertEquals('contactdataprotectionofficer', $message->eventtype);
517 $this->assertContains(fullname($dpo), $message->fullmessage);
518 $this->assertContains(fullname($user1), $message->fullmessage);
519 }
520
521 /**
522 * Test of creating purpose as a user without privileges.
523 */
524 public function test_create_purpose_non_dpo_user() {
525 $pleb = $this->getDataGenerator()->create_user();
526
527 $this->setUser($pleb);
528 $this->expectException(required_capability_exception::class);
529 api::create_purpose((object)[
530 'name' => 'aaa',
531 'description' => '<b>yeah</b>',
532 'descriptionformat' => 1,
533 'retentionperiod' => 'PT1M'
534 ]);
535 }
536
537 /**
538 * Test fetching of purposes as a user without privileges.
539 */
540 public function test_get_purposes_non_dpo_user() {
541 $pleb = $this->getDataGenerator()->create_user();
542 $this->setAdminUser();
543 api::create_purpose((object)[
544 'name' => 'bbb',
545 'description' => '<b>yeah</b>',
546 'descriptionformat' => 1,
0462786a
JP
547 'retentionperiod' => 'PT1M',
548 'lawfulbases' => 'gdpr_art_6_1_a'
5efc1f9e
DM
549 ]);
550
551 $this->setUser($pleb);
552 $this->expectException(required_capability_exception::class);
553 api::get_purposes();
554 }
555
556 /**
557 * Test updating of purpose as a user without privileges.
558 */
559 public function test_update_purposes_non_dpo_user() {
560 $pleb = $this->getDataGenerator()->create_user();
561 $this->setAdminUser();
562 $purpose = api::create_purpose((object)[
563 'name' => 'bbb',
564 'description' => '<b>yeah</b>',
565 'descriptionformat' => 1,
0462786a
JP
566 'retentionperiod' => 'PT1M',
567 'lawfulbases' => 'gdpr_art_6_1_a'
5efc1f9e
DM
568 ]);
569
570 $this->setUser($pleb);
571 $this->expectException(required_capability_exception::class);
572 $purpose->set('retentionperiod', 'PT2M');
573 api::update_purpose($purpose->to_record());
574 }
575
576 /**
577 * Test purpose deletion as a user without privileges.
578 */
579 public function test_delete_purpose_non_dpo_user() {
580 $pleb = $this->getDataGenerator()->create_user();
581 $this->setAdminUser();
582 $purpose = api::create_purpose((object)[
583 'name' => 'bbb',
584 'description' => '<b>yeah</b>',
585 'descriptionformat' => 1,
0462786a
JP
586 'retentionperiod' => 'PT1M',
587 'lawfulbases' => 'gdpr_art_6_1_a'
5efc1f9e
DM
588 ]);
589
590 $this->setUser($pleb);
591 $this->expectException(required_capability_exception::class);
592 api::delete_purpose($purpose->get('id'));
593 }
594
595 /**
596 * Test data purposes CRUD actions.
597 *
598 * @return null
599 */
600 public function test_purpose_crud() {
601
602 $this->setAdminUser();
603
604 // Add.
605 $purpose = api::create_purpose((object)[
606 'name' => 'bbb',
607 'description' => '<b>yeah</b>',
608 'descriptionformat' => 1,
0462786a
JP
609 'retentionperiod' => 'PT1M',
610 'lawfulbases' => 'gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e'
5efc1f9e
DM
611 ]);
612 $this->assertInstanceOf('\tool_dataprivacy\purpose', $purpose);
613 $this->assertEquals('bbb', $purpose->get('name'));
614 $this->assertEquals('PT1M', $purpose->get('retentionperiod'));
0462786a 615 $this->assertEquals('gdpr_art_6_1_a,gdpr_art_6_1_c,gdpr_art_6_1_e', $purpose->get('lawfulbases'));
5efc1f9e
DM
616
617 // Update.
618 $purpose->set('retentionperiod', 'PT2M');
619 $purpose = api::update_purpose($purpose->to_record());
620 $this->assertEquals('PT2M', $purpose->get('retentionperiod'));
621
622 // Retrieve.
0462786a 623 $purpose = api::create_purpose((object)['name' => 'aaa', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
5efc1f9e
DM
624 $purposes = api::get_purposes();
625 $this->assertCount(2, $purposes);
626 $this->assertEquals('aaa', $purposes[0]->get('name'));
627 $this->assertEquals('bbb', $purposes[1]->get('name'));
628
629 // Delete.
630 api::delete_purpose($purposes[0]->get('id'));
631 $this->assertCount(1, api::get_purposes());
632 api::delete_purpose($purposes[1]->get('id'));
633 $this->assertCount(0, api::get_purposes());
634 }
635
636 /**
637 * Test creation of data categories as a user without privileges.
638 */
639 public function test_create_category_non_dpo_user() {
640 $pleb = $this->getDataGenerator()->create_user();
641
642 $this->setUser($pleb);
643 $this->expectException(required_capability_exception::class);
644 api::create_category((object)[
645 'name' => 'bbb',
646 'description' => '<b>yeah</b>',
647 'descriptionformat' => 1
648 ]);
649 }
650
651 /**
652 * Test fetching of data categories as a user without privileges.
653 */
654 public function test_get_categories_non_dpo_user() {
655 $pleb = $this->getDataGenerator()->create_user();
656
657 $this->setAdminUser();
658 api::create_category((object)[
659 'name' => 'bbb',
660 'description' => '<b>yeah</b>',
661 'descriptionformat' => 1
662 ]);
663
664 // Back to a regular user.
665 $this->setUser($pleb);
666 $this->expectException(required_capability_exception::class);
667 api::get_categories();
668 }
669
670 /**
671 * Test updating of data category as a user without privileges.
672 */
673 public function test_update_category_non_dpo_user() {
674 $pleb = $this->getDataGenerator()->create_user();
675
676 $this->setAdminUser();
677 $category = api::create_category((object)[
678 'name' => 'bbb',
679 'description' => '<b>yeah</b>',
680 'descriptionformat' => 1
681 ]);
682
683 // Back to a regular user.
684 $this->setUser($pleb);
685 $this->expectException(required_capability_exception::class);
686 $category->set('name', 'yeah');
687 api::update_category($category->to_record());
688 }
689
690 /**
691 * Test deletion of data category as a user without privileges.
692 */
693 public function test_delete_category_non_dpo_user() {
694 $pleb = $this->getDataGenerator()->create_user();
695
696 $this->setAdminUser();
697 $category = api::create_category((object)[
698 'name' => 'bbb',
699 'description' => '<b>yeah</b>',
700 'descriptionformat' => 1
701 ]);
702
703 // Back to a regular user.
704 $this->setUser($pleb);
705 $this->expectException(required_capability_exception::class);
706 api::delete_category($category->get('id'));
707 $this->fail('Users shouldn\'t be allowed to manage categories by default');
708 }
709
710 /**
711 * Test data categories CRUD actions.
712 *
713 * @return null
714 */
715 public function test_category_crud() {
716
717 $this->setAdminUser();
718
719 // Add.
720 $category = api::create_category((object)[
721 'name' => 'bbb',
722 'description' => '<b>yeah</b>',
723 'descriptionformat' => 1
724 ]);
725 $this->assertInstanceOf('\tool_dataprivacy\category', $category);
726 $this->assertEquals('bbb', $category->get('name'));
727
728 // Update.
729 $category->set('name', 'bcd');
730 $category = api::update_category($category->to_record());
731 $this->assertEquals('bcd', $category->get('name'));
732
733 // Retrieve.
734 $category = api::create_category((object)['name' => 'aaa']);
735 $categories = api::get_categories();
736 $this->assertCount(2, $categories);
737 $this->assertEquals('aaa', $categories[0]->get('name'));
738 $this->assertEquals('bcd', $categories[1]->get('name'));
739
740 // Delete.
741 api::delete_category($categories[0]->get('id'));
742 $this->assertCount(1, api::get_categories());
743 api::delete_category($categories[1]->get('id'));
744 $this->assertCount(0, api::get_categories());
745 }
746
747 /**
748 * Test context instances.
749 *
750 * @return null
751 */
752 public function test_context_instances() {
753 global $DB;
754
755 $this->setAdminUser();
756
757 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
758
759 $coursecontext1 = \context_course::instance($courses[0]->id);
760 $coursecontext2 = \context_course::instance($courses[1]->id);
761
a8a69050
DM
762 $record1 = (object)['contextid' => $coursecontext1->id, 'purposeid' => $purposes[0]->get('id'),
763 'categoryid' => $categories[0]->get('id')];
5efc1f9e
DM
764 $contextinstance1 = api::set_context_instance($record1);
765
a8a69050
DM
766 $record2 = (object)['contextid' => $coursecontext2->id, 'purposeid' => $purposes[1]->get('id'),
767 'categoryid' => $categories[1]->get('id')];
5efc1f9e
DM
768 $contextinstance2 = api::set_context_instance($record2);
769
770 $this->assertCount(2, $DB->get_records('tool_dataprivacy_ctxinstance'));
771
772 api::unset_context_instance($contextinstance1);
773 $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
774
775 $update = (object)['id' => $contextinstance2->get('id'), 'contextid' => $coursecontext2->id,
776 'purposeid' => $purposes[0]->get('id'), 'categoryid' => $categories[0]->get('id')];
777 $contextinstance2 = api::set_context_instance($update);
778 $this->assertCount(1, $DB->get_records('tool_dataprivacy_ctxinstance'));
779 }
780
781 /**
782 * Test contextlevel.
783 *
784 * @return null
785 */
786 public function test_contextlevel() {
787 global $DB;
788
789 $this->setAdminUser();
790 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
791
792 $record = (object)[
793 'purposeid' => $purposes[0]->get('id'),
794 'categoryid' => $categories[0]->get('id'),
795 'contextlevel' => CONTEXT_SYSTEM,
796 ];
797 $contextlevel = api::set_contextlevel($record);
798 $this->assertInstanceOf('\tool_dataprivacy\contextlevel', $contextlevel);
799 $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
800 $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
801 $this->assertEquals($record->categoryid, $contextlevel->get('categoryid'));
802
803 // Now update it.
804 $record->purposeid = $purposes[1]->get('id');
805 $contextlevel = api::set_contextlevel($record);
806 $this->assertEquals($record->contextlevel, $contextlevel->get('contextlevel'));
807 $this->assertEquals($record->purposeid, $contextlevel->get('purposeid'));
808 $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxlevel'));
809
810 $record->contextlevel = CONTEXT_USER;
811 $contextlevel = api::set_contextlevel($record);
812 $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxlevel'));
813 }
814
815 /**
816 * Test effective context levels purpose and category defaults.
817 *
818 * @return null
819 */
820 public function test_effective_contextlevel_defaults() {
821 $this->setAdminUser();
822
823 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
824
825 list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
826 $this->assertEquals(false, $purposeid);
827 $this->assertEquals(false, $categoryid);
828
829 list($purposevar, $categoryvar) = data_registry::var_names_from_context(
830 \context_helper::get_class_for_level(CONTEXT_SYSTEM)
831 );
832 set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
833
834 list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_SYSTEM);
835 $this->assertEquals($purposes[0]->get('id'), $purposeid);
836 $this->assertEquals(false, $categoryid);
837
838 // Course inherits from system if not defined.
839 list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
840 $this->assertEquals($purposes[0]->get('id'), $purposeid);
841 $this->assertEquals(false, $categoryid);
842
843 // Course defined values should have preference.
844 list($purposevar, $categoryvar) = data_registry::var_names_from_context(
845 \context_helper::get_class_for_level(CONTEXT_COURSE)
846 );
847 set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
848 set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
849
850 list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
851 $this->assertEquals($purposes[1]->get('id'), $purposeid);
852 $this->assertEquals($categories[0]->get('id'), $categoryid);
853
854 // Context level defaults are also allowed to be set to 'inherit'.
855 set_config($purposevar, context_instance::INHERIT, 'tool_dataprivacy');
856
857 list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_COURSE);
858 $this->assertEquals($purposes[0]->get('id'), $purposeid);
859 $this->assertEquals($categories[0]->get('id'), $categoryid);
860
861 list($purposeid, $categoryid) = data_registry::get_effective_default_contextlevel_purpose_and_category(CONTEXT_MODULE);
862 $this->assertEquals($purposes[0]->get('id'), $purposeid);
863 $this->assertEquals($categories[0]->get('id'), $categoryid);
864 }
865
866 /**
867 * Test effective contextlevel return.
868 *
869 * @return null
870 */
871 public function test_effective_contextlevel() {
872 $this->setAdminUser();
873
874 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
875
876 // Set the system context level to purpose 1.
877 $record = (object)[
878 'contextlevel' => CONTEXT_SYSTEM,
879 'purposeid' => $purposes[1]->get('id'),
880 'categoryid' => $categories[1]->get('id'),
881 ];
882 api::set_contextlevel($record);
883
884 $purpose = api::get_effective_contextlevel_purpose(CONTEXT_SYSTEM);
885 $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
886
a8a69050 887 // Value 'not set' will get the default value for the context level. For context level defaults
5efc1f9e
DM
888 // both 'not set' and 'inherit' result in inherit, so the parent context (system) default
889 // will be retrieved.
890 $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
891 $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
892
893 // The behaviour forcing an inherit from context system should result in the same effective
894 // purpose.
895 $record->purposeid = context_instance::INHERIT;
896 $record->contextlevel = CONTEXT_USER;
897 api::set_contextlevel($record);
898 $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
899 $this->assertEquals($purposes[1]->get('id'), $purpose->get('id'));
900
901 $record->purposeid = $purposes[2]->get('id');
902 $record->contextlevel = CONTEXT_USER;
903 api::set_contextlevel($record);
904
905 $purpose = api::get_effective_contextlevel_purpose(CONTEXT_USER);
906 $this->assertEquals($purposes[2]->get('id'), $purpose->get('id'));
907
908 // Only system and user allowed.
909 $this->expectException(coding_exception::class);
910 $record->contextlevel = CONTEXT_COURSE;
911 $record->purposeid = $purposes[1]->get('id');
912 api::set_contextlevel($record);
913 }
914
915 /**
916 * Test effective context purposes and categories.
917 *
918 * @return null
919 */
920 public function test_effective_context() {
921 $this->setAdminUser();
922
923 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
924
925 // Define system defaults (all context levels below will inherit).
926 list($purposevar, $categoryvar) = data_registry::var_names_from_context(
927 \context_helper::get_class_for_level(CONTEXT_SYSTEM)
928 );
929 set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
930 set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
931
932 // Define course defaults.
933 list($purposevar, $categoryvar) = data_registry::var_names_from_context(
934 \context_helper::get_class_for_level(CONTEXT_COURSE)
935 );
936 set_config($purposevar, $purposes[1]->get('id'), 'tool_dataprivacy');
937 set_config($categoryvar, $categories[1]->get('id'), 'tool_dataprivacy');
938
939 $course0context = \context_course::instance($courses[0]->id);
940 $course1context = \context_course::instance($courses[1]->id);
941 $mod0context = \context_module::instance($modules[0]->cmid);
942 $mod1context = \context_module::instance($modules[1]->cmid);
943
944 // Set course instance values.
945 $record = (object)[
946 'contextid' => $course0context->id,
947 'purposeid' => $purposes[1]->get('id'),
948 'categoryid' => $categories[2]->get('id'),
949 ];
950 api::set_context_instance($record);
951 $category = api::get_effective_context_category($course0context);
952 $this->assertEquals($record->categoryid, $category->get('id'));
953
954 // Module instances get the context level default if nothing specified.
955 $category = api::get_effective_context_category($mod0context);
956 $this->assertEquals($categories[1]->get('id'), $category->get('id'));
957
958 // Module instances get the parent context category if they inherit.
959 $record->contextid = $mod0context->id;
960 $record->categoryid = context_instance::INHERIT;
961 api::set_context_instance($record);
962 $category = api::get_effective_context_category($mod0context);
963 $this->assertEquals($categories[2]->get('id'), $category->get('id'));
964
965 // The $forcedvalue param allows us to override the actual value (method php-docs for more info).
966 $category = api::get_effective_context_category($mod0context, $categories[1]->get('id'));
967 $this->assertEquals($categories[1]->get('id'), $category->get('id'));
968 $category = api::get_effective_context_category($mod0context, $categories[0]->get('id'));
969 $this->assertEquals($categories[0]->get('id'), $category->get('id'));
970
971 // Module instances get the parent context category if they inherit; in
972 // this case the parent context category is not set so it should use the
973 // context level default (see 'Define course defaults' above).
974 $record->contextid = $mod1context->id;
975 $record->categoryid = context_instance::INHERIT;
976 api::set_context_instance($record);
977 $category = api::get_effective_context_category($mod1context);
978 $this->assertEquals($categories[1]->get('id'), $category->get('id'));
979
980 // User instances use the value set at user context level instead of the user default.
981
982 // User defaults to cat 0 and user context level to 1.
983 list($purposevar, $categoryvar) = data_registry::var_names_from_context(
984 \context_helper::get_class_for_level(CONTEXT_USER)
985 );
986 set_config($purposevar, $purposes[0]->get('id'), 'tool_dataprivacy');
987 set_config($categoryvar, $categories[0]->get('id'), 'tool_dataprivacy');
988 $usercontextlevel = (object)[
989 'contextlevel' => CONTEXT_USER,
990 'purposeid' => $purposes[1]->get('id'),
991 'categoryid' => $categories[1]->get('id'),
992 ];
993 api::set_contextlevel($usercontextlevel);
994
995 $newuser = $this->getDataGenerator()->create_user();
996 $usercontext = \context_user::instance($newuser->id);
997 $category = api::get_effective_context_category($usercontext);
998 $this->assertEquals($categories[1]->get('id'), $category->get('id'));
999 }
1000
1001 /**
1002 * Tests the deletion of expired contexts.
1003 *
1004 * @return null
1005 */
1006 public function test_expired_context_deletion() {
1007 global $DB;
1008
1009 $this->setAdminUser();
1010
1011 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1012
1013 $course0context = \context_course::instance($courses[0]->id);
1014 $course1context = \context_course::instance($courses[1]->id);
1015
1016 $expiredcontext0 = api::create_expired_context($course0context->id);
1017 $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1018 $expiredcontext1 = api::create_expired_context($course1context->id);
1019 $this->assertEquals(2, $DB->count_records('tool_dataprivacy_ctxexpired'));
1020
1021 api::delete_expired_context($expiredcontext0->get('id'));
1022 $this->assertEquals(1, $DB->count_records('tool_dataprivacy_ctxexpired'));
1023 }
1024
1025 /**
1026 * Tests the status of expired contexts.
1027 *
1028 * @return null
1029 */
1030 public function test_expired_context_status() {
1031 global $DB;
1032
1033 $this->setAdminUser();
1034
1035 list($purposes, $categories, $courses, $modules) = $this->add_purposes_and_categories();
1036
1037 $course0context = \context_course::instance($courses[0]->id);
1038
1039 $expiredcontext = api::create_expired_context($course0context->id);
1040
1041 // Default status.
1042 $this->assertEquals(expired_context::STATUS_EXPIRED, $expiredcontext->get('status'));
1043
1044 api::set_expired_context_status($expiredcontext, expired_context::STATUS_APPROVED);
1045 $this->assertEquals(expired_context::STATUS_APPROVED, $expiredcontext->get('status'));
1046 }
1047
1048 /**
1049 * Creates test purposes and categories.
1050 *
1051 * @return null
1052 */
1053 protected function add_purposes_and_categories() {
1054
0462786a
JP
1055 $purpose1 = api::create_purpose((object)['name' => 'p1', 'retentionperiod' => 'PT1H', 'lawfulbases' => 'gdpr_art_6_1_a']);
1056 $purpose2 = api::create_purpose((object)['name' => 'p2', 'retentionperiod' => 'PT2H', 'lawfulbases' => 'gdpr_art_6_1_b']);
1057 $purpose3 = api::create_purpose((object)['name' => 'p3', 'retentionperiod' => 'PT3H', 'lawfulbases' => 'gdpr_art_6_1_c']);
5efc1f9e
DM
1058
1059 $cat1 = api::create_category((object)['name' => 'a']);
1060 $cat2 = api::create_category((object)['name' => 'b']);
1061 $cat3 = api::create_category((object)['name' => 'c']);
1062
1063 $course1 = $this->getDataGenerator()->create_course();
1064 $course2 = $this->getDataGenerator()->create_course();
1065
1066 $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
1067 $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
1068
1069 return [
1070 [$purpose1, $purpose2, $purpose3],
1071 [$cat1, $cat2, $cat3],
1072 [$course1, $course2],
1073 [$module1, $module2]
1074 ];
1075 }
00293f90
AN
1076
1077 /**
1078 * Test that delete requests filter out protected purpose contexts.
1079 */
1080 public function test_add_request_contexts_with_status_delete() {
1081 $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_DELETE);
1082 $contextids = $data->list->get_contextids();
1083
1084 $this->assertCount(1, $contextids);
1085 $this->assertEquals($data->contexts->unprotected, $contextids);
1086 }
1087
1088 /**
1089 * Test that export requests don't filter out protected purpose contexts.
1090 */
1091 public function test_add_request_contexts_with_status_export() {
1092 $data = $this->setup_test_add_request_contexts_with_status(api::DATAREQUEST_TYPE_EXPORT);
1093 $contextids = $data->list->get_contextids();
1094
1095 $this->assertCount(2, $contextids);
1096 $this->assertEquals($data->contexts->used, $contextids, '', 0.0, 10, true);
1097 }
1098
1099 /**
1100 * Perform setup for the test_add_request_contexts_with_status_xxxxx tests.
1101 *
1102 * @param int $type The type of request to create
1103 * @return \stdClass
1104 */
1105 protected function setup_test_add_request_contexts_with_status($type) {
1106 $this->setAdminUser();
1107
1108 // User under test.
1109 $s1 = $this->getDataGenerator()->create_user();
1110
1111 // Create three sample contexts.
1112 // 1 which should not be returned; and
1113 // 1 which will be returned and is not protected; and
1114 // 1 which will be returned and is protected.
1115
1116 $c1 = $this->getDataGenerator()->create_course();
1117 $c2 = $this->getDataGenerator()->create_course();
1118 $c3 = $this->getDataGenerator()->create_course();
1119
1120 $ctx1 = \context_course::instance($c1->id);
1121 $ctx2 = \context_course::instance($c2->id);
1122 $ctx3 = \context_course::instance($c3->id);
1123
1124 $unprotected = api::create_purpose((object)[
1125 'name' => 'Unprotected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a']);
1126 $protected = api::create_purpose((object) [
1127 'name' => 'Protected', 'retentionperiod' => 'PT1M', 'lawfulbases' => 'gdpr_art_6_1_a', 'protected' => true]);
1128
1129 $cat1 = api::create_category((object)['name' => 'a']);
1130
1131 // Set the defaults.
1132 list($purposevar, $categoryvar) = data_registry::var_names_from_context(
1133 \context_helper::get_class_for_level(CONTEXT_SYSTEM)
1134 );
1135 set_config($purposevar, $unprotected->get('id'), 'tool_dataprivacy');
1136 set_config($categoryvar, $cat1->get('id'), 'tool_dataprivacy');
1137
1138 $contextinstance1 = api::set_context_instance((object) [
1139 'contextid' => $ctx1->id,
1140 'purposeid' => $unprotected->get('id'),
1141 'categoryid' => $cat1->get('id'),
1142 ]);
1143
1144 $contextinstance2 = api::set_context_instance((object) [
1145 'contextid' => $ctx2->id,
1146 'purposeid' => $unprotected->get('id'),
1147 'categoryid' => $cat1->get('id'),
1148 ]);
1149
1150 $contextinstance3 = api::set_context_instance((object) [
1151 'contextid' => $ctx3->id,
1152 'purposeid' => $protected->get('id'),
1153 'categoryid' => $cat1->get('id'),
1154 ]);
1155
1156 $collection = new \core_privacy\local\request\contextlist_collection($s1->id);
1157 $contextlist = new \core_privacy\local\request\contextlist();
1158 $contextlist->set_component('tool_dataprivacy');
1159 $contextlist->add_from_sql('SELECT id FROM {context} WHERE id IN(:ctx2, :ctx3)', [
1160 'ctx2' => $ctx2->id,
1161 'ctx3' => $ctx3->id,
1162 ]);
1163
1164 $collection->add_contextlist($contextlist);
1165
1166 // Create the sample data request.
1167 $datarequest = api::create_data_request($s1->id, $type);
1168 $requestid = $datarequest->get('id');
1169
1170 // Add the full collection with contexts 2, and 3.
1171 api::add_request_contexts_with_status($collection, $requestid, \tool_dataprivacy\contextlist_context::STATUS_PENDING);
1172
1173 // Mark it as approved.
1174 api::update_request_contexts_with_status($requestid, \tool_dataprivacy\contextlist_context::STATUS_APPROVED);
1175
1176 // Fetch the list.
1177 $approvedcollection = api::get_approved_contextlist_collection_for_request($datarequest);
1178
1179 return (object) [
1180 'contexts' => (object) [
1181 'unused' => [
1182 $ctx1->id,
1183 ],
1184 'used' => [
1185 $ctx2->id,
1186 $ctx3->id,
1187 ],
1188 'unprotected' => [
1189 $ctx2->id,
1190 ],
1191 'protected' => [
1192 $ctx3->id,
1193 ],
1194 ],
1195 'list' => $approvedcollection->get_contextlist_for_component('tool_dataprivacy'),
1196 ];
1197 }
5efc1f9e 1198}