2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
17 * Privacy tests for core_user.
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();
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");
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
{
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());
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));
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));
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));
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));
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() {
135 $this->resetAfterTest();
136 $user = $this->getDataGenerator()->create_user([
137 'idnumber' => 'A0023',
140 'phone1' => '555 3257',
141 'institution' => 'test',
142 'department' => 'Science',
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() {
206 $this->resetAfterTest();
207 $user = $this->getDataGenerator()->create_user([
208 'idnumber' => 'A0023',
211 'phone1' => '555 3257',
212 'institution' => 'test',
213 'department' => 'Science',
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';
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() {
308 $this->resetAfterTest();
310 $component = 'core_user';
313 $user1 = $this->getDataGenerator()->create_user([
314 'idnumber' => 'A0023',
317 'phone1' => '555 3257',
318 'institution' => 'test',
319 'department' => 'Science',
323 $usercontext1 = \context_user
::instance($user1->id
);
324 $userlist1 = new \core_privacy\local\request\
userlist($usercontext1, $component);
327 $user2 = $this->getDataGenerator()->create_user([
328 'idnumber' => 'A0024',
330 'icq' => 'aksdjf981',
331 'phone1' => '555 3258',
332 'institution' => 'test',
333 'department' => 'Science',
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) {
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);
417 $history = (object) [
418 'userid' => $user->id
,
419 'hash' => 'HID098djJUU',
420 'timecreated' => time()
422 $DB->insert_record('user_password_history', $history);
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',
438 'model' => 'Nexus 4',
439 'platform' => 'Android',
440 'version' => '4.2.2',
441 'pushid' => 'kishUhd',
443 'timecreated' => time(),
444 'timemodified' => time()
446 $DB->insert_record('user_devices', $userdevices);
449 $courserequest = (object) [
450 'fullname' => 'Test Course',
452 'summary' => 'Summary of course',
453 'summaryformat' => 1,
455 'reason' => 'Because it would be nice.',
456 'requester' => $user->id
,
459 $DB->insert_record('course_request', $courserequest);
461 // User session table data.
462 $usersessions = (object) [
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) {
483 $source = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
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];