Merge branch 'MDL-63730-34-enfix' of git://github.com/mudrd8mz/moodle into MOODLE_34_...
[moodle.git] / user / tests / privacy_test.php
blobbd630f163ca7e5f19e70bed59849fd0d717a236d
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.
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 * Privacy tests for core_user.
19 * @package core_user
20 * @category test
21 * @copyright 2018 Adrian Greeve <adrian@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
26 global $CFG;
28 use \core_privacy\tests\provider_testcase;
29 use \core_user\privacy\provider;
30 use \core_privacy\local\request\approved_userlist;
32 require_once($CFG->dirroot . "/user/lib.php");
34 /**
35 * Unit tests for core_user.
37 * @copyright 2018 Adrian Greeve <adrian@moodle.com>
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 class core_user_privacy_testcase extends provider_testcase {
42 /**
43 * Check that context information is returned correctly.
45 public function test_get_contexts_for_userid() {
46 $this->resetAfterTest();
47 $user = $this->getDataGenerator()->create_user();
48 // Create some other users as well.
49 $user2 = $this->getDataGenerator()->create_user();
50 $user3 = $this->getDataGenerator()->create_user();
52 $context = context_user::instance($user->id);
53 $contextlist = \core_user\privacy\provider::get_contexts_for_userid($user->id);
54 $this->assertSame($context, $contextlist->current());
57 /**
58 * Test that data is exported as expected for a user.
60 public function test_export_user_data() {
61 $this->resetAfterTest();
62 $user = $this->getDataGenerator()->create_user();
63 $course = $this->getDataGenerator()->create_course();
64 $context = \context_user::instance($user->id);
66 $this->create_data_for_user($user, $course);
68 $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'core_user', [$context->id]);
70 $writer = \core_privacy\local\request\writer::with_context($context);
71 \core_user\privacy\provider::export_user_data($approvedlist);
73 // Make sure that the password history only returns a count.
74 $history = $writer->get_data([get_string('privacy:passwordhistorypath', 'user')]);
75 $objectcount = new ArrayObject($history);
76 // This object should only have one property.
77 $this->assertCount(1, $objectcount);
78 $this->assertEquals(1, $history->password_history_count);
80 // Password resets should have two fields - timerequested and timererequested.
81 $resetarray = (array) $writer->get_data([get_string('privacy:passwordresetpath', 'user')]);
82 $detail = array_shift($resetarray);
83 $this->assertTrue(array_key_exists('timerequested', $detail));
84 $this->assertTrue(array_key_exists('timererequested', $detail));
86 // Last access to course.
87 $lastcourseaccess = (array) $writer->get_data([get_string('privacy:lastaccesspath', 'user')]);
88 $entry = array_shift($lastcourseaccess);
89 $this->assertEquals($course->fullname, $entry['course_name']);
90 $this->assertTrue(array_key_exists('timeaccess', $entry));
92 // User devices.
93 $userdevices = (array) $writer->get_data([get_string('privacy:devicespath', 'user')]);
94 $entry = array_shift($userdevices);
95 $this->assertEquals('com.moodle.moodlemobile', $entry['appid']);
96 // Make sure these fields are not exported.
97 $this->assertFalse(array_key_exists('pushid', $entry));
98 $this->assertFalse(array_key_exists('uuid', $entry));
100 // Session data.
101 $sessiondata = (array) $writer->get_data([get_string('privacy:sessionpath', 'user')]);
102 $entry = array_shift($sessiondata);
103 // Make sure that the sid is not exported.
104 $this->assertFalse(array_key_exists('sid', $entry));
105 // Check that some of the other fields are present.
106 $this->assertTrue(array_key_exists('state', $entry));
107 $this->assertTrue(array_key_exists('sessdata', $entry));
108 $this->assertTrue(array_key_exists('timecreated', $entry));
110 // Course requests
111 $courserequestdata = (array) $writer->get_data([get_string('privacy:courserequestpath', 'user')]);
112 $entry = array_shift($courserequestdata);
113 // Make sure that the password is not exported.
114 $this->assertFalse(array_key_exists('password', $entry));
115 // Check that some of the other fields are present.
116 $this->assertTrue(array_key_exists('fullname', $entry));
117 $this->assertTrue(array_key_exists('shortname', $entry));
118 $this->assertTrue(array_key_exists('summary', $entry));
120 // User details.
121 $userdata = (array) $writer->get_data([]);
122 // Check that the password is not exported.
123 $this->assertFalse(array_key_exists('password', $userdata));
124 // Check that some critical fields exist.
125 $this->assertTrue(array_key_exists('firstname', $userdata));
126 $this->assertTrue(array_key_exists('lastname', $userdata));
127 $this->assertTrue(array_key_exists('email', $userdata));
131 * Test that user data is deleted for one user.
133 public function test_delete_data_for_all_users_in_context() {
134 global $DB;
135 $this->resetAfterTest();
136 $user = $this->getDataGenerator()->create_user([
137 'idnumber' => 'A0023',
138 'emailstop' => 1,
139 'icq' => 'aksdjf98',
140 'phone1' => '555 3257',
141 'institution' => 'test',
142 'department' => 'Science',
143 'city' => 'Perth',
144 'country' => 'au'
146 $user2 = $this->getDataGenerator()->create_user();
147 $course = $this->getDataGenerator()->create_course();
149 $this->create_data_for_user($user, $course);
150 $this->create_data_for_user($user2, $course);
152 \core_user\privacy\provider::delete_data_for_all_users_in_context(context_user::instance($user->id));
154 // These tables should not have any user data for $user. Only for $user2.
155 $records = $DB->get_records('user_password_history');
156 $this->assertCount(1, $records);
157 $data = array_shift($records);
158 $this->assertNotEquals($user->id, $data->userid);
159 $this->assertEquals($user2->id, $data->userid);
160 $records = $DB->get_records('user_password_resets');
161 $this->assertCount(1, $records);
162 $data = array_shift($records);
163 $this->assertNotEquals($user->id, $data->userid);
164 $this->assertEquals($user2->id, $data->userid);
165 $records = $DB->get_records('user_lastaccess');
166 $this->assertCount(1, $records);
167 $data = array_shift($records);
168 $this->assertNotEquals($user->id, $data->userid);
169 $this->assertEquals($user2->id, $data->userid);
170 $records = $DB->get_records('user_devices');
171 $this->assertCount(1, $records);
172 $data = array_shift($records);
173 $this->assertNotEquals($user->id, $data->userid);
174 $this->assertEquals($user2->id, $data->userid);
176 // Now check that there is still a record for the deleted user, but that non-critical information is removed.
177 $record = $DB->get_record('user', ['id' => $user->id]);
178 $this->assertEmpty($record->idnumber);
179 $this->assertEmpty($record->emailstop);
180 $this->assertEmpty($record->icq);
181 $this->assertEmpty($record->phone1);
182 $this->assertEmpty($record->institution);
183 $this->assertEmpty($record->department);
184 $this->assertEmpty($record->city);
185 $this->assertEmpty($record->country);
186 $this->assertEmpty($record->timezone);
187 $this->assertEmpty($record->timecreated);
188 $this->assertEmpty($record->timemodified);
189 $this->assertEmpty($record->firstnamephonetic);
190 // Check for critical fields.
191 // Deleted should now be 1.
192 $this->assertEquals(1, $record->deleted);
193 $this->assertEquals($user->id, $record->id);
194 $this->assertEquals($user->username, $record->username);
195 $this->assertEquals($user->password, $record->password);
196 $this->assertEquals($user->firstname, $record->firstname);
197 $this->assertEquals($user->lastname, $record->lastname);
198 $this->assertEquals($user->email, $record->email);
202 * Test that user data is deleted for one user.
204 public function test_delete_data_for_user() {
205 global $DB;
206 $this->resetAfterTest();
207 $user = $this->getDataGenerator()->create_user([
208 'idnumber' => 'A0023',
209 'emailstop' => 1,
210 'icq' => 'aksdjf98',
211 'phone1' => '555 3257',
212 'institution' => 'test',
213 'department' => 'Science',
214 'city' => 'Perth',
215 'country' => 'au'
217 $user2 = $this->getDataGenerator()->create_user();
218 $course = $this->getDataGenerator()->create_course();
220 $this->create_data_for_user($user, $course);
221 $this->create_data_for_user($user2, $course);
223 // Provide multiple different context to check that only the correct user is deleted.
224 $contexts = [context_user::instance($user->id)->id, context_user::instance($user2->id)->id, context_system::instance()->id];
225 $approvedlist = new \core_privacy\local\request\approved_contextlist($user, 'core_user', $contexts);
227 \core_user\privacy\provider::delete_data_for_user($approvedlist);
229 // These tables should not have any user data for $user. Only for $user2.
230 $records = $DB->get_records('user_password_history');
231 $this->assertCount(1, $records);
232 $data = array_shift($records);
233 $this->assertNotEquals($user->id, $data->userid);
234 $this->assertEquals($user2->id, $data->userid);
235 $records = $DB->get_records('user_password_resets');
236 $this->assertCount(1, $records);
237 $data = array_shift($records);
238 $this->assertNotEquals($user->id, $data->userid);
239 $this->assertEquals($user2->id, $data->userid);
240 $records = $DB->get_records('user_lastaccess');
241 $this->assertCount(1, $records);
242 $data = array_shift($records);
243 $this->assertNotEquals($user->id, $data->userid);
244 $this->assertEquals($user2->id, $data->userid);
245 $records = $DB->get_records('user_devices');
246 $this->assertCount(1, $records);
247 $data = array_shift($records);
248 $this->assertNotEquals($user->id, $data->userid);
249 $this->assertEquals($user2->id, $data->userid);
251 // Now check that there is still a record for the deleted user, but that non-critical information is removed.
252 $record = $DB->get_record('user', ['id' => $user->id]);
253 $this->assertEmpty($record->idnumber);
254 $this->assertEmpty($record->emailstop);
255 $this->assertEmpty($record->icq);
256 $this->assertEmpty($record->phone1);
257 $this->assertEmpty($record->institution);
258 $this->assertEmpty($record->department);
259 $this->assertEmpty($record->city);
260 $this->assertEmpty($record->country);
261 $this->assertEmpty($record->timezone);
262 $this->assertEmpty($record->timecreated);
263 $this->assertEmpty($record->timemodified);
264 $this->assertEmpty($record->firstnamephonetic);
265 // Check for critical fields.
266 // Deleted should now be 1.
267 $this->assertEquals(1, $record->deleted);
268 $this->assertEquals($user->id, $record->id);
269 $this->assertEquals($user->username, $record->username);
270 $this->assertEquals($user->password, $record->password);
271 $this->assertEquals($user->firstname, $record->firstname);
272 $this->assertEquals($user->lastname, $record->lastname);
273 $this->assertEquals($user->email, $record->email);
277 * Test that only users with a user context are fetched.
279 public function test_get_users_in_context() {
280 $this->resetAfterTest();
282 $component = 'core_user';
283 // Create a user.
284 $user = $this->getDataGenerator()->create_user();
285 $usercontext = \context_user::instance($user->id);
286 $userlist = new \core_privacy\local\request\userlist($usercontext, $component);
288 // The list of users for user context should return the user.
289 provider::get_users_in_context($userlist);
290 $this->assertCount(1, $userlist);
291 $expected = [$user->id];
292 $actual = $userlist->get_userids();
293 $this->assertEquals($expected, $actual);
295 // The list of users for system context should not return any users.
296 $systemcontext = context_system::instance();
297 $userlist = new \core_privacy\local\request\userlist($systemcontext, $component);
298 provider::get_users_in_context($userlist);
299 $this->assertCount(0, $userlist);
303 * Test that data for users in approved userlist is deleted.
305 public function test_delete_data_for_users() {
306 global $DB;
308 $this->resetAfterTest();
310 $component = 'core_user';
312 // Create user1.
313 $user1 = $this->getDataGenerator()->create_user([
314 'idnumber' => 'A0023',
315 'emailstop' => 1,
316 'icq' => 'aksdjf98',
317 'phone1' => '555 3257',
318 'institution' => 'test',
319 'department' => 'Science',
320 'city' => 'Perth',
321 'country' => 'au'
323 $usercontext1 = \context_user::instance($user1->id);
324 $userlist1 = new \core_privacy\local\request\userlist($usercontext1, $component);
326 // Create user2.
327 $user2 = $this->getDataGenerator()->create_user([
328 'idnumber' => 'A0024',
329 'emailstop' => 1,
330 'icq' => 'aksdjf981',
331 'phone1' => '555 3258',
332 'institution' => 'test',
333 'department' => 'Science',
334 'city' => 'Perth',
335 'country' => 'au'
337 $usercontext2 = \context_user::instance($user2->id);
338 $userlist2 = new \core_privacy\local\request\userlist($usercontext2, $component);
340 // The list of users for usercontext1 should return user1.
341 provider::get_users_in_context($userlist1);
342 $this->assertCount(1, $userlist1);
343 // The list of users for usercontext2 should return user2.
344 provider::get_users_in_context($userlist2);
345 $this->assertCount(1, $userlist2);
347 // Add userlist1 to the approved user list.
348 $approvedlist = new approved_userlist($usercontext1, $component, $userlist1->get_userids());
349 // Delete using delete_data_for_users().
350 provider::delete_data_for_users($approvedlist);
352 // Now check that there is still a record for user1 (deleted user), but non-critical information is removed.
353 $record = $DB->get_record('user', ['id' => $user1->id]);
354 $this->assertEmpty($record->idnumber);
355 $this->assertEmpty($record->emailstop);
356 $this->assertEmpty($record->icq);
357 $this->assertEmpty($record->phone1);
358 $this->assertEmpty($record->institution);
359 $this->assertEmpty($record->department);
360 $this->assertEmpty($record->city);
361 $this->assertEmpty($record->country);
362 $this->assertEmpty($record->timezone);
363 $this->assertEmpty($record->timecreated);
364 $this->assertEmpty($record->timemodified);
365 $this->assertEmpty($record->firstnamephonetic);
366 // Check for critical fields.
367 // Deleted should now be 1.
368 $this->assertEquals(1, $record->deleted);
369 $this->assertEquals($user1->id, $record->id);
370 $this->assertEquals($user1->username, $record->username);
371 $this->assertEquals($user1->password, $record->password);
372 $this->assertEquals($user1->firstname, $record->firstname);
373 $this->assertEquals($user1->lastname, $record->lastname);
374 $this->assertEquals($user1->email, $record->email);
376 // Now check that the record and information for user2 is still present.
377 $record = $DB->get_record('user', ['id' => $user2->id]);
378 $this->assertNotEmpty($record->idnumber);
379 $this->assertNotEmpty($record->emailstop);
380 $this->assertNotEmpty($record->icq);
381 $this->assertNotEmpty($record->phone1);
382 $this->assertNotEmpty($record->institution);
383 $this->assertNotEmpty($record->department);
384 $this->assertNotEmpty($record->city);
385 $this->assertNotEmpty($record->country);
386 $this->assertNotEmpty($record->timezone);
387 $this->assertNotEmpty($record->timecreated);
388 $this->assertNotEmpty($record->timemodified);
389 $this->assertNotEmpty($record->firstnamephonetic);
390 $this->assertEquals(0, $record->deleted);
391 $this->assertEquals($user2->id, $record->id);
392 $this->assertEquals($user2->username, $record->username);
393 $this->assertEquals($user2->password, $record->password);
394 $this->assertEquals($user2->firstname, $record->firstname);
395 $this->assertEquals($user2->lastname, $record->lastname);
396 $this->assertEquals($user2->email, $record->email);
400 * Create user data for a user.
402 * @param stdClass $user A user object.
403 * @param stdClass $course A course.
405 protected function create_data_for_user($user, $course) {
406 global $DB;
407 $this->resetAfterTest();
408 // Last course access.
409 $lastaccess = (object) [
410 'userid' => $user->id,
411 'courseid' => $course->id,
412 'timeaccess' => time() - DAYSECS
414 $DB->insert_record('user_lastaccess', $lastaccess);
416 // Password history.
417 $history = (object) [
418 'userid' => $user->id,
419 'hash' => 'HID098djJUU',
420 'timecreated' => time()
422 $DB->insert_record('user_password_history', $history);
424 // Password resets.
425 $passwordreset = (object) [
426 'userid' => $user->id,
427 'timerequested' => time(),
428 'timererequested' => time(),
429 'token' => $this->generate_random_string()
431 $DB->insert_record('user_password_resets', $passwordreset);
433 // User mobile devices.
434 $userdevices = (object) [
435 'userid' => $user->id,
436 'appid' => 'com.moodle.moodlemobile',
437 'name' => 'occam',
438 'model' => 'Nexus 4',
439 'platform' => 'Android',
440 'version' => '4.2.2',
441 'pushid' => 'kishUhd',
442 'uuid' => 'KIhud7s',
443 'timecreated' => time(),
444 'timemodified' => time()
446 $DB->insert_record('user_devices', $userdevices);
448 // Course request.
449 $courserequest = (object) [
450 'fullname' => 'Test Course',
451 'shortname' => 'TC',
452 'summary' => 'Summary of course',
453 'summaryformat' => 1,
454 'category' => 1,
455 'reason' => 'Because it would be nice.',
456 'requester' => $user->id,
457 'password' => ''
459 $DB->insert_record('course_request', $courserequest);
461 // User session table data.
462 $usersessions = (object) [
463 'state' => 0,
464 'sid' => $this->generate_random_string(), // Needs a unique id.
465 'userid' => $user->id,
466 'sessdata' => 'Nothing',
467 'timecreated' => time(),
468 'timemodified' => time(),
469 'firstip' => '0.0.0.0',
470 'lastip' => '0.0.0.0'
472 $DB->insert_record('sessions', $usersessions);
476 * Create a random string.
478 * @param integer $length length of the string to generate.
479 * @return string A random string.
481 protected function generate_random_string($length = 6) {
482 $response = '';
483 $source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
485 if ($length > 0) {
487 $response = '';
488 $source = str_split($source, 1);
490 for ($i = 1; $i <= $length; $i++) {
491 $num = mt_rand(1, count($source));
492 $response .= $source[$num - 1];
496 return $response;