MediaWiki REL1_33
UserTest.php
Go to the documentation of this file.
1<?php
2
3define( 'NS_UNITTEST', 5600 );
4define( 'NS_UNITTEST_TALK', 5601 );
5
11
16
18 const USER_TALK_PAGE = '<user talk page>';
19
23 protected $user;
24
25 protected function setUp() {
26 parent::setUp();
27
28 $this->setMwGlobals( [
29 'wgGroupPermissions' => [],
30 'wgRevokePermissions' => [],
31 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
32 ] );
33 $this->overrideMwServices();
34
36
37 $this->user = $this->getTestUser( [ 'unittesters' ] )->getUser();
38 }
39
40 private function setUpPermissionGlobals() {
42
43 # Data for regular $wgGroupPermissions test
44 $wgGroupPermissions['unittesters'] = [
45 'test' => true,
46 'runtest' => true,
47 'writetest' => false,
48 'nukeworld' => false,
49 ];
50 $wgGroupPermissions['testwriters'] = [
51 'test' => true,
52 'writetest' => true,
53 'modifytest' => true,
54 ];
55
56 # Data for regular $wgRevokePermissions test
57 $wgRevokePermissions['formertesters'] = [
58 'runtest' => true,
59 ];
60
61 # For the options test
62 $wgGroupPermissions['*'] = [
63 'editmyoptions' => true,
64 ];
65 }
66
70 public function testGroupPermissions() {
71 $rights = User::getGroupPermissions( [ 'unittesters' ] );
72 $this->assertContains( 'runtest', $rights );
73 $this->assertNotContains( 'writetest', $rights );
74 $this->assertNotContains( 'modifytest', $rights );
75 $this->assertNotContains( 'nukeworld', $rights );
76
77 $rights = User::getGroupPermissions( [ 'unittesters', 'testwriters' ] );
78 $this->assertContains( 'runtest', $rights );
79 $this->assertContains( 'writetest', $rights );
80 $this->assertContains( 'modifytest', $rights );
81 $this->assertNotContains( 'nukeworld', $rights );
82 }
83
87 public function testRevokePermissions() {
88 $rights = User::getGroupPermissions( [ 'unittesters', 'formertesters' ] );
89 $this->assertNotContains( 'runtest', $rights );
90 $this->assertNotContains( 'writetest', $rights );
91 $this->assertNotContains( 'modifytest', $rights );
92 $this->assertNotContains( 'nukeworld', $rights );
93 }
94
98 public function testUserPermissions() {
99 $rights = $this->user->getRights();
100 $this->assertContains( 'runtest', $rights );
101 $this->assertNotContains( 'writetest', $rights );
102 $this->assertNotContains( 'modifytest', $rights );
103 $this->assertNotContains( 'nukeworld', $rights );
104 }
105
109 public function testUserGetRightsHooks() {
110 $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
111 $userWrapper = TestingAccessWrapper::newFromObject( $user );
112
113 $rights = $user->getRights();
114 $this->assertContains( 'test', $rights, 'sanity check' );
115 $this->assertContains( 'runtest', $rights, 'sanity check' );
116 $this->assertContains( 'writetest', $rights, 'sanity check' );
117 $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
118
119 // Add a hook manipluating the rights
120 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
121 $rights[] = 'nukeworld';
122 $rights = array_diff( $rights, [ 'writetest' ] );
123 } ] ] );
124
125 $userWrapper->mRights = null;
126 $rights = $user->getRights();
127 $this->assertContains( 'test', $rights );
128 $this->assertContains( 'runtest', $rights );
129 $this->assertNotContains( 'writetest', $rights );
130 $this->assertContains( 'nukeworld', $rights );
131
132 // Add a Session that limits rights
133 $mock = $this->getMockBuilder( stdClass::class )
134 ->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
135 ->getMock();
136 $mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
137 $mock->method( 'getSessionId' )->willReturn(
138 new MediaWiki\Session\SessionId( str_repeat( 'X', 32 ) )
139 );
140 $session = MediaWiki\Session\TestUtils::getDummySession( $mock );
141 $mockRequest = $this->getMockBuilder( FauxRequest::class )
142 ->setMethods( [ 'getSession' ] )
143 ->getMock();
144 $mockRequest->method( 'getSession' )->willReturn( $session );
145 $userWrapper->mRequest = $mockRequest;
146
147 $userWrapper->mRights = null;
148 $rights = $user->getRights();
149 $this->assertContains( 'test', $rights );
150 $this->assertNotContains( 'runtest', $rights );
151 $this->assertNotContains( 'writetest', $rights );
152 $this->assertNotContains( 'nukeworld', $rights );
153 }
154
159 public function testGetGroupsWithPermission( $expected, $right ) {
160 $result = User::getGroupsWithPermission( $right );
161 sort( $result );
162 sort( $expected );
163
164 $this->assertEquals( $expected, $result, "Groups with permission $right" );
165 }
166
167 public static function provideGetGroupsWithPermission() {
168 return [
169 [
170 [ 'unittesters', 'testwriters' ],
171 'test'
172 ],
173 [
174 [ 'unittesters' ],
175 'runtest'
176 ],
177 [
178 [ 'testwriters' ],
179 'writetest'
180 ],
181 [
182 [ 'testwriters' ],
183 'modifytest'
184 ],
185 ];
186 }
187
192 public function testIsIP( $value, $result, $message ) {
193 $this->assertEquals( $this->user->isIP( $value ), $result, $message );
194 }
195
196 public static function provideIPs() {
197 return [
198 [ '', false, 'Empty string' ],
199 [ ' ', false, 'Blank space' ],
200 [ '10.0.0.0', true, 'IPv4 private 10/8' ],
201 [ '10.255.255.255', true, 'IPv4 private 10/8' ],
202 [ '192.168.1.1', true, 'IPv4 private 192.168/16' ],
203 [ '203.0.113.0', true, 'IPv4 example' ],
204 [ '2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff', true, 'IPv6 example' ],
205 // Not valid IPs but classified as such by MediaWiki for negated asserting
206 // of whether this might be the identifier of a logged-out user or whether
207 // to allow usernames like it.
208 [ '300.300.300.300', true, 'Looks too much like an IPv4 address' ],
209 [ '203.0.113.xxx', true, 'Assigned by UseMod to cloaked logged-out users' ],
210 ];
211 }
212
217 public function testIsValidUserName( $username, $result, $message ) {
218 $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
219 }
220
221 public static function provideUserNames() {
222 return [
223 [ '', false, 'Empty string' ],
224 [ ' ', false, 'Blank space' ],
225 [ 'abcd', false, 'Starts with small letter' ],
226 [ 'Ab/cd', false, 'Contains slash' ],
227 [ 'Ab cd', true, 'Whitespace' ],
228 [ '192.168.1.1', false, 'IP' ],
229 [ '116.17.184.5/32', false, 'IP range' ],
230 [ '::e:f:2001/96', false, 'IPv6 range' ],
231 [ 'User:Abcd', false, 'Reserved Namespace' ],
232 [ '12abcd232', true, 'Starts with Numbers' ],
233 [ '?abcd', true, 'Start with ? mark' ],
234 [ '#abcd', false, 'Start with #' ],
235 [ 'Abcdകഖഗഘ', true, ' Mixed scripts' ],
236 [ 'ജോസ്‌തോമസ്', false, 'ZWNJ- Format control character' ],
237 [ 'Ab cd', false, ' Ideographic space' ],
238 [ '300.300.300.300', false, 'Looks too much like an IPv4 address' ],
239 [ '302.113.311.900', false, 'Looks too much like an IPv4 address' ],
240 [ '203.0.113.xxx', false, 'Reserved for usage by UseMod for cloaked logged-out users' ],
241 ];
242 }
243
249 public function testGetEditCount() {
250 $user = $this->getMutableTestUser()->getUser();
251
252 // let the user have a few (3) edits
253 $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) );
254 for ( $i = 0; $i < 3; $i++ ) {
255 $page->doEditContent(
256 ContentHandler::makeContent( (string)$i, $page->getTitle() ),
257 'test',
258 0,
259 false,
260 $user
261 );
262 }
263
264 $this->assertEquals(
265 3,
266 $user->getEditCount(),
267 'After three edits, the user edit count should be 3'
268 );
269
270 // increase the edit count
271 $user->incEditCount();
272 $user->clearInstanceCache();
273
274 $this->assertEquals(
275 4,
276 $user->getEditCount(),
277 'After increasing the edit count manually, the user edit count should be 4'
278 );
279 }
280
286 public function testGetEditCountForAnons() {
287 $user = User::newFromName( 'Anonymous' );
288
289 $this->assertNull(
290 $user->getEditCount(),
291 'Edit count starts null for anonymous users.'
292 );
293
294 $user->incEditCount();
295
296 $this->assertNull(
297 $user->getEditCount(),
298 'Edit count remains null for anonymous users despite calls to increase it.'
299 );
300 }
301
307 public function testIncEditCount() {
308 $user = $this->getMutableTestUser()->getUser();
309 $user->incEditCount();
310
311 $reloadedUser = User::newFromId( $user->getId() );
312 $reloadedUser->incEditCount();
313
314 $this->assertEquals(
315 2,
316 $reloadedUser->getEditCount(),
317 'Increasing the edit count after a fresh load leaves the object up to date.'
318 );
319 }
320
326 public function testOptions() {
327 $user = $this->getMutableTestUser()->getUser();
328
329 $user->setOption( 'userjs-someoption', 'test' );
330 $user->setOption( 'rclimit', 200 );
331 $user->setOption( 'wpwatchlistdays', '0' );
332 $user->saveSettings();
333
334 $user = User::newFromName( $user->getName() );
335 $user->load( User::READ_LATEST );
336 $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
337 $this->assertEquals( 200, $user->getOption( 'rclimit' ) );
338
339 $user = User::newFromName( $user->getName() );
340 MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
341 $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
342 $this->assertEquals( 200, $user->getOption( 'rclimit' ) );
343
344 // Check that an option saved as a string '0' is returned as an integer.
345 $user = User::newFromName( $user->getName() );
346 $user->load( User::READ_LATEST );
347 $this->assertSame( 0, $user->getOption( 'wpwatchlistdays' ) );
348 }
349
355 public function testAnonOptions() {
357 $this->user->setOption( 'userjs-someoption', 'test' );
358 $this->assertEquals( $wgDefaultUserOptions['rclimit'], $this->user->getOption( 'rclimit' ) );
359 $this->assertEquals( 'test', $this->user->getOption( 'userjs-someoption' ) );
360 }
361
371 public function testCheckPasswordValidity() {
372 $this->setMwGlobals( [
373 'wgPasswordPolicy' => [
374 'policies' => [
375 'sysop' => [
376 'MinimalPasswordLength' => 8,
377 'MinimumPasswordLengthToLogin' => 1,
378 'PasswordCannotMatchUsername' => 1,
379 ],
380 'default' => [
381 'MinimalPasswordLength' => 6,
382 'PasswordCannotMatchUsername' => true,
383 'PasswordCannotMatchBlacklist' => true,
384 'MaximalPasswordLength' => 40,
385 ],
386 ],
387 'checks' => [
388 'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
389 'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
390 'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
391 'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
392 'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
393 ],
394 ],
395 ] );
396 $this->hideDeprecated( 'User::getPasswordValidity' );
397
398 $user = static::getTestUser()->getUser();
399
400 // Sanity
401 $this->assertTrue( $user->isValidPassword( 'Password1234' ) );
402
403 // Minimum length
404 $this->assertFalse( $user->isValidPassword( 'a' ) );
405 $this->assertFalse( $user->checkPasswordValidity( 'a' )->isGood() );
406 $this->assertTrue( $user->checkPasswordValidity( 'a' )->isOK() );
407 $this->assertEquals( 'passwordtooshort', $user->getPasswordValidity( 'a' ) );
408
409 // Maximum length
410 $longPass = str_repeat( 'a', 41 );
411 $this->assertFalse( $user->isValidPassword( $longPass ) );
412 $this->assertFalse( $user->checkPasswordValidity( $longPass )->isGood() );
413 $this->assertFalse( $user->checkPasswordValidity( $longPass )->isOK() );
414 $this->assertEquals( 'passwordtoolong', $user->getPasswordValidity( $longPass ) );
415
416 // Matches username
417 $this->assertFalse( $user->checkPasswordValidity( $user->getName() )->isGood() );
418 $this->assertTrue( $user->checkPasswordValidity( $user->getName() )->isOK() );
419 $this->assertEquals( 'password-name-match', $user->getPasswordValidity( $user->getName() ) );
420
421 // On the forbidden list
422 $user = User::newFromName( 'Useruser' );
423 $this->assertFalse( $user->checkPasswordValidity( 'Passpass' )->isGood() );
424 $this->assertEquals( 'password-login-forbidden', $user->getPasswordValidity( 'Passpass' ) );
425 }
426
431 public function testGetCanonicalName( $name, $expectedArray ) {
432 // fake interwiki map for the 'Interwiki prefix' testcase
433 $this->mergeMwGlobalArrayValue( 'wgHooks', [
434 'InterwikiLoadPrefix' => [
435 function ( $prefix, &$iwdata ) {
436 if ( $prefix === 'interwiki' ) {
437 $iwdata = [
438 'iw_url' => 'http://example.com/',
439 'iw_local' => 0,
440 'iw_trans' => 0,
441 ];
442 return false;
443 }
444 },
445 ],
446 ] );
447
448 foreach ( $expectedArray as $validate => $expected ) {
449 $this->assertEquals(
450 $expected,
451 User::getCanonicalName( $name, $validate === 'false' ? false : $validate ), $validate );
452 }
453 }
454
455 public static function provideGetCanonicalName() {
456 return [
457 'Leading space' => [ ' Leading space', [ 'creatable' => 'Leading space' ] ],
458 'Trailing space ' => [ 'Trailing space ', [ 'creatable' => 'Trailing space' ] ],
459 'Namespace prefix' => [ 'Talk:Username', [ 'creatable' => false, 'usable' => false,
460 'valid' => false, 'false' => 'Talk:Username' ] ],
461 'Interwiki prefix' => [ 'interwiki:Username', [ 'creatable' => false, 'usable' => false,
462 'valid' => false, 'false' => 'Interwiki:Username' ] ],
463 'With hash' => [ 'name with # hash', [ 'creatable' => false, 'usable' => false ] ],
464 'Multi spaces' => [ 'Multi spaces', [ 'creatable' => 'Multi spaces',
465 'usable' => 'Multi spaces' ] ],
466 'Lowercase' => [ 'lowercase', [ 'creatable' => 'Lowercase' ] ],
467 'Invalid character' => [ 'in[]valid', [ 'creatable' => false, 'usable' => false,
468 'valid' => false, 'false' => 'In[]valid' ] ],
469 'With slash' => [ 'with / slash', [ 'creatable' => false, 'usable' => false, 'valid' => false,
470 'false' => 'With / slash' ] ],
471 ];
472 }
473
477 public function testEquals() {
478 $first = $this->getMutableTestUser()->getUser();
479 $second = User::newFromName( $first->getName() );
480
481 $this->assertTrue( $first->equals( $first ) );
482 $this->assertTrue( $first->equals( $second ) );
483 $this->assertTrue( $second->equals( $first ) );
484
485 $third = $this->getMutableTestUser()->getUser();
486 $fourth = $this->getMutableTestUser()->getUser();
487
488 $this->assertFalse( $third->equals( $fourth ) );
489 $this->assertFalse( $fourth->equals( $third ) );
490
491 // Test users loaded from db with id
492 $user = $this->getMutableTestUser()->getUser();
493 $fifth = User::newFromId( $user->getId() );
494 $sixth = User::newFromName( $user->getName() );
495 $this->assertTrue( $fifth->equals( $sixth ) );
496 }
497
501 public function testGetId() {
502 $user = static::getTestUser()->getUser();
503 $this->assertTrue( $user->getId() > 0 );
504 }
505
510 public function testLoggedIn() {
511 $user = $this->getMutableTestUser()->getUser();
512 $this->assertTrue( $user->isLoggedIn() );
513 $this->assertFalse( $user->isAnon() );
514
515 // Non-existent users are perceived as anonymous
516 $user = User::newFromName( 'UTNonexistent' );
517 $this->assertFalse( $user->isLoggedIn() );
518 $this->assertTrue( $user->isAnon() );
519
520 $user = new User;
521 $this->assertFalse( $user->isLoggedIn() );
522 $this->assertTrue( $user->isAnon() );
523 }
524
528 public function testCheckAndSetTouched() {
529 $user = $this->getMutableTestUser()->getUser();
530 $user = TestingAccessWrapper::newFromObject( $user );
531 $this->assertTrue( $user->isLoggedIn() );
532
533 $touched = $user->getDBTouched();
534 $this->assertTrue(
535 $user->checkAndSetTouched(), "checkAndSetTouched() succedeed" );
536 $this->assertGreaterThan(
537 $touched, $user->getDBTouched(), "user_touched increased with casOnTouched()" );
538
539 $touched = $user->getDBTouched();
540 $this->assertTrue(
541 $user->checkAndSetTouched(), "checkAndSetTouched() succedeed #2" );
542 $this->assertGreaterThan(
543 $touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" );
544 }
545
549 public function testFindUsersByGroup() {
550 // FIXME: fails under postgres
551 $this->markTestSkippedIfDbType( 'postgres' );
552
554 $this->assertEquals( 0, iterator_count( $users ) );
555
557 $this->assertEquals( 0, iterator_count( $users ) );
558
559 $user = $this->getMutableTestUser( [ 'foo' ] )->getUser();
561 $this->assertEquals( 1, iterator_count( $users ) );
562 $users->rewind();
563 $this->assertTrue( $user->equals( $users->current() ) );
564
565 // arguments have OR relationship
566 $user2 = $this->getMutableTestUser( [ 'bar' ] )->getUser();
567 $users = User::findUsersByGroup( [ 'foo', 'bar' ] );
568 $this->assertEquals( 2, iterator_count( $users ) );
569 $users->rewind();
570 $this->assertTrue( $user->equals( $users->current() ) );
571 $users->next();
572 $this->assertTrue( $user2->equals( $users->current() ) );
573
574 // users are not duplicated
575 $user = $this->getMutableTestUser( [ 'baz', 'boom' ] )->getUser();
576 $users = User::findUsersByGroup( [ 'baz', 'boom' ] );
577 $this->assertEquals( 1, iterator_count( $users ) );
578 $users->rewind();
579 $this->assertTrue( $user->equals( $users->current() ) );
580 }
581
588 public function testAutoblockCookies() {
589 // Set up the bits of global configuration that we use.
590 $this->setMwGlobals( [
591 'wgCookieSetOnAutoblock' => true,
592 'wgCookiePrefix' => 'wmsitetitle',
593 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
594 ] );
595
596 // Unregister the hooks for proper unit testing
597 $this->mergeMwGlobalArrayValue( 'wgHooks', [
598 'PerformRetroactiveAutoblock' => []
599 ] );
600
601 // 1. Log in a test user, and block them.
602 $user1tmp = $this->getTestUser()->getUser();
603 $request1 = new FauxRequest();
604 $request1->getSession()->setUser( $user1tmp );
605 $expiryFiveHours = wfTimestamp() + ( 5 * 60 * 60 );
606 $block = new Block( [
607 'enableAutoblock' => true,
608 'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
609 ] );
610 $block->setBlocker( $this->getTestSysop()->getUser() );
611 $block->setTarget( $user1tmp );
612 $res = $block->insert();
613 $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
614 $user1 = User::newFromSession( $request1 );
615 $user1->mBlock = $block;
616 $user1->load();
617
618 // Confirm that the block has been applied as required.
619 $this->assertTrue( $user1->isLoggedIn() );
620 $this->assertTrue( $user1->isBlocked() );
621 $this->assertEquals( Block::TYPE_USER, $block->getType() );
622 $this->assertTrue( $block->isAutoblocking() );
623 $this->assertGreaterThanOrEqual( 1, $block->getId() );
624
625 // Test for the desired cookie name, value, and expiry.
626 $cookies = $request1->response()->getCookies();
627 $this->assertArrayHasKey( 'wmsitetitleBlockID', $cookies );
628 $this->assertEquals( $expiryFiveHours, $cookies['wmsitetitleBlockID']['expire'] );
629 $cookieValue = Block::getIdFromCookieValue( $cookies['wmsitetitleBlockID']['value'] );
630 $this->assertEquals( $block->getId(), $cookieValue );
631
632 // 2. Create a new request, set the cookies, and see if the (anon) user is blocked.
633 $request2 = new FauxRequest();
634 $request2->setCookie( 'BlockID', $block->getCookieValue() );
635 $user2 = User::newFromSession( $request2 );
636 $user2->load();
637 $this->assertNotEquals( $user1->getId(), $user2->getId() );
638 $this->assertNotEquals( $user1->getToken(), $user2->getToken() );
639 $this->assertTrue( $user2->isAnon() );
640 $this->assertFalse( $user2->isLoggedIn() );
641 $this->assertTrue( $user2->isBlocked() );
642 // Non-strict type-check.
643 $this->assertEquals( true, $user2->getBlock()->isAutoblocking(), 'Autoblock does not work' );
644 // Can't directly compare the objects because of member type differences.
645 // One day this will work: $this->assertEquals( $block, $user2->getBlock() );
646 $this->assertEquals( $block->getId(), $user2->getBlock()->getId() );
647 $this->assertEquals( $block->getExpiry(), $user2->getBlock()->getExpiry() );
648
649 // 3. Finally, set up a request as a new user, and the block should still be applied.
650 $user3tmp = $this->getTestUser()->getUser();
651 $request3 = new FauxRequest();
652 $request3->getSession()->setUser( $user3tmp );
653 $request3->setCookie( 'BlockID', $block->getId() );
654 $user3 = User::newFromSession( $request3 );
655 $user3->load();
656 $this->assertTrue( $user3->isLoggedIn() );
657 $this->assertTrue( $user3->isBlocked() );
658 $this->assertEquals( true, $user3->getBlock()->isAutoblocking() ); // Non-strict type-check.
659
660 // Clean up.
661 $block->delete();
662 }
663
670 // Set up the bits of global configuration that we use.
671 $this->setMwGlobals( [
672 'wgCookieSetOnAutoblock' => false,
673 'wgCookiePrefix' => 'wm_no_cookies',
674 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
675 ] );
676
677 // Unregister the hooks for proper unit testing
678 $this->mergeMwGlobalArrayValue( 'wgHooks', [
679 'PerformRetroactiveAutoblock' => []
680 ] );
681
682 // 1. Log in a test user, and block them.
683 $testUser = $this->getTestUser()->getUser();
684 $request1 = new FauxRequest();
685 $request1->getSession()->setUser( $testUser );
686 $block = new Block( [ 'enableAutoblock' => true ] );
687 $block->setBlocker( $this->getTestSysop()->getUser() );
688 $block->setTarget( $testUser );
689 $res = $block->insert();
690 $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
691 $user = User::newFromSession( $request1 );
692 $user->mBlock = $block;
693 $user->load();
694
695 // 2. Test that the cookie IS NOT present.
696 $this->assertTrue( $user->isLoggedIn() );
697 $this->assertTrue( $user->isBlocked() );
698 $this->assertEquals( Block::TYPE_USER, $block->getType() );
699 $this->assertTrue( $block->isAutoblocking() );
700 $this->assertGreaterThanOrEqual( 1, $user->getBlockId() );
701 $this->assertGreaterThanOrEqual( $block->getId(), $user->getBlockId() );
702 $cookies = $request1->response()->getCookies();
703 $this->assertArrayNotHasKey( 'wm_no_cookiesBlockID', $cookies );
704
705 // Clean up.
706 $block->delete();
707 }
708
716 $this->setMwGlobals( [
717 'wgCookieSetOnAutoblock' => true,
718 'wgCookiePrefix' => 'wm_infinite_block',
719 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
720 ] );
721
722 // Unregister the hooks for proper unit testing
723 $this->mergeMwGlobalArrayValue( 'wgHooks', [
724 'PerformRetroactiveAutoblock' => []
725 ] );
726
727 // 1. Log in a test user, and block them indefinitely.
728 $user1Tmp = $this->getTestUser()->getUser();
729 $request1 = new FauxRequest();
730 $request1->getSession()->setUser( $user1Tmp );
731 $block = new Block( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
732 $block->setBlocker( $this->getTestSysop()->getUser() );
733 $block->setTarget( $user1Tmp );
734 $res = $block->insert();
735 $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
736 $user1 = User::newFromSession( $request1 );
737 $user1->mBlock = $block;
738 $user1->load();
739
740 // 2. Test the cookie's expiry timestamp.
741 $this->assertTrue( $user1->isLoggedIn() );
742 $this->assertTrue( $user1->isBlocked() );
743 $this->assertEquals( Block::TYPE_USER, $block->getType() );
744 $this->assertTrue( $block->isAutoblocking() );
745 $this->assertGreaterThanOrEqual( 1, $user1->getBlockId() );
746 $cookies = $request1->response()->getCookies();
747 // Test the cookie's expiry to the nearest minute.
748 $this->assertArrayHasKey( 'wm_infinite_blockBlockID', $cookies );
749 $expOneDay = wfTimestamp() + ( 24 * 60 * 60 );
750 // Check for expiry dates in a 10-second window, to account for slow testing.
751 $this->assertEquals(
752 $expOneDay,
753 $cookies['wm_infinite_blockBlockID']['expire'],
754 'Expiry date',
755 5.0
756 );
757
758 // 3. Change the block's expiry (to 2 hours), and the cookie's should be changed also.
759 $newExpiry = wfTimestamp() + 2 * 60 * 60;
760 $block->setExpiry( wfTimestamp( TS_MW, $newExpiry ) );
761 $block->update();
762 $user2tmp = $this->getTestUser()->getUser();
763 $request2 = new FauxRequest();
764 $request2->getSession()->setUser( $user2tmp );
765 $user2 = User::newFromSession( $request2 );
766 $user2->mBlock = $block;
767 $user2->load();
768 $cookies = $request2->response()->getCookies();
769 $this->assertEquals( wfTimestamp( TS_MW, $newExpiry ), $block->getExpiry() );
770 $this->assertEquals( $newExpiry, $cookies['wm_infinite_blockBlockID']['expire'] );
771
772 // Clean up.
773 $block->delete();
774 }
775
779 public function testSoftBlockRanges() {
780 $setSessionUser = function ( User $user, WebRequest $request ) {
781 $this->setMwGlobals( 'wgUser', $user );
782 RequestContext::getMain()->setUser( $user );
783 RequestContext::getMain()->setRequest( $request );
784 TestingAccessWrapper::newFromObject( $user )->mRequest = $request;
785 $request->getSession()->setUser( $user );
786 };
787 $this->setMwGlobals( 'wgSoftBlockRanges', [ '10.0.0.0/8' ] );
788
789 // IP isn't in $wgSoftBlockRanges
790 $wgUser = new User();
791 $request = new FauxRequest();
792 $request->setIP( '192.168.0.1' );
793 $setSessionUser( $wgUser, $request );
794 $this->assertNull( $wgUser->getBlock() );
795
796 // IP is in $wgSoftBlockRanges
797 $wgUser = new User();
798 $request = new FauxRequest();
799 $request->setIP( '10.20.30.40' );
800 $setSessionUser( $wgUser, $request );
801 $block = $wgUser->getBlock();
802 $this->assertInstanceOf( Block::class, $block );
803 $this->assertSame( 'wgSoftBlockRanges', $block->getSystemBlockType() );
804
805 // Make sure the block is really soft
806 $wgUser = $this->getTestUser()->getUser();
807 $request = new FauxRequest();
808 $request->setIP( '10.20.30.40' );
809 $setSessionUser( $wgUser, $request );
810 $this->assertFalse( $wgUser->isAnon(), 'sanity check' );
811 $this->assertNull( $wgUser->getBlock() );
812 }
813
819 // Set up the bits of global configuration that we use.
820 $this->setMwGlobals( [
821 'wgCookieSetOnAutoblock' => true,
822 'wgCookiePrefix' => 'wmsitetitle',
823 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
824 ] );
825
826 // Unregister the hooks for proper unit testing
827 $this->mergeMwGlobalArrayValue( 'wgHooks', [
828 'PerformRetroactiveAutoblock' => []
829 ] );
830
831 // 1. Log in a blocked test user.
832 $user1tmp = $this->getTestUser()->getUser();
833 $request1 = new FauxRequest();
834 $request1->getSession()->setUser( $user1tmp );
835 $block = new Block( [ 'enableAutoblock' => true ] );
836 $block->setBlocker( $this->getTestSysop()->getUser() );
837 $block->setTarget( $user1tmp );
838 $res = $block->insert();
839 $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
840 $user1 = User::newFromSession( $request1 );
841 $user1->mBlock = $block;
842 $user1->load();
843
844 // 2. Create a new request, set the cookie to an invalid value, and make sure the (anon)
845 // user not blocked.
846 $request2 = new FauxRequest();
847 $request2->setCookie( 'BlockID', $block->getId() . '!zzzzzzz' );
848 $user2 = User::newFromSession( $request2 );
849 $user2->load();
850 $this->assertTrue( $user2->isAnon() );
851 $this->assertFalse( $user2->isLoggedIn() );
852 $this->assertFalse( $user2->isBlocked() );
853
854 // Clean up.
855 $block->delete();
856 }
857
864 // Set up the bits of global configuration that we use.
865 $this->setMwGlobals( [
866 'wgCookieSetOnAutoblock' => true,
867 'wgCookiePrefix' => 'wmsitetitle',
868 'wgSecretKey' => null,
869 ] );
870
871 // Unregister the hooks for proper unit testing
872 $this->mergeMwGlobalArrayValue( 'wgHooks', [
873 'PerformRetroactiveAutoblock' => []
874 ] );
875
876 // 1. Log in a blocked test user.
877 $user1tmp = $this->getTestUser()->getUser();
878 $request1 = new FauxRequest();
879 $request1->getSession()->setUser( $user1tmp );
880 $block = new Block( [ 'enableAutoblock' => true ] );
881 $block->setBlocker( $this->getTestSysop()->getUser() );
882 $block->setTarget( $user1tmp );
883 $res = $block->insert();
884 $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
885 $user1 = User::newFromSession( $request1 );
886 $user1->mBlock = $block;
887 $user1->load();
888 $this->assertTrue( $user1->isBlocked() );
889
890 // 2. Create a new request, set the cookie to just the block ID, and the user should
891 // still get blocked when they log in again.
892 $request2 = new FauxRequest();
893 $request2->setCookie( 'BlockID', $block->getId() );
894 $user2 = User::newFromSession( $request2 );
895 $user2->load();
896 $this->assertNotEquals( $user1->getId(), $user2->getId() );
897 $this->assertNotEquals( $user1->getToken(), $user2->getToken() );
898 $this->assertTrue( $user2->isAnon() );
899 $this->assertFalse( $user2->isLoggedIn() );
900 $this->assertTrue( $user2->isBlocked() );
901 $this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
902
903 // Clean up.
904 $block->delete();
905 }
906
910 public function testIsPingLimitable() {
911 $request = new FauxRequest();
912 $request->setIP( '1.2.3.4' );
914
915 $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
916 $this->assertTrue( $user->isPingLimitable() );
917
918 $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.4' ] );
919 $this->assertFalse( $user->isPingLimitable() );
920
921 $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.0/8' ] );
922 $this->assertFalse( $user->isPingLimitable() );
923
924 $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
925 $noRateLimitUser = $this->getMockBuilder( User::class )->disableOriginalConstructor()
926 ->setMethods( [ 'getIP', 'getRights' ] )->getMock();
927 $noRateLimitUser->expects( $this->any() )->method( 'getIP' )->willReturn( '1.2.3.4' );
928 $noRateLimitUser->expects( $this->any() )->method( 'getRights' )->willReturn( [ 'noratelimit' ] );
929 $this->assertFalse( $noRateLimitUser->isPingLimitable() );
930 }
931
932 public function provideExperienceLevel() {
933 return [
934 [ 2, 2, 'newcomer' ],
935 [ 12, 3, 'newcomer' ],
936 [ 8, 5, 'newcomer' ],
937 [ 15, 10, 'learner' ],
938 [ 450, 20, 'learner' ],
939 [ 460, 33, 'learner' ],
940 [ 525, 28, 'learner' ],
941 [ 538, 33, 'experienced' ],
942 ];
943 }
944
949 public function testExperienceLevel( $editCount, $memberSince, $expLevel ) {
950 $this->setMwGlobals( [
951 'wgLearnerEdits' => 10,
952 'wgLearnerMemberSince' => 4,
953 'wgExperiencedUserEdits' => 500,
954 'wgExperiencedUserMemberSince' => 30,
955 ] );
956
957 $db = wfGetDB( DB_MASTER );
958 $userQuery = User::getQueryInfo();
959 $row = $db->selectRow(
960 $userQuery['tables'],
961 $userQuery['fields'],
962 [ 'user_id' => $this->getTestUser()->getUser()->getId() ],
963 __METHOD__,
964 [],
965 $userQuery['joins']
966 );
967 $row->user_editcount = $editCount;
968 $row->user_registration = $db->timestamp( time() - $memberSince * 86400 );
969 $user = User::newFromRow( $row );
970
971 $this->assertEquals( $expLevel, $user->getExperienceLevel() );
972 }
973
977 public function testExperienceLevelAnon() {
978 $user = User::newFromName( '10.11.12.13', false );
979
980 $this->assertFalse( $user->getExperienceLevel() );
981 }
982
983 public static function provideIsLocallBlockedProxy() {
984 return [
985 [ '1.2.3.4', '1.2.3.4' ],
986 [ '1.2.3.4', '1.2.3.0/16' ],
987 ];
988 }
989
994 public function testIsLocallyBlockedProxy( $ip, $blockListEntry ) {
995 $this->setMwGlobals(
996 'wgProxyList', []
997 );
999
1000 $this->setMwGlobals(
1001 'wgProxyList',
1002 [
1003 $blockListEntry
1004 ]
1005 );
1006 $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
1007
1008 $this->setMwGlobals(
1009 'wgProxyList',
1010 [
1011 'test' => $blockListEntry
1012 ]
1013 );
1014 $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
1015
1016 $this->hideDeprecated(
1017 'IP addresses in the keys of $wgProxyList (found the following IP ' .
1018 'addresses in keys: ' . $blockListEntry . ', please move them to values)'
1019 );
1020 $this->setMwGlobals(
1021 'wgProxyList',
1022 [
1023 $blockListEntry => 'test'
1024 ]
1025 );
1026 $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
1027 }
1028
1032 public function testActorId() {
1033 $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
1034 $this->hideDeprecated( 'User::selectFields' );
1035
1036 // Newly-created user has an actor ID
1037 $user = User::createNew( 'UserTestActorId1' );
1038 $id = $user->getId();
1039 $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
1040
1041 $user = User::newFromName( 'UserTestActorId2' );
1042 $user->addToDatabase();
1043 $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
1044
1045 $user = User::newFromName( 'UserTestActorId1' );
1046 $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
1047
1048 $user = User::newFromId( $id );
1049 $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
1050
1051 $user2 = User::newFromActorId( $user->getActorId() );
1052 $this->assertEquals( $user->getId(), $user2->getId(),
1053 'User::newFromActorId works for an existing user' );
1054
1055 $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
1056 $user = User::newFromRow( $row );
1057 $this->assertTrue( $user->getActorId() > 0,
1058 'Actor ID can be retrieved for user loaded with User::selectFields()' );
1059
1060 $user = User::newFromId( $id );
1061 $user->setName( 'UserTestActorId4-renamed' );
1062 $user->saveSettings();
1063 $this->assertEquals(
1064 $user->getName(),
1065 $this->db->selectField(
1066 'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
1067 ),
1068 'User::saveSettings updates actor table for name change'
1069 );
1070
1071 // For sanity
1072 $ip = '192.168.12.34';
1073 $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
1074
1075 $user = User::newFromName( $ip, false );
1076 $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
1077 $this->assertTrue( $user->getActorId( $this->db ) > 0,
1078 'Actor ID can be created for an anonymous user' );
1079
1080 $user = User::newFromName( $ip, false );
1081 $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
1082 $user2 = User::newFromActorId( $user->getActorId() );
1083 $this->assertEquals( $user->getName(), $user2->getName(),
1084 'User::newFromActorId works for an anonymous user' );
1085 }
1086
1096 public function testActorId_old() {
1097 $this->setMwGlobals( [
1098 'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
1099 ] );
1100 $this->overrideMwServices();
1101
1102 $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
1103 $this->hideDeprecated( 'User::selectFields' );
1104
1105 // Newly-created user has an actor ID
1106 $user = User::createNew( 'UserTestActorIdOld1' );
1107 $id = $user->getId();
1108 $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
1109
1110 $user = User::newFromName( 'UserTestActorIdOld2' );
1111 $user->addToDatabase();
1112 $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
1113
1114 $user = User::newFromName( 'UserTestActorIdOld1' );
1115 $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
1116
1117 $user = User::newFromId( $id );
1118 $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
1119
1120 $user2 = User::newFromActorId( $user->getActorId() );
1121 $this->assertEquals( $user->getId(), $user2->getId(),
1122 'User::newFromActorId works for an existing user' );
1123
1124 $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
1125 $user = User::newFromRow( $row );
1126 $this->assertTrue( $user->getActorId() > 0,
1127 'Actor ID can be retrieved for user loaded with User::selectFields()' );
1128
1129 $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
1130 User::purge( $domain, $id );
1131 // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
1132 ObjectCache::getMainWANInstance()->clearProcessCache();
1133
1134 $user = User::newFromId( $id );
1135 $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );
1136 $this->assertTrue( $user->getActorId( $this->db ) > 0, 'Actor ID can be created if none in db' );
1137
1138 $user->setName( 'UserTestActorIdOld4-renamed' );
1139 $user->saveSettings();
1140 $this->assertEquals(
1141 $user->getName(),
1142 $this->db->selectField(
1143 'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
1144 ),
1145 'User::saveSettings updates actor table for name change'
1146 );
1147
1148 // For sanity
1149 $ip = '192.168.12.34';
1150 $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
1151
1152 $user = User::newFromName( $ip, false );
1153 $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
1154 $this->assertTrue( $user->getActorId( $this->db ) > 0,
1155 'Actor ID can be created for an anonymous user' );
1156
1157 $user = User::newFromName( $ip, false );
1158 $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
1159 $user2 = User::newFromActorId( $user->getActorId() );
1160 $this->assertEquals( $user->getName(), $user2->getName(),
1161 'User::newFromActorId works for an anonymous user' );
1162 }
1163
1167 public function testNewFromAnyId() {
1168 // Registered user
1169 $user = $this->getTestUser()->getUser();
1170 for ( $i = 1; $i <= 7; $i++ ) {
1171 $test = User::newFromAnyId(
1172 ( $i & 1 ) ? $user->getId() : null,
1173 ( $i & 2 ) ? $user->getName() : null,
1174 ( $i & 4 ) ? $user->getActorId() : null
1175 );
1176 $this->assertSame( $user->getId(), $test->getId() );
1177 $this->assertSame( $user->getName(), $test->getName() );
1178 $this->assertSame( $user->getActorId(), $test->getActorId() );
1179 }
1180
1181 // Anon user. Can't load by only user ID when that's 0.
1182 $user = User::newFromName( '192.168.12.34', false );
1183 $user->getActorId( $this->db ); // Make sure an actor ID exists
1184
1185 $test = User::newFromAnyId( null, '192.168.12.34', null );
1186 $this->assertSame( $user->getId(), $test->getId() );
1187 $this->assertSame( $user->getName(), $test->getName() );
1188 $this->assertSame( $user->getActorId(), $test->getActorId() );
1189 $test = User::newFromAnyId( null, null, $user->getActorId() );
1190 $this->assertSame( $user->getId(), $test->getId() );
1191 $this->assertSame( $user->getName(), $test->getName() );
1192 $this->assertSame( $user->getActorId(), $test->getActorId() );
1193
1194 // Bogus data should still "work" as long as nothing triggers a ->load(),
1195 // and accessing the specified data shouldn't do that.
1196 $test = User::newFromAnyId( 123456, 'Bogus', 654321 );
1197 $this->assertSame( 123456, $test->getId() );
1198 $this->assertSame( 'Bogus', $test->getName() );
1199 $this->assertSame( 654321, $test->getActorId() );
1200
1201 // Exceptional cases
1202 try {
1203 User::newFromAnyId( null, null, null );
1204 $this->fail( 'Expected exception not thrown' );
1205 } catch ( InvalidArgumentException $ex ) {
1206 }
1207 try {
1208 User::newFromAnyId( 0, null, 0 );
1209 $this->fail( 'Expected exception not thrown' );
1210 } catch ( InvalidArgumentException $ex ) {
1211 }
1212 }
1213
1217 public function testNewFromIdentity() {
1218 // Registered user
1219 $user = $this->getTestUser()->getUser();
1220
1221 $this->assertSame( $user, User::newFromIdentity( $user ) );
1222
1223 // ID only
1224 $identity = new UserIdentityValue( $user->getId(), '', 0 );
1225 $result = User::newFromIdentity( $identity );
1226 $this->assertInstanceOf( User::class, $result );
1227 $this->assertSame( $user->getId(), $result->getId(), 'ID' );
1228 $this->assertSame( $user->getName(), $result->getName(), 'Name' );
1229 $this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
1230
1231 // Name only
1232 $identity = new UserIdentityValue( 0, $user->getName(), 0 );
1233 $result = User::newFromIdentity( $identity );
1234 $this->assertInstanceOf( User::class, $result );
1235 $this->assertSame( $user->getId(), $result->getId(), 'ID' );
1236 $this->assertSame( $user->getName(), $result->getName(), 'Name' );
1237 $this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
1238
1239 // Actor only
1240 $identity = new UserIdentityValue( 0, '', $user->getActorId() );
1241 $result = User::newFromIdentity( $identity );
1242 $this->assertInstanceOf( User::class, $result );
1243 $this->assertSame( $user->getId(), $result->getId(), 'ID' );
1244 $this->assertSame( $user->getName(), $result->getName(), 'Name' );
1245 $this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
1246 }
1247
1256 public function testBlockInstanceCache() {
1257 // First, check the user isn't blocked
1258 $user = $this->getMutableTestUser()->getUser();
1259 $ut = Title::makeTitle( NS_USER_TALK, $user->getName() );
1260 $this->assertNull( $user->getBlock( false ), 'sanity check' );
1261 $this->assertSame( '', $user->blockedBy(), 'sanity check' );
1262 $this->assertSame( '', $user->blockedFor(), 'sanity check' );
1263 $this->assertFalse( (bool)$user->isHidden(), 'sanity check' );
1264 $this->assertFalse( $user->isBlockedFrom( $ut ), 'sanity check' );
1265
1266 // Block the user
1267 $blocker = $this->getTestSysop()->getUser();
1268 $block = new Block( [
1269 'hideName' => true,
1270 'allowUsertalk' => false,
1271 'reason' => 'Because',
1272 ] );
1273 $block->setTarget( $user );
1274 $block->setBlocker( $blocker );
1275 $res = $block->insert();
1276 $this->assertTrue( (bool)$res['id'], 'sanity check: Failed to insert block' );
1277
1278 // Clear cache and confirm it loaded the block properly
1279 $user->clearInstanceCache();
1280 $this->assertInstanceOf( Block::class, $user->getBlock( false ) );
1281 $this->assertSame( $blocker->getName(), $user->blockedBy() );
1282 $this->assertSame( 'Because', $user->blockedFor() );
1283 $this->assertTrue( (bool)$user->isHidden() );
1284 $this->assertTrue( $user->isBlockedFrom( $ut ) );
1285
1286 // Unblock
1287 $block->delete();
1288
1289 // Clear cache and confirm it loaded the not-blocked properly
1290 $user->clearInstanceCache();
1291 $this->assertNull( $user->getBlock( false ) );
1292 $this->assertSame( '', $user->blockedBy() );
1293 $this->assertSame( '', $user->blockedFor() );
1294 $this->assertFalse( (bool)$user->isHidden() );
1295 $this->assertFalse( $user->isBlockedFrom( $ut ) );
1296 }
1297
1308 public function testIsBlockedFrom( $title, $expect, array $options = [] ) {
1309 $this->setMwGlobals( [
1310 'wgBlockAllowsUTEdit' => $options['blockAllowsUTEdit'] ?? true,
1311 ] );
1312
1313 $user = $this->getTestUser()->getUser();
1314
1315 if ( $title === self::USER_TALK_PAGE ) {
1316 $title = $user->getTalkPage();
1317 } else {
1318 $title = Title::newFromText( $title );
1319 }
1320
1321 $restrictions = [];
1322 foreach ( $options['pageRestrictions'] ?? [] as $pagestr ) {
1323 $page = $this->getExistingTestPage(
1324 $pagestr === self::USER_TALK_PAGE ? $user->getTalkPage() : $pagestr
1325 );
1326 $restrictions[] = new PageRestriction( 0, $page->getId() );
1327 }
1328 foreach ( $options['namespaceRestrictions'] ?? [] as $ns ) {
1329 $restrictions[] = new NamespaceRestriction( 0, $ns );
1330 }
1331
1332 $block = new Block( [
1333 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
1334 'allowUsertalk' => $options['allowUsertalk'] ?? false,
1335 'sitewide' => !$restrictions,
1336 ] );
1337 $block->setTarget( $user );
1338 $block->setBlocker( $this->getTestSysop()->getUser() );
1339 if ( $restrictions ) {
1340 $block->setRestrictions( $restrictions );
1341 }
1342 $block->insert();
1343
1344 try {
1345 $this->assertSame( $expect, $user->isBlockedFrom( $title ) );
1346 } finally {
1347 $block->delete();
1348 }
1349 }
1350
1351 public static function provideIsBlockedFrom() {
1352 return [
1353 'Sitewide block, basic operation' => [ 'Test page', true ],
1354 'Sitewide block, not allowing user talk' => [
1355 self::USER_TALK_PAGE, true, [
1356 'allowUsertalk' => false,
1357 ]
1358 ],
1359 'Sitewide block, allowing user talk' => [
1360 self::USER_TALK_PAGE, false, [
1361 'allowUsertalk' => true,
1362 ]
1363 ],
1364 'Sitewide block, allowing user talk but $wgBlockAllowsUTEdit is false' => [
1365 self::USER_TALK_PAGE, true, [
1366 'allowUsertalk' => true,
1367 'blockAllowsUTEdit' => false,
1368 ]
1369 ],
1370 'Partial block, blocking the page' => [
1371 'Test page', true, [
1372 'pageRestrictions' => [ 'Test page' ],
1373 ]
1374 ],
1375 'Partial block, not blocking the page' => [
1376 'Test page 2', false, [
1377 'pageRestrictions' => [ 'Test page' ],
1378 ]
1379 ],
1380 'Partial block, not allowing user talk but user talk page is not blocked' => [
1381 self::USER_TALK_PAGE, false, [
1382 'allowUsertalk' => false,
1383 'pageRestrictions' => [ 'Test page' ],
1384 ]
1385 ],
1386 'Partial block, allowing user talk but user talk page is blocked' => [
1387 self::USER_TALK_PAGE, true, [
1388 'allowUsertalk' => true,
1389 'pageRestrictions' => [ self::USER_TALK_PAGE ],
1390 ]
1391 ],
1392 'Partial block, user talk page is not blocked but $wgBlockAllowsUTEdit is false' => [
1393 self::USER_TALK_PAGE, false, [
1394 'allowUsertalk' => false,
1395 'pageRestrictions' => [ 'Test page' ],
1396 'blockAllowsUTEdit' => false,
1397 ]
1398 ],
1399 'Partial block, user talk page is blocked and $wgBlockAllowsUTEdit is false' => [
1400 self::USER_TALK_PAGE, true, [
1401 'allowUsertalk' => true,
1402 'pageRestrictions' => [ self::USER_TALK_PAGE ],
1403 'blockAllowsUTEdit' => false,
1404 ]
1405 ],
1406 'Partial user talk namespace block, not allowing user talk' => [
1407 self::USER_TALK_PAGE, true, [
1408 'allowUsertalk' => false,
1409 'namespaceRestrictions' => [ NS_USER_TALK ],
1410 ]
1411 ],
1412 'Partial user talk namespace block, allowing user talk' => [
1413 self::USER_TALK_PAGE, false, [
1414 'allowUsertalk' => true,
1415 'namespaceRestrictions' => [ NS_USER_TALK ],
1416 ]
1417 ],
1418 'Partial user talk namespace block, where $wgBlockAllowsUTEdit is false' => [
1419 self::USER_TALK_PAGE, true, [
1420 'allowUsertalk' => true,
1421 'namespaceRestrictions' => [ NS_USER_TALK ],
1422 'blockAllowsUTEdit' => false,
1423 ]
1424 ],
1425 ];
1426 }
1427
1433 public function testIpBlockCookieSet() {
1434 $this->setMwGlobals( [
1435 'wgCookieSetOnIpBlock' => true,
1436 'wgCookiePrefix' => 'wiki',
1437 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
1438 ] );
1439
1440 // setup block
1441 $block = new Block( [
1442 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
1443 ] );
1444 $block->setTarget( '1.2.3.4' );
1445 $block->setBlocker( $this->getTestSysop()->getUser() );
1446 $block->insert();
1447
1448 // setup request
1449 $request = new FauxRequest();
1450 $request->setIP( '1.2.3.4' );
1451
1452 // get user
1453 $user = User::newFromSession( $request );
1454 $user->trackBlockWithCookie();
1455
1456 // test cookie was set
1457 $cookies = $request->response()->getCookies();
1458 $this->assertArrayHasKey( 'wikiBlockID', $cookies );
1459
1460 // clean up
1461 $block->delete();
1462 }
1463
1469 public function testIpBlockCookieNotSet() {
1470 $this->setMwGlobals( [
1471 'wgCookieSetOnIpBlock' => false,
1472 'wgCookiePrefix' => 'wiki',
1473 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
1474 ] );
1475
1476 // setup block
1477 $block = new Block( [
1478 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
1479 ] );
1480 $block->setTarget( '1.2.3.4' );
1481 $block->setBlocker( $this->getTestSysop()->getUser() );
1482 $block->insert();
1483
1484 // setup request
1485 $request = new FauxRequest();
1486 $request->setIP( '1.2.3.4' );
1487
1488 // get user
1489 $user = User::newFromSession( $request );
1490 $user->trackBlockWithCookie();
1491
1492 // test cookie was not set
1493 $cookies = $request->response()->getCookies();
1494 $this->assertArrayNotHasKey( 'wikiBlockID', $cookies );
1495
1496 // clean up
1497 $block->delete();
1498 }
1499
1506 $this->setMwGlobals( [
1507 'wgAutoblockExpiry' => 8000,
1508 'wgCookieSetOnIpBlock' => true,
1509 'wgCookiePrefix' => 'wiki',
1510 'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
1511 ] );
1512
1513 // setup block
1514 $block = new Block( [
1515 'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
1516 ] );
1517 $block->setTarget( '1.2.3.4' );
1518 $block->setBlocker( $this->getTestSysop()->getUser() );
1519 $block->insert();
1520
1521 // setup request
1522 $request = new FauxRequest();
1523 $request->setIP( '1.2.3.4' );
1524 $request->getSession()->setUser( $this->getTestUser()->getUser() );
1525 $request->setCookie( 'BlockID', $block->getCookieValue() );
1526
1527 // setup user
1528 $user = User::newFromSession( $request );
1529
1530 // logged in users should be inmune to cookie block of type ip/range
1531 $this->assertFalse( $user->isBlocked() );
1532
1533 // cookie is being cleared
1534 $cookies = $request->response()->getCookies();
1535 $this->assertEquals( '', $cookies['wikiBlockID']['value'] );
1536
1537 // clean up
1538 $block->delete();
1539 }
1540
1546 $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
1547 MWTimestamp::setFakeTime( function () use ( &$clock ) {
1548 return $clock += 1000;
1549 } );
1550 try {
1551 $user = $this->getTestUser()->getUser();
1552 $firstRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'one', 'test' );
1553 $secondRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'two', 'test' );
1554 // Sanity check: revisions timestamp are different
1555 $this->assertNotEquals( $firstRevision->getTimestamp(), $secondRevision->getTimestamp() );
1556
1557 $this->assertEquals( $firstRevision->getTimestamp(), $user->getFirstEditTimestamp() );
1558 $this->assertEquals( $secondRevision->getTimestamp(), $user->getLatestEditTimestamp() );
1559 } finally {
1560 MWTimestamp::setFakeTime( false );
1561 }
1562 }
1563
1571 private static function makeEdit( User $user, $title, $content, $comment ) {
1572 $page = WikiPage::factory( Title::newFromText( $title ) );
1573 $content = ContentHandler::makeContent( $content, $page->getTitle() );
1574 $updater = $page->newPageUpdater( $user );
1575 $updater->setContent( 'main', $content );
1576 return $updater->saveRevision( CommentStoreComment::newUnsavedComment( $comment ) );
1577 }
1578}
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
they could even be mouse clicks or menu items whatever suits your program You should also get your if any
Definition COPYING.txt:326
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
$wgRevokePermissions
Permission keys revoked from users in each group.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$wgGroupPermissions['sysop']['replacetext']
static getIdFromCookieValue( $cookieValue)
Get the stored ID from the 'BlockID' cookie.
Definition Block.php:1851
const TYPE_USER
Definition Block.php:96
WebRequest clone which takes values from a provided array.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
static TestUser[] $users
Database $db
Primary database.
getExistingTestPage( $title=null)
Returns a WikiPage representing an existing page.
static getMutableTestUser( $groups=[])
Convenience method for getting a mutable test user.
markTestSkippedIfDbType( $type)
Skip the test if using the specified database type.
static getTestSysop()
Convenience method for getting an immutable admin test user.
overrideMwServices(Config $configOverrides=null, array $services=[])
Stashes the global instance of MediaWikiServices, and installs a new one, allowing test cases to over...
mergeMwGlobalArrayValue( $name, $values)
Merges the given values into a MW global array variable.
setMwGlobals( $pairs, $value=null)
Sets a global, maintaining a stashed version of the previous global to be restored in tearDown.
hideDeprecated( $function)
Don't throw a warning if $function is deprecated and called later.
static getTestUser( $groups=[])
Convenience method for getting an immutable test user.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Value object representing a user's identity.
Database.
Definition UserTest.php:15
testUserPermissions()
User::getRights.
Definition UserTest.php:98
testGetGroupsWithPermission( $expected, $right)
provideGetGroupsWithPermission User::getGroupsWithPermission
Definition UserTest.php:159
testIpBlockCookieSet()
Block cookie should be set for IP Blocks if wgCookieSetOnIpBlock is set to true User::trackBlockWithC...
testActorId_old()
Actor tests with SCHEMA_COMPAT_READ_OLD.
testExperienceLevelAnon()
User::getExperienceLevel.
Definition UserTest.php:977
testUserGetRightsHooks()
User::getRights.
Definition UserTest.php:109
testOptions()
Test changing user options.
Definition UserTest.php:326
const USER_TALK_PAGE
Constant for self::testIsBlockedFrom.
Definition UserTest.php:18
testGetFirstLatestEditTimestamp()
User::getFirstEditTimestamp User::getLatestEditTimestamp.
testGetCanonicalName( $name, $expectedArray)
User::getCanonicalName() provideGetCanonicalName.
Definition UserTest.php:431
User $user
Definition UserTest.php:23
testNewFromAnyId()
User::newFromAnyId.
testAutoblockCookies()
When a user is autoblocked a cookie is set with which to track them in case they log out and change I...
Definition UserTest.php:588
static makeEdit(User $user, $title, $content, $comment)
testAutoblockCookieInfiniteExpiry()
When a user is autoblocked and a cookie is set to track them, the expiry time of the cookie should ma...
Definition UserTest.php:715
testAutoblockCookieInauthentic()
Test that a modified BlockID cookie doesn't actually load the relevant block (T152951).
Definition UserTest.php:818
testExperienceLevel( $editCount, $memberSince, $expLevel)
User::getExperienceLevel provideExperienceLevel.
Definition UserTest.php:949
testIsPingLimitable()
User::isPingLimitable.
Definition UserTest.php:910
static provideGetGroupsWithPermission()
Definition UserTest.php:167
testIsValidUserName( $username, $result, $message)
provideUserNames User::isValidUserName
Definition UserTest.php:217
testIsIP( $value, $result, $message)
provideIPs User::isIP
Definition UserTest.php:192
testGetEditCount()
Test User::editCount medium User::getEditCount.
Definition UserTest.php:249
testAutoblockCookieNoSecretKey()
The BlockID cookie is normally verified with a HMAC, but not if wgSecretKey is not set.
Definition UserTest.php:863
testSoftBlockRanges()
User::getBlockedStatus.
Definition UserTest.php:779
static provideGetCanonicalName()
Definition UserTest.php:455
testAnonOptions()
T39963 Make sure defaults are loaded when setOption is called.
Definition UserTest.php:355
setUpPermissionGlobals()
Definition UserTest.php:40
testIncEditCount()
Test User::editCount medium User::incEditCount.
Definition UserTest.php:307
testIpBlockCookieIgnoredWhenUserLoggedIn()
When an ip user is blocked and then they log in, cookie block should be invalid and the cookie remove...
testIpBlockCookieNotSet()
Block cookie should NOT be set when wgCookieSetOnIpBlock is disabled User::trackBlockWithCookie.
testGetEditCountForAnons()
Test User::editCount medium User::getEditCount.
Definition UserTest.php:286
testRevokePermissions()
User::getGroupPermissions.
Definition UserTest.php:87
static provideIsLocallBlockedProxy()
Definition UserTest.php:983
provideExperienceLevel()
Definition UserTest.php:932
testGetId()
User::getId.
Definition UserTest.php:501
testNewFromIdentity()
User::newFromIdentity.
testEquals()
User::equals.
Definition UserTest.php:477
static provideIsBlockedFrom()
testCheckPasswordValidity()
Test password validity checks.
Definition UserTest.php:371
testIsLocallyBlockedProxy( $ip, $blockListEntry)
provideIsLocallBlockedProxy User::isLocallyBlockedProxy
Definition UserTest.php:994
testBlockInstanceCache()
User::getBlockedStatus User::getBlock User::blockedBy User::blockedFor User::isHidden User::isBlocked...
testCheckAndSetTouched()
User::checkAndSetTouched.
Definition UserTest.php:528
testLoggedIn()
User::isLoggedIn User::isAnon.
Definition UserTest.php:510
testAutoblockCookiesDisabled()
Make sure that no cookie is set to track autoblocked users when $wgCookieSetOnAutoblock is false.
Definition UserTest.php:669
testGroupPermissions()
User::getGroupPermissions.
Definition UserTest.php:70
static provideIPs()
Definition UserTest.php:196
testFindUsersByGroup()
User::findUsersByGroup.
Definition UserTest.php:549
testIsBlockedFrom( $title, $expect, array $options=[])
User::isBlockedFrom provideIsBlockedFrom.
testActorId()
User::newFromActorId.
static provideUserNames()
Definition UserTest.php:221
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
getPasswordValidity( $password)
Given unvalidated password input, return error message on failure.
Definition User.php:1159
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2452
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition User.php:4065
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:585
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object.
Definition User.php:5643
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition User.php:5729
getId()
Get the user's ID.
Definition User.php:2425
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition User.php:2061
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition User.php:676
static purge( $wikiId, $userId)
Definition User.php:488
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition User.php:1734
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user's current setting for a given option.
Definition User.php:3169
isPingLimitable()
Is this user subject to rate limiting?
Definition User.php:2107
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition User.php:1244
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition User.php:772
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:609
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition User.php:5039
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition User.php:5012
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition User.php:1077
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition User.php:5617
isHidden()
Check if user account is hidden.
Definition User.php:2408
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition User.php:1202
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition User.php:750
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:3256
getEditCount()
Get the user's edit count.
Definition User.php:3692
getActorId(IDatabase $dbw=null)
Get the user's actor ID.
Definition User.php:2491
isValidPassword( $password)
Is the input a valid password for this user?
Definition User.php:1147
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition User.php:652
checkAndSetTouched()
Bump user_touched if it didn't change since this object was loaded.
Definition User.php:1696
getBlockId()
If user is blocked, return the ID for the block.
Definition User.php:2333
isLoggedIn()
Get whether the user is logged in.
Definition User.php:3793
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition User.php:2315
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition User.php:4300
incEditCount()
Schedule a deferred update to update the user's edit count.
Definition User.php:5350
blockedFor()
If user is blocked, return the specified reason for the block.
Definition User.php:2324
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:2289
isBlocked( $fromReplica=true)
Check if user is blocked.
Definition User.php:2278
isAnon()
Get whether the user is anonymous.
Definition User.php:3801
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition User.php:2306
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition User.php:624
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
selectRow( $table, $vars, $conds, $fname=__METHOD__, $options=[], $join_conds=[])
Single row SELECT wrapper.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by wfTimestamp() to the format used for inserting ...
$res
Definition database.txt:21
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2843
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:1999
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition hooks.txt:783
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition hooks.txt:2004
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:782
processing should stop and the error should be shown to the user * false
Definition hooks.txt:187
const SCHEMA_COMPAT_WRITE_BOTH
Definition Defines.php:297
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:294
const SCHEMA_COMPAT_NEW
Definition Defines.php:300
const NS_USER_TALK
Definition Defines.php:76
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
A helper class for throttling authentication attempts.
$page->newPageUpdater($user) $updater
$content
const DB_MASTER
Definition defines.php:26