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