MediaWiki  master
UserTest.php
Go to the documentation of this file.
1 <?php
2 
3 define( 'NS_UNITTEST', 5600 );
4 define( 'NS_UNITTEST_TALK', 5601 );
5 
14 
18 class UserTest extends MediaWikiTestCase {
19 
21  const USER_TALK_PAGE = '<user talk page>';
22 
26  protected $user;
27 
28  protected function setUp() {
29  parent::setUp();
30 
31  $this->setMwGlobals( [
32  'wgGroupPermissions' => [],
33  'wgRevokePermissions' => [],
34  'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_NEW,
35  ] );
36  $this->overrideMwServices();
37 
38  $this->setUpPermissionGlobals();
39 
40  $this->user = $this->getTestUser( [ 'unittesters' ] )->getUser();
41  }
42 
43  private function setUpPermissionGlobals() {
45 
46  # Data for regular $wgGroupPermissions test
47  $wgGroupPermissions['unittesters'] = [
48  'test' => true,
49  'runtest' => true,
50  'writetest' => false,
51  'nukeworld' => false,
52  ];
53  $wgGroupPermissions['testwriters'] = [
54  'test' => true,
55  'writetest' => true,
56  'modifytest' => true,
57  ];
58 
59  # Data for regular $wgRevokePermissions test
60  $wgRevokePermissions['formertesters'] = [
61  'runtest' => true,
62  ];
63 
64  # For the options test
65  $wgGroupPermissions['*'] = [
66  'editmyoptions' => true,
67  ];
68  }
69 
70  private function setSessionUser( User $user, WebRequest $request ) {
71  $this->setMwGlobals( 'wgUser', $user );
72  RequestContext::getMain()->setUser( $user );
73  RequestContext::getMain()->setRequest( $request );
74  TestingAccessWrapper::newFromObject( $user )->mRequest = $request;
75  $request->getSession()->setUser( $user );
76  $this->overrideMwServices();
77  }
78 
82  public function testGroupPermissions() {
83  $rights = User::getGroupPermissions( [ 'unittesters' ] );
84  $this->assertContains( 'runtest', $rights );
85  $this->assertNotContains( 'writetest', $rights );
86  $this->assertNotContains( 'modifytest', $rights );
87  $this->assertNotContains( 'nukeworld', $rights );
88 
89  $rights = User::getGroupPermissions( [ 'unittesters', 'testwriters' ] );
90  $this->assertContains( 'runtest', $rights );
91  $this->assertContains( 'writetest', $rights );
92  $this->assertContains( 'modifytest', $rights );
93  $this->assertNotContains( 'nukeworld', $rights );
94  }
95 
99  public function testRevokePermissions() {
100  $rights = User::getGroupPermissions( [ 'unittesters', 'formertesters' ] );
101  $this->assertNotContains( 'runtest', $rights );
102  $this->assertNotContains( 'writetest', $rights );
103  $this->assertNotContains( 'modifytest', $rights );
104  $this->assertNotContains( 'nukeworld', $rights );
105  }
106 
110  public function testUserPermissions() {
111  $rights = $this->user->getRights();
112  $this->assertContains( 'runtest', $rights );
113  $this->assertNotContains( 'writetest', $rights );
114  $this->assertNotContains( 'modifytest', $rights );
115  $this->assertNotContains( 'nukeworld', $rights );
116  }
117 
121  public function testUserGetRightsHooks() {
122  $user = $this->getTestUser( [ 'unittesters', 'testwriters' ] )->getUser();
123  $userWrapper = TestingAccessWrapper::newFromObject( $user );
124 
125  $rights = $user->getRights();
126  $this->assertContains( 'test', $rights, 'sanity check' );
127  $this->assertContains( 'runtest', $rights, 'sanity check' );
128  $this->assertContains( 'writetest', $rights, 'sanity check' );
129  $this->assertNotContains( 'nukeworld', $rights, 'sanity check' );
130 
131  // Add a hook manipluating the rights
132  $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'UserGetRights' => [ function ( $user, &$rights ) {
133  $rights[] = 'nukeworld';
134  $rights = array_diff( $rights, [ 'writetest' ] );
135  } ] ] );
136 
137  $userWrapper->mRights = null;
138  $rights = $user->getRights();
139  $this->assertContains( 'test', $rights );
140  $this->assertContains( 'runtest', $rights );
141  $this->assertNotContains( 'writetest', $rights );
142  $this->assertContains( 'nukeworld', $rights );
143 
144  // Add a Session that limits rights
145  $mock = $this->getMockBuilder( stdClass::class )
146  ->setMethods( [ 'getAllowedUserRights', 'deregisterSession', 'getSessionId' ] )
147  ->getMock();
148  $mock->method( 'getAllowedUserRights' )->willReturn( [ 'test', 'writetest' ] );
149  $mock->method( 'getSessionId' )->willReturn(
150  new MediaWiki\Session\SessionId( str_repeat( 'X', 32 ) )
151  );
153  $mockRequest = $this->getMockBuilder( FauxRequest::class )
154  ->setMethods( [ 'getSession' ] )
155  ->getMock();
156  $mockRequest->method( 'getSession' )->willReturn( $session );
157  $userWrapper->mRequest = $mockRequest;
158 
159  $userWrapper->mRights = null;
160  $rights = $user->getRights();
161  $this->assertContains( 'test', $rights );
162  $this->assertNotContains( 'runtest', $rights );
163  $this->assertNotContains( 'writetest', $rights );
164  $this->assertNotContains( 'nukeworld', $rights );
165  }
166 
171  public function testGetGroupsWithPermission( $expected, $right ) {
173  sort( $result );
174  sort( $expected );
175 
176  $this->assertEquals( $expected, $result, "Groups with permission $right" );
177  }
178 
179  public static function provideGetGroupsWithPermission() {
180  return [
181  [
182  [ 'unittesters', 'testwriters' ],
183  'test'
184  ],
185  [
186  [ 'unittesters' ],
187  'runtest'
188  ],
189  [
190  [ 'testwriters' ],
191  'writetest'
192  ],
193  [
194  [ 'testwriters' ],
195  'modifytest'
196  ],
197  ];
198  }
199 
204  public function testIsIP( $value, $result, $message ) {
205  $this->assertEquals( $this->user->isIP( $value ), $result, $message );
206  }
207 
208  public static function provideIPs() {
209  return [
210  [ '', false, 'Empty string' ],
211  [ ' ', false, 'Blank space' ],
212  [ '10.0.0.0', true, 'IPv4 private 10/8' ],
213  [ '10.255.255.255', true, 'IPv4 private 10/8' ],
214  [ '192.168.1.1', true, 'IPv4 private 192.168/16' ],
215  [ '203.0.113.0', true, 'IPv4 example' ],
216  [ '2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff', true, 'IPv6 example' ],
217  // Not valid IPs but classified as such by MediaWiki for negated asserting
218  // of whether this might be the identifier of a logged-out user or whether
219  // to allow usernames like it.
220  [ '300.300.300.300', true, 'Looks too much like an IPv4 address' ],
221  [ '203.0.113.xxx', true, 'Assigned by UseMod to cloaked logged-out users' ],
222  ];
223  }
224 
229  public function testIsValidUserName( $username, $result, $message ) {
230  $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
231  }
232 
233  public static function provideUserNames() {
234  return [
235  [ '', false, 'Empty string' ],
236  [ ' ', false, 'Blank space' ],
237  [ 'abcd', false, 'Starts with small letter' ],
238  [ 'Ab/cd', false, 'Contains slash' ],
239  [ 'Ab cd', true, 'Whitespace' ],
240  [ '192.168.1.1', false, 'IP' ],
241  [ '116.17.184.5/32', false, 'IP range' ],
242  [ '::e:f:2001/96', false, 'IPv6 range' ],
243  [ 'User:Abcd', false, 'Reserved Namespace' ],
244  [ '12abcd232', true, 'Starts with Numbers' ],
245  [ '?abcd', true, 'Start with ? mark' ],
246  [ '#abcd', false, 'Start with #' ],
247  [ 'Abcdകഖഗഘ', true, ' Mixed scripts' ],
248  [ 'ജോസ്‌തോമസ്', false, 'ZWNJ- Format control character' ],
249  [ 'Ab cd', false, ' Ideographic space' ],
250  [ '300.300.300.300', false, 'Looks too much like an IPv4 address' ],
251  [ '302.113.311.900', false, 'Looks too much like an IPv4 address' ],
252  [ '203.0.113.xxx', false, 'Reserved for usage by UseMod for cloaked logged-out users' ],
253  ];
254  }
255 
261  public function testGetEditCount() {
262  $user = $this->getMutableTestUser()->getUser();
263 
264  // let the user have a few (3) edits
265  $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) );
266  for ( $i = 0; $i < 3; $i++ ) {
267  $page->doEditContent(
268  ContentHandler::makeContent( (string)$i, $page->getTitle() ),
269  'test',
270  0,
271  false,
272  $user
273  );
274  }
275 
276  $this->assertEquals(
277  3,
278  $user->getEditCount(),
279  'After three edits, the user edit count should be 3'
280  );
281 
282  // increase the edit count
283  $user->incEditCount();
285 
286  $this->assertEquals(
287  4,
288  $user->getEditCount(),
289  'After increasing the edit count manually, the user edit count should be 4'
290  );
291  }
292 
298  public function testGetEditCountForAnons() {
299  $user = User::newFromName( 'Anonymous' );
300 
301  $this->assertNull(
302  $user->getEditCount(),
303  'Edit count starts null for anonymous users.'
304  );
305 
306  $user->incEditCount();
307 
308  $this->assertNull(
309  $user->getEditCount(),
310  'Edit count remains null for anonymous users despite calls to increase it.'
311  );
312  }
313 
319  public function testIncEditCount() {
320  $user = $this->getMutableTestUser()->getUser();
321  $user->incEditCount();
322 
323  $reloadedUser = User::newFromId( $user->getId() );
324  $reloadedUser->incEditCount();
325 
326  $this->assertEquals(
327  2,
328  $reloadedUser->getEditCount(),
329  'Increasing the edit count after a fresh load leaves the object up to date.'
330  );
331  }
332 
338  public function testOptions() {
339  $user = $this->getMutableTestUser()->getUser();
340 
341  $user->setOption( 'userjs-someoption', 'test' );
342  $user->setOption( 'rclimit', 200 );
343  $user->setOption( 'wpwatchlistdays', '0' );
344  $user->saveSettings();
345 
347  $user->load( User::READ_LATEST );
348  $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
349  $this->assertEquals( 200, $user->getOption( 'rclimit' ) );
350 
351  $user = User::newFromName( $user->getName() );
352  MediaWikiServices::getInstance()->getMainWANObjectCache()->clearProcessCache();
353  $this->assertEquals( 'test', $user->getOption( 'userjs-someoption' ) );
354  $this->assertEquals( 200, $user->getOption( 'rclimit' ) );
355 
356  // Check that an option saved as a string '0' is returned as an integer.
357  $user = User::newFromName( $user->getName() );
358  $user->load( User::READ_LATEST );
359  $this->assertSame( 0, $user->getOption( 'wpwatchlistdays' ) );
360  }
361 
367  public function testAnonOptions() {
368  global $wgDefaultUserOptions;
369  $this->user->setOption( 'userjs-someoption', 'test' );
370  $this->assertEquals( $wgDefaultUserOptions['rclimit'], $this->user->getOption( 'rclimit' ) );
371  $this->assertEquals( 'test', $this->user->getOption( 'userjs-someoption' ) );
372  }
373 
382  public function testCheckPasswordValidity() {
383  $this->setMwGlobals( [
384  'wgPasswordPolicy' => [
385  'policies' => [
386  'sysop' => [
387  'MinimalPasswordLength' => 8,
388  'MinimumPasswordLengthToLogin' => 1,
389  'PasswordCannotMatchUsername' => 1,
390  ],
391  'default' => [
392  'MinimalPasswordLength' => 6,
393  'PasswordCannotMatchUsername' => true,
394  'PasswordCannotMatchBlacklist' => true,
395  'MaximalPasswordLength' => 40,
396  ],
397  ],
398  'checks' => [
399  'MinimalPasswordLength' => 'PasswordPolicyChecks::checkMinimalPasswordLength',
400  'MinimumPasswordLengthToLogin' => 'PasswordPolicyChecks::checkMinimumPasswordLengthToLogin',
401  'PasswordCannotMatchUsername' => 'PasswordPolicyChecks::checkPasswordCannotMatchUsername',
402  'PasswordCannotMatchBlacklist' => 'PasswordPolicyChecks::checkPasswordCannotMatchBlacklist',
403  'MaximalPasswordLength' => 'PasswordPolicyChecks::checkMaximalPasswordLength',
404  ],
405  ],
406  ] );
407 
408  $user = static::getTestUser()->getUser();
409 
410  // Sanity
411  $this->assertTrue( $user->isValidPassword( 'Password1234' ) );
412 
413  // Minimum length
414  $this->assertFalse( $user->isValidPassword( 'a' ) );
415  $this->assertFalse( $user->checkPasswordValidity( 'a' )->isGood() );
416  $this->assertTrue( $user->checkPasswordValidity( 'a' )->isOK() );
417 
418  // Maximum length
419  $longPass = str_repeat( 'a', 41 );
420  $this->assertFalse( $user->isValidPassword( $longPass ) );
421  $this->assertFalse( $user->checkPasswordValidity( $longPass )->isGood() );
422  $this->assertFalse( $user->checkPasswordValidity( $longPass )->isOK() );
423 
424  // Matches username
425  $this->assertFalse( $user->checkPasswordValidity( $user->getName() )->isGood() );
426  $this->assertTrue( $user->checkPasswordValidity( $user->getName() )->isOK() );
427 
428  // On the forbidden list
429  $user = User::newFromName( 'Useruser' );
430  $this->assertFalse( $user->checkPasswordValidity( 'Passpass' )->isGood() );
431  }
432 
437  public function testGetCanonicalName( $name, $expectedArray ) {
438  // fake interwiki map for the 'Interwiki prefix' testcase
439  $this->mergeMwGlobalArrayValue( 'wgHooks', [
440  'InterwikiLoadPrefix' => [
441  function ( $prefix, &$iwdata ) {
442  if ( $prefix === 'interwiki' ) {
443  $iwdata = [
444  'iw_url' => 'http://example.com/',
445  'iw_local' => 0,
446  'iw_trans' => 0,
447  ];
448  return false;
449  }
450  },
451  ],
452  ] );
453 
454  foreach ( $expectedArray as $validate => $expected ) {
455  $this->assertEquals(
456  $expected,
457  User::getCanonicalName( $name, $validate === 'false' ? false : $validate ), $validate );
458  }
459  }
460 
461  public static function provideGetCanonicalName() {
462  return [
463  'Leading space' => [ ' Leading space', [ 'creatable' => 'Leading space' ] ],
464  'Trailing space ' => [ 'Trailing space ', [ 'creatable' => 'Trailing space' ] ],
465  'Namespace prefix' => [ 'Talk:Username', [ 'creatable' => false, 'usable' => false,
466  'valid' => false, 'false' => 'Talk:Username' ] ],
467  'Interwiki prefix' => [ 'interwiki:Username', [ 'creatable' => false, 'usable' => false,
468  'valid' => false, 'false' => 'Interwiki:Username' ] ],
469  'With hash' => [ 'name with # hash', [ 'creatable' => false, 'usable' => false ] ],
470  'Multi spaces' => [ 'Multi spaces', [ 'creatable' => 'Multi spaces',
471  'usable' => 'Multi spaces' ] ],
472  'Lowercase' => [ 'lowercase', [ 'creatable' => 'Lowercase' ] ],
473  'Invalid character' => [ 'in[]valid', [ 'creatable' => false, 'usable' => false,
474  'valid' => false, 'false' => 'In[]valid' ] ],
475  'With slash' => [ 'with / slash', [ 'creatable' => false, 'usable' => false, 'valid' => false,
476  'false' => 'With / slash' ] ],
477  ];
478  }
479 
483  public function testEquals() {
484  $first = $this->getMutableTestUser()->getUser();
485  $second = User::newFromName( $first->getName() );
486 
487  $this->assertTrue( $first->equals( $first ) );
488  $this->assertTrue( $first->equals( $second ) );
489  $this->assertTrue( $second->equals( $first ) );
490 
491  $third = $this->getMutableTestUser()->getUser();
492  $fourth = $this->getMutableTestUser()->getUser();
493 
494  $this->assertFalse( $third->equals( $fourth ) );
495  $this->assertFalse( $fourth->equals( $third ) );
496 
497  // Test users loaded from db with id
498  $user = $this->getMutableTestUser()->getUser();
499  $fifth = User::newFromId( $user->getId() );
500  $sixth = User::newFromName( $user->getName() );
501  $this->assertTrue( $fifth->equals( $sixth ) );
502  }
503 
507  public function testGetId() {
508  $user = static::getTestUser()->getUser();
509  $this->assertTrue( $user->getId() > 0 );
510  }
511 
517  public function testLoggedIn() {
518  $user = $this->getMutableTestUser()->getUser();
519  $this->assertTrue( $user->isRegistered() );
520  $this->assertTrue( $user->isLoggedIn() );
521  $this->assertFalse( $user->isAnon() );
522 
523  // Non-existent users are perceived as anonymous
524  $user = User::newFromName( 'UTNonexistent' );
525  $this->assertFalse( $user->isRegistered() );
526  $this->assertFalse( $user->isLoggedIn() );
527  $this->assertTrue( $user->isAnon() );
528 
529  $user = new User;
530  $this->assertFalse( $user->isRegistered() );
531  $this->assertFalse( $user->isLoggedIn() );
532  $this->assertTrue( $user->isAnon() );
533  }
534 
538  public function testCheckAndSetTouched() {
539  $user = $this->getMutableTestUser()->getUser();
540  $user = TestingAccessWrapper::newFromObject( $user );
541  $this->assertTrue( $user->isLoggedIn() );
542 
543  $touched = $user->getDBTouched();
544  $this->assertTrue(
545  $user->checkAndSetTouched(), "checkAndSetTouched() succedeed" );
546  $this->assertGreaterThan(
547  $touched, $user->getDBTouched(), "user_touched increased with casOnTouched()" );
548 
549  $touched = $user->getDBTouched();
550  $this->assertTrue(
551  $user->checkAndSetTouched(), "checkAndSetTouched() succedeed #2" );
552  $this->assertGreaterThan(
553  $touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" );
554  }
555 
559  public function testFindUsersByGroup() {
560  // FIXME: fails under postgres
561  $this->markTestSkippedIfDbType( 'postgres' );
562 
564  $this->assertEquals( 0, iterator_count( $users ) );
565 
566  $users = User::findUsersByGroup( 'foo' );
567  $this->assertEquals( 0, iterator_count( $users ) );
568 
569  $user = $this->getMutableTestUser( [ 'foo' ] )->getUser();
570  $users = User::findUsersByGroup( 'foo' );
571  $this->assertEquals( 1, iterator_count( $users ) );
572  $users->rewind();
573  $this->assertTrue( $user->equals( $users->current() ) );
574 
575  // arguments have OR relationship
576  $user2 = $this->getMutableTestUser( [ 'bar' ] )->getUser();
577  $users = User::findUsersByGroup( [ 'foo', 'bar' ] );
578  $this->assertEquals( 2, iterator_count( $users ) );
579  $users->rewind();
580  $this->assertTrue( $user->equals( $users->current() ) );
581  $users->next();
582  $this->assertTrue( $user2->equals( $users->current() ) );
583 
584  // users are not duplicated
585  $user = $this->getMutableTestUser( [ 'baz', 'boom' ] )->getUser();
586  $users = User::findUsersByGroup( [ 'baz', 'boom' ] );
587  $this->assertEquals( 1, iterator_count( $users ) );
588  $users->rewind();
589  $this->assertTrue( $user->equals( $users->current() ) );
590  }
591 
598  public function testAutoblockCookies() {
599  // Set up the bits of global configuration that we use.
600  $this->setMwGlobals( [
601  'wgCookieSetOnAutoblock' => true,
602  'wgCookiePrefix' => 'wmsitetitle',
603  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
604  ] );
605 
606  // Unregister the hooks for proper unit testing
607  $this->mergeMwGlobalArrayValue( 'wgHooks', [
608  'PerformRetroactiveAutoblock' => []
609  ] );
610 
611  // 1. Log in a test user, and block them.
612  $user1tmp = $this->getTestUser()->getUser();
613  $request1 = new FauxRequest();
614  $request1->getSession()->setUser( $user1tmp );
615  $expiryFiveHours = wfTimestamp() + ( 5 * 60 * 60 );
616  $block = new DatabaseBlock( [
617  'enableAutoblock' => true,
618  'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
619  ] );
620  $block->setBlocker( $this->getTestSysop()->getUser() );
621  $block->setTarget( $user1tmp );
622  $res = $block->insert();
623  $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
624  $user1 = User::newFromSession( $request1 );
625  $user1->mBlock = $block;
626  $user1->load();
627 
628  // Confirm that the block has been applied as required.
629  $this->assertTrue( $user1->isLoggedIn() );
630  $this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
631  $this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
632  $this->assertTrue( $block->isAutoblocking() );
633  $this->assertGreaterThanOrEqual( 1, $block->getId() );
634 
635  // Test for the desired cookie name, value, and expiry.
636  $cookies = $request1->response()->getCookies();
637  $this->assertArrayHasKey( 'wmsitetitleBlockID', $cookies );
638  $this->assertEquals( $expiryFiveHours, $cookies['wmsitetitleBlockID']['expire'] );
639  $cookieId = MediaWikiServices::getInstance()->getBlockManager()->getIdFromCookieValue(
640  $cookies['wmsitetitleBlockID']['value']
641  );
642  $this->assertEquals( $block->getId(), $cookieId );
643 
644  // 2. Create a new request, set the cookies, and see if the (anon) user is blocked.
645  $request2 = new FauxRequest();
646  $request2->setCookie( 'BlockID', $block->getCookieValue() );
647  $user2 = User::newFromSession( $request2 );
648  $user2->load();
649  $this->assertNotEquals( $user1->getId(), $user2->getId() );
650  $this->assertNotEquals( $user1->getToken(), $user2->getToken() );
651  $this->assertTrue( $user2->isAnon() );
652  $this->assertFalse( $user2->isLoggedIn() );
653  $this->assertInstanceOf( DatabaseBlock::class, $user2->getBlock() );
654  // Non-strict type-check.
655  $this->assertEquals( true, $user2->getBlock()->isAutoblocking(), 'Autoblock does not work' );
656  // Can't directly compare the objects because of member type differences.
657  // One day this will work: $this->assertEquals( $block, $user2->getBlock() );
658  $this->assertEquals( $block->getId(), $user2->getBlock()->getId() );
659  $this->assertEquals( $block->getExpiry(), $user2->getBlock()->getExpiry() );
660 
661  // 3. Finally, set up a request as a new user, and the block should still be applied.
662  $user3tmp = $this->getTestUser()->getUser();
663  $request3 = new FauxRequest();
664  $request3->getSession()->setUser( $user3tmp );
665  $request3->setCookie( 'BlockID', $block->getId() );
666  $user3 = User::newFromSession( $request3 );
667  $user3->load();
668  $this->assertTrue( $user3->isLoggedIn() );
669  $this->assertInstanceOf( DatabaseBlock::class, $user3->getBlock() );
670  $this->assertEquals( true, $user3->getBlock()->isAutoblocking() ); // Non-strict type-check.
671 
672  // Clean up.
673  $block->delete();
674  }
675 
681  public function testAutoblockCookiesDisabled() {
682  // Set up the bits of global configuration that we use.
683  $this->setMwGlobals( [
684  'wgCookieSetOnAutoblock' => false,
685  'wgCookiePrefix' => 'wm_no_cookies',
686  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
687  ] );
688 
689  // Unregister the hooks for proper unit testing
690  $this->mergeMwGlobalArrayValue( 'wgHooks', [
691  'PerformRetroactiveAutoblock' => []
692  ] );
693 
694  // 1. Log in a test user, and block them.
695  $testUser = $this->getTestUser()->getUser();
696  $request1 = new FauxRequest();
697  $request1->getSession()->setUser( $testUser );
698  $block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
699  $block->setBlocker( $this->getTestSysop()->getUser() );
700  $block->setTarget( $testUser );
701  $res = $block->insert();
702  $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
703  $user = User::newFromSession( $request1 );
704  $user->mBlock = $block;
705  $user->load();
706 
707  // 2. Test that the cookie IS NOT present.
708  $this->assertTrue( $user->isLoggedIn() );
709  $this->assertInstanceOf( DatabaseBlock::class, $user->getBlock() );
710  $this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
711  $this->assertTrue( $block->isAutoblocking() );
712  $this->assertGreaterThanOrEqual( 1, $user->getBlockId() );
713  $this->assertGreaterThanOrEqual( $block->getId(), $user->getBlockId() );
714  $cookies = $request1->response()->getCookies();
715  $this->assertArrayNotHasKey( 'wm_no_cookiesBlockID', $cookies );
716 
717  // Clean up.
718  $block->delete();
719  }
720 
728  $this->setMwGlobals( [
729  'wgCookieSetOnAutoblock' => true,
730  'wgCookiePrefix' => 'wm_infinite_block',
731  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
732  ] );
733 
734  // Unregister the hooks for proper unit testing
735  $this->mergeMwGlobalArrayValue( 'wgHooks', [
736  'PerformRetroactiveAutoblock' => []
737  ] );
738 
739  // 1. Log in a test user, and block them indefinitely.
740  $user1Tmp = $this->getTestUser()->getUser();
741  $request1 = new FauxRequest();
742  $request1->getSession()->setUser( $user1Tmp );
743  $block = new DatabaseBlock( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
744  $block->setBlocker( $this->getTestSysop()->getUser() );
745  $block->setTarget( $user1Tmp );
746  $res = $block->insert();
747  $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
748  $user1 = User::newFromSession( $request1 );
749  $user1->mBlock = $block;
750  $user1->load();
751 
752  // 2. Test the cookie's expiry timestamp.
753  $this->assertTrue( $user1->isLoggedIn() );
754  $this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
755  $this->assertEquals( DatabaseBlock::TYPE_USER, $block->getType() );
756  $this->assertTrue( $block->isAutoblocking() );
757  $this->assertGreaterThanOrEqual( 1, $user1->getBlockId() );
758  $cookies = $request1->response()->getCookies();
759  // Test the cookie's expiry to the nearest minute.
760  $this->assertArrayHasKey( 'wm_infinite_blockBlockID', $cookies );
761  $expOneDay = wfTimestamp() + ( 24 * 60 * 60 );
762  // Check for expiry dates in a 10-second window, to account for slow testing.
763  $this->assertEquals(
764  $expOneDay,
765  $cookies['wm_infinite_blockBlockID']['expire'],
766  'Expiry date',
767  5.0
768  );
769 
770  // 3. Change the block's expiry (to 2 hours), and the cookie's should be changed also.
771  $newExpiry = wfTimestamp() + 2 * 60 * 60;
772  $block->setExpiry( wfTimestamp( TS_MW, $newExpiry ) );
773  $block->update();
774  $user2tmp = $this->getTestUser()->getUser();
775  $request2 = new FauxRequest();
776  $request2->getSession()->setUser( $user2tmp );
777  $user2 = User::newFromSession( $request2 );
778  $user2->mBlock = $block;
779  $user2->load();
780  $cookies = $request2->response()->getCookies();
781  $this->assertEquals( wfTimestamp( TS_MW, $newExpiry ), $block->getExpiry() );
782  $this->assertEquals( $newExpiry, $cookies['wm_infinite_blockBlockID']['expire'] );
783 
784  // Clean up.
785  $block->delete();
786  }
787 
791  public function testSoftBlockRanges() {
792  $this->setMwGlobals( 'wgSoftBlockRanges', [ '10.0.0.0/8' ] );
793 
794  // IP isn't in $wgSoftBlockRanges
795  $wgUser = new User();
796  $request = new FauxRequest();
797  $request->setIP( '192.168.0.1' );
798  $this->setSessionUser( $wgUser, $request );
799  $this->assertNull( $wgUser->getBlock() );
800 
801  // IP is in $wgSoftBlockRanges
802  $wgUser = new User();
803  $request = new FauxRequest();
804  $request->setIP( '10.20.30.40' );
805  $this->setSessionUser( $wgUser, $request );
806  $block = $wgUser->getBlock();
807  $this->assertInstanceOf( SystemBlock::class, $block );
808  $this->assertSame( 'wgSoftBlockRanges', $block->getSystemBlockType() );
809 
810  // Make sure the block is really soft
811  $wgUser = $this->getTestUser()->getUser();
812  $request = new FauxRequest();
813  $request->setIP( '10.20.30.40' );
814  $this->setSessionUser( $wgUser, $request );
815  $this->assertFalse( $wgUser->isAnon(), 'sanity check' );
816  $this->assertNull( $wgUser->getBlock() );
817  }
818 
823  public function testAutoblockCookieInauthentic() {
824  // Set up the bits of global configuration that we use.
825  $this->setMwGlobals( [
826  'wgCookieSetOnAutoblock' => true,
827  'wgCookiePrefix' => 'wmsitetitle',
828  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
829  ] );
830 
831  // Unregister the hooks for proper unit testing
832  $this->mergeMwGlobalArrayValue( 'wgHooks', [
833  'PerformRetroactiveAutoblock' => []
834  ] );
835 
836  // 1. Log in a blocked test user.
837  $user1tmp = $this->getTestUser()->getUser();
838  $request1 = new FauxRequest();
839  $request1->getSession()->setUser( $user1tmp );
840  $block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
841  $block->setBlocker( $this->getTestSysop()->getUser() );
842  $block->setTarget( $user1tmp );
843  $res = $block->insert();
844  $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
845  $user1 = User::newFromSession( $request1 );
846  $user1->mBlock = $block;
847  $user1->load();
848 
849  // 2. Create a new request, set the cookie to an invalid value, and make sure the (anon)
850  // user not blocked.
851  $request2 = new FauxRequest();
852  $request2->setCookie( 'BlockID', $block->getId() . '!zzzzzzz' );
853  $user2 = User::newFromSession( $request2 );
854  $user2->load();
855  $this->assertTrue( $user2->isAnon() );
856  $this->assertFalse( $user2->isLoggedIn() );
857  $this->assertNull( $user2->getBlock() );
858 
859  // Clean up.
860  $block->delete();
861  }
862 
868  public function testAutoblockCookieNoSecretKey() {
869  // Set up the bits of global configuration that we use.
870  $this->setMwGlobals( [
871  'wgCookieSetOnAutoblock' => true,
872  'wgCookiePrefix' => 'wmsitetitle',
873  'wgSecretKey' => null,
874  ] );
875 
876  // Unregister the hooks for proper unit testing
877  $this->mergeMwGlobalArrayValue( 'wgHooks', [
878  'PerformRetroactiveAutoblock' => []
879  ] );
880 
881  // 1. Log in a blocked test user.
882  $user1tmp = $this->getTestUser()->getUser();
883  $request1 = new FauxRequest();
884  $request1->getSession()->setUser( $user1tmp );
885  $block = new DatabaseBlock( [ 'enableAutoblock' => true ] );
886  $block->setBlocker( $this->getTestSysop()->getUser() );
887  $block->setTarget( $user1tmp );
888  $res = $block->insert();
889  $this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
890  $user1 = User::newFromSession( $request1 );
891  $user1->mBlock = $block;
892  $user1->load();
893  $this->assertInstanceOf( DatabaseBlock::class, $user1->getBlock() );
894 
895  // 2. Create a new request, set the cookie to just the block ID, and the user should
896  // still get blocked when they log in again.
897  $request2 = new FauxRequest();
898  $request2->setCookie( 'BlockID', $block->getId() );
899  $user2 = User::newFromSession( $request2 );
900  $user2->load();
901  $this->assertNotEquals( $user1->getId(), $user2->getId() );
902  $this->assertNotEquals( $user1->getToken(), $user2->getToken() );
903  $this->assertTrue( $user2->isAnon() );
904  $this->assertFalse( $user2->isLoggedIn() );
905  $this->assertInstanceOf( DatabaseBlock::class, $user2->getBlock() );
906  $this->assertEquals( true, $user2->getBlock()->isAutoblocking() ); // Non-strict type-check.
907 
908  // Clean up.
909  $block->delete();
910  }
911 
915  public function testIsPingLimitable() {
916  $request = new FauxRequest();
917  $request->setIP( '1.2.3.4' );
919 
920  $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
921  $this->assertTrue( $user->isPingLimitable() );
922 
923  $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.4' ] );
924  $this->assertFalse( $user->isPingLimitable() );
925 
926  $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [ '1.2.3.0/8' ] );
927  $this->assertFalse( $user->isPingLimitable() );
928 
929  $this->setMwGlobals( 'wgRateLimitsExcludedIPs', [] );
930  $noRateLimitUser = $this->getMockBuilder( User::class )->disableOriginalConstructor()
931  ->setMethods( [ 'getIP', 'getRights' ] )->getMock();
932  $noRateLimitUser->expects( $this->any() )->method( 'getIP' )->willReturn( '1.2.3.4' );
933  $noRateLimitUser->expects( $this->any() )->method( 'getRights' )->willReturn( [ 'noratelimit' ] );
934  $this->assertFalse( $noRateLimitUser->isPingLimitable() );
935  }
936 
937  public function provideExperienceLevel() {
938  return [
939  [ 2, 2, 'newcomer' ],
940  [ 12, 3, 'newcomer' ],
941  [ 8, 5, 'newcomer' ],
942  [ 15, 10, 'learner' ],
943  [ 450, 20, 'learner' ],
944  [ 460, 33, 'learner' ],
945  [ 525, 28, 'learner' ],
946  [ 538, 33, 'experienced' ],
947  ];
948  }
949 
954  public function testExperienceLevel( $editCount, $memberSince, $expLevel ) {
955  $this->setMwGlobals( [
956  'wgLearnerEdits' => 10,
957  'wgLearnerMemberSince' => 4,
958  'wgExperiencedUserEdits' => 500,
959  'wgExperiencedUserMemberSince' => 30,
960  ] );
961 
962  $db = wfGetDB( DB_MASTER );
963  $userQuery = User::getQueryInfo();
964  $row = $db->selectRow(
965  $userQuery['tables'],
966  $userQuery['fields'],
967  [ 'user_id' => $this->getTestUser()->getUser()->getId() ],
968  __METHOD__,
969  [],
970  $userQuery['joins']
971  );
972  $row->user_editcount = $editCount;
973  $row->user_registration = $db->timestamp( time() - $memberSince * 86400 );
974  $user = User::newFromRow( $row );
975 
976  $this->assertEquals( $expLevel, $user->getExperienceLevel() );
977  }
978 
982  public function testExperienceLevelAnon() {
983  $user = User::newFromName( '10.11.12.13', false );
984 
985  $this->assertFalse( $user->getExperienceLevel() );
986  }
987 
988  public static function provideIsLocallyBlockedProxy() {
989  return [
990  [ '1.2.3.4', '1.2.3.4' ],
991  [ '1.2.3.4', '1.2.3.0/16' ],
992  ];
993  }
994 
999  public function testIsLocallyBlockedProxy( $ip, $blockListEntry ) {
1000  $this->hideDeprecated( 'User::isLocallyBlockedProxy' );
1001 
1002  $this->setMwGlobals(
1003  'wgProxyList', []
1004  );
1005  $this->assertFalse( User::isLocallyBlockedProxy( $ip ) );
1006 
1007  $this->setMwGlobals(
1008  'wgProxyList',
1009  [
1010  $blockListEntry
1011  ]
1012  );
1013  $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
1014 
1015  $this->setMwGlobals(
1016  'wgProxyList',
1017  [
1018  'test' => $blockListEntry
1019  ]
1020  );
1021  $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
1022 
1023  $this->hideDeprecated(
1024  'IP addresses in the keys of $wgProxyList (found the following IP ' .
1025  'addresses in keys: ' . $blockListEntry . ', please move them to values)'
1026  );
1027  $this->setMwGlobals(
1028  'wgProxyList',
1029  [
1030  $blockListEntry => 'test'
1031  ]
1032  );
1033  $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
1034  }
1035 
1039  public function testActorId() {
1040  $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
1041  $this->hideDeprecated( 'User::selectFields' );
1042 
1043  // Newly-created user has an actor ID
1044  $user = User::createNew( 'UserTestActorId1' );
1045  $id = $user->getId();
1046  $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
1047 
1048  $user = User::newFromName( 'UserTestActorId2' );
1049  $user->addToDatabase();
1050  $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
1051 
1052  $user = User::newFromName( 'UserTestActorId1' );
1053  $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
1054 
1055  $user = User::newFromId( $id );
1056  $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
1057 
1058  $user2 = User::newFromActorId( $user->getActorId() );
1059  $this->assertEquals( $user->getId(), $user2->getId(),
1060  'User::newFromActorId works for an existing user' );
1061 
1062  $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
1063  $user = User::newFromRow( $row );
1064  $this->assertTrue( $user->getActorId() > 0,
1065  'Actor ID can be retrieved for user loaded with User::selectFields()' );
1066 
1067  $user = User::newFromId( $id );
1068  $user->setName( 'UserTestActorId4-renamed' );
1069  $user->saveSettings();
1070  $this->assertEquals(
1071  $user->getName(),
1072  $this->db->selectField(
1073  'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
1074  ),
1075  'User::saveSettings updates actor table for name change'
1076  );
1077 
1078  // For sanity
1079  $ip = '192.168.12.34';
1080  $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
1081 
1082  $user = User::newFromName( $ip, false );
1083  $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
1084  $this->assertTrue( $user->getActorId( $this->db ) > 0,
1085  'Actor ID can be created for an anonymous user' );
1086 
1087  $user = User::newFromName( $ip, false );
1088  $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
1089  $user2 = User::newFromActorId( $user->getActorId() );
1090  $this->assertEquals( $user->getName(), $user2->getName(),
1091  'User::newFromActorId works for an anonymous user' );
1092  }
1093 
1103  public function testActorId_old() {
1104  $this->setMwGlobals( [
1105  'wgActorTableSchemaMigrationStage' => SCHEMA_COMPAT_WRITE_BOTH | SCHEMA_COMPAT_READ_OLD,
1106  ] );
1107  $this->overrideMwServices();
1108 
1109  $domain = MediaWikiServices::getInstance()->getDBLoadBalancer()->getLocalDomainID();
1110  $this->hideDeprecated( 'User::selectFields' );
1111 
1112  // Newly-created user has an actor ID
1113  $user = User::createNew( 'UserTestActorIdOld1' );
1114  $id = $user->getId();
1115  $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
1116 
1117  $user = User::newFromName( 'UserTestActorIdOld2' );
1118  $user->addToDatabase();
1119  $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
1120 
1121  $user = User::newFromName( 'UserTestActorIdOld1' );
1122  $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
1123 
1124  $user = User::newFromId( $id );
1125  $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
1126 
1127  $user2 = User::newFromActorId( $user->getActorId() );
1128  $this->assertEquals( $user->getId(), $user2->getId(),
1129  'User::newFromActorId works for an existing user' );
1130 
1131  $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
1132  $user = User::newFromRow( $row );
1133  $this->assertTrue( $user->getActorId() > 0,
1134  'Actor ID can be retrieved for user loaded with User::selectFields()' );
1135 
1136  $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
1137  User::purge( $domain, $id );
1138  // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
1139  ObjectCache::getMainWANInstance()->clearProcessCache();
1140 
1141  $user = User::newFromId( $id );
1142  $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );
1143  $this->assertTrue( $user->getActorId( $this->db ) > 0, 'Actor ID can be created if none in db' );
1144 
1145  $user->setName( 'UserTestActorIdOld4-renamed' );
1146  $user->saveSettings();
1147  $this->assertEquals(
1148  $user->getName(),
1149  $this->db->selectField(
1150  'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
1151  ),
1152  'User::saveSettings updates actor table for name change'
1153  );
1154 
1155  // For sanity
1156  $ip = '192.168.12.34';
1157  $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
1158 
1159  $user = User::newFromName( $ip, false );
1160  $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
1161  $this->assertTrue( $user->getActorId( $this->db ) > 0,
1162  'Actor ID can be created for an anonymous user' );
1163 
1164  $user = User::newFromName( $ip, false );
1165  $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
1166  $user2 = User::newFromActorId( $user->getActorId() );
1167  $this->assertEquals( $user->getName(), $user2->getName(),
1168  'User::newFromActorId works for an anonymous user' );
1169  }
1170 
1174  public function testNewFromAnyId() {
1175  // Registered user
1176  $user = $this->getTestUser()->getUser();
1177  for ( $i = 1; $i <= 7; $i++ ) {
1178  $test = User::newFromAnyId(
1179  ( $i & 1 ) ? $user->getId() : null,
1180  ( $i & 2 ) ? $user->getName() : null,
1181  ( $i & 4 ) ? $user->getActorId() : null
1182  );
1183  $this->assertSame( $user->getId(), $test->getId() );
1184  $this->assertSame( $user->getName(), $test->getName() );
1185  $this->assertSame( $user->getActorId(), $test->getActorId() );
1186  }
1187 
1188  // Anon user. Can't load by only user ID when that's 0.
1189  $user = User::newFromName( '192.168.12.34', false );
1190  $user->getActorId( $this->db ); // Make sure an actor ID exists
1191 
1192  $test = User::newFromAnyId( null, '192.168.12.34', null );
1193  $this->assertSame( $user->getId(), $test->getId() );
1194  $this->assertSame( $user->getName(), $test->getName() );
1195  $this->assertSame( $user->getActorId(), $test->getActorId() );
1196  $test = User::newFromAnyId( null, null, $user->getActorId() );
1197  $this->assertSame( $user->getId(), $test->getId() );
1198  $this->assertSame( $user->getName(), $test->getName() );
1199  $this->assertSame( $user->getActorId(), $test->getActorId() );
1200 
1201  // Bogus data should still "work" as long as nothing triggers a ->load(),
1202  // and accessing the specified data shouldn't do that.
1203  $test = User::newFromAnyId( 123456, 'Bogus', 654321 );
1204  $this->assertSame( 123456, $test->getId() );
1205  $this->assertSame( 'Bogus', $test->getName() );
1206  $this->assertSame( 654321, $test->getActorId() );
1207 
1208  // Loading remote user by name from remote wiki should succeed
1209  $test = User::newFromAnyId( null, 'Bogus', null, 'foo' );
1210  $this->assertSame( 0, $test->getId() );
1211  $this->assertSame( 'Bogus', $test->getName() );
1212  $this->assertSame( 0, $test->getActorId() );
1213  $test = User::newFromAnyId( 123456, 'Bogus', 654321, 'foo' );
1214  $this->assertSame( 0, $test->getId() );
1215  $this->assertSame( 0, $test->getActorId() );
1216 
1217  // Exceptional cases
1218  try {
1220  $this->fail( 'Expected exception not thrown' );
1221  } catch ( InvalidArgumentException $ex ) {
1222  }
1223  try {
1224  User::newFromAnyId( 0, null, 0 );
1225  $this->fail( 'Expected exception not thrown' );
1226  } catch ( InvalidArgumentException $ex ) {
1227  }
1228 
1229  // Loading remote user by id from remote wiki should fail
1230  try {
1231  User::newFromAnyId( 123456, null, 654321, 'foo' );
1232  $this->fail( 'Expected exception not thrown' );
1233  } catch ( InvalidArgumentException $ex ) {
1234  }
1235  }
1236 
1240  public function testNewFromIdentity() {
1241  // Registered user
1242  $user = $this->getTestUser()->getUser();
1243 
1244  $this->assertSame( $user, User::newFromIdentity( $user ) );
1245 
1246  // ID only
1247  $identity = new UserIdentityValue( $user->getId(), '', 0 );
1248  $result = User::newFromIdentity( $identity );
1249  $this->assertInstanceOf( User::class, $result );
1250  $this->assertSame( $user->getId(), $result->getId(), 'ID' );
1251  $this->assertSame( $user->getName(), $result->getName(), 'Name' );
1252  $this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
1253 
1254  // Name only
1255  $identity = new UserIdentityValue( 0, $user->getName(), 0 );
1256  $result = User::newFromIdentity( $identity );
1257  $this->assertInstanceOf( User::class, $result );
1258  $this->assertSame( $user->getId(), $result->getId(), 'ID' );
1259  $this->assertSame( $user->getName(), $result->getName(), 'Name' );
1260  $this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
1261 
1262  // Actor only
1263  $identity = new UserIdentityValue( 0, '', $user->getActorId() );
1264  $result = User::newFromIdentity( $identity );
1265  $this->assertInstanceOf( User::class, $result );
1266  $this->assertSame( $user->getId(), $result->getId(), 'ID' );
1267  $this->assertSame( $user->getName(), $result->getName(), 'Name' );
1268  $this->assertSame( $user->getActorId(), $result->getActorId(), 'Actor' );
1269  }
1270 
1279  public function testBlockInstanceCache() {
1280  // First, check the user isn't blocked
1281  $user = $this->getMutableTestUser()->getUser();
1283  $this->assertNull( $user->getBlock( false ), 'sanity check' );
1284  $this->assertSame( '', $user->blockedBy(), 'sanity check' );
1285  $this->assertSame( '', $user->blockedFor(), 'sanity check' );
1286  $this->assertFalse( (bool)$user->isHidden(), 'sanity check' );
1287  $this->assertFalse( $user->isBlockedFrom( $ut ), 'sanity check' );
1288 
1289  // Block the user
1290  $blocker = $this->getTestSysop()->getUser();
1291  $block = new DatabaseBlock( [
1292  'hideName' => true,
1293  'allowUsertalk' => false,
1294  'reason' => 'Because',
1295  ] );
1296  $block->setTarget( $user );
1297  $block->setBlocker( $blocker );
1298  $res = $block->insert();
1299  $this->assertTrue( (bool)$res['id'], 'sanity check: Failed to insert block' );
1300 
1301  // Clear cache and confirm it loaded the block properly
1303  $this->assertInstanceOf( DatabaseBlock::class, $user->getBlock( false ) );
1304  $this->assertSame( $blocker->getName(), $user->blockedBy() );
1305  $this->assertSame( 'Because', $user->blockedFor() );
1306  $this->assertTrue( (bool)$user->isHidden() );
1307  $this->assertTrue( $user->isBlockedFrom( $ut ) );
1308 
1309  // Unblock
1310  $block->delete();
1311 
1312  // Clear cache and confirm it loaded the not-blocked properly
1314  $this->assertNull( $user->getBlock( false ) );
1315  $this->assertSame( '', $user->blockedBy() );
1316  $this->assertSame( '', $user->blockedFor() );
1317  $this->assertFalse( (bool)$user->isHidden() );
1318  $this->assertFalse( $user->isBlockedFrom( $ut ) );
1319  }
1320 
1324  public function testCompositeBlocks() {
1325  $user = $this->getMutableTestUser()->getUser();
1326  $request = $user->getRequest();
1327  $this->setSessionUser( $user, $request );
1328 
1329  $ipBlock = new Block( [
1330  'address' => $user->getRequest()->getIP(),
1331  'by' => $this->getTestSysop()->getUser()->getId(),
1332  'createAccount' => true,
1333  ] );
1334  $ipBlock->insert();
1335 
1336  $userBlock = new Block( [
1337  'address' => $user,
1338  'by' => $this->getTestSysop()->getUser()->getId(),
1339  'createAccount' => false,
1340  ] );
1341  $userBlock->insert();
1342 
1343  $block = $user->getBlock();
1344  $this->assertInstanceOf( CompositeBlock::class, $block );
1345  $this->assertTrue( $block->isCreateAccountBlocked() );
1346  $this->assertTrue( $block->appliesToPasswordReset() );
1347  $this->assertTrue( $block->appliesToNamespace( NS_MAIN ) );
1348  }
1349 
1360  public function testIsBlockedFrom( $title, $expect, array $options = [] ) {
1361  $this->setMwGlobals( [
1362  'wgBlockAllowsUTEdit' => $options['blockAllowsUTEdit'] ?? true,
1363  ] );
1364 
1365  $user = $this->getTestUser()->getUser();
1366 
1367  if ( $title === self::USER_TALK_PAGE ) {
1368  $title = $user->getTalkPage();
1369  } else {
1371  }
1372 
1373  $restrictions = [];
1374  foreach ( $options['pageRestrictions'] ?? [] as $pagestr ) {
1375  $page = $this->getExistingTestPage(
1376  $pagestr === self::USER_TALK_PAGE ? $user->getTalkPage() : $pagestr
1377  );
1378  $restrictions[] = new PageRestriction( 0, $page->getId() );
1379  }
1380  foreach ( $options['namespaceRestrictions'] ?? [] as $ns ) {
1381  $restrictions[] = new NamespaceRestriction( 0, $ns );
1382  }
1383 
1384  $block = new DatabaseBlock( [
1385  'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
1386  'allowUsertalk' => $options['allowUsertalk'] ?? false,
1387  'sitewide' => !$restrictions,
1388  ] );
1389  $block->setTarget( $user );
1390  $block->setBlocker( $this->getTestSysop()->getUser() );
1391  if ( $restrictions ) {
1392  $block->setRestrictions( $restrictions );
1393  }
1394  $block->insert();
1395 
1396  try {
1397  $this->assertSame( $expect, $user->isBlockedFrom( $title ) );
1398  } finally {
1399  $block->delete();
1400  }
1401  }
1402 
1403  public static function provideIsBlockedFrom() {
1404  return [
1405  'Sitewide block, basic operation' => [ 'Test page', true ],
1406  'Sitewide block, not allowing user talk' => [
1407  self::USER_TALK_PAGE, true, [
1408  'allowUsertalk' => false,
1409  ]
1410  ],
1411  'Sitewide block, allowing user talk' => [
1412  self::USER_TALK_PAGE, false, [
1413  'allowUsertalk' => true,
1414  ]
1415  ],
1416  'Sitewide block, allowing user talk but $wgBlockAllowsUTEdit is false' => [
1417  self::USER_TALK_PAGE, true, [
1418  'allowUsertalk' => true,
1419  'blockAllowsUTEdit' => false,
1420  ]
1421  ],
1422  'Partial block, blocking the page' => [
1423  'Test page', true, [
1424  'pageRestrictions' => [ 'Test page' ],
1425  ]
1426  ],
1427  'Partial block, not blocking the page' => [
1428  'Test page 2', false, [
1429  'pageRestrictions' => [ 'Test page' ],
1430  ]
1431  ],
1432  'Partial block, not allowing user talk but user talk page is not blocked' => [
1433  self::USER_TALK_PAGE, false, [
1434  'allowUsertalk' => false,
1435  'pageRestrictions' => [ 'Test page' ],
1436  ]
1437  ],
1438  'Partial block, allowing user talk but user talk page is blocked' => [
1439  self::USER_TALK_PAGE, true, [
1440  'allowUsertalk' => true,
1441  'pageRestrictions' => [ self::USER_TALK_PAGE ],
1442  ]
1443  ],
1444  'Partial block, user talk page is not blocked but $wgBlockAllowsUTEdit is false' => [
1445  self::USER_TALK_PAGE, false, [
1446  'allowUsertalk' => false,
1447  'pageRestrictions' => [ 'Test page' ],
1448  'blockAllowsUTEdit' => false,
1449  ]
1450  ],
1451  'Partial block, user talk page is blocked and $wgBlockAllowsUTEdit is false' => [
1452  self::USER_TALK_PAGE, true, [
1453  'allowUsertalk' => true,
1454  'pageRestrictions' => [ self::USER_TALK_PAGE ],
1455  'blockAllowsUTEdit' => false,
1456  ]
1457  ],
1458  'Partial user talk namespace block, not allowing user talk' => [
1459  self::USER_TALK_PAGE, true, [
1460  'allowUsertalk' => false,
1461  'namespaceRestrictions' => [ NS_USER_TALK ],
1462  ]
1463  ],
1464  'Partial user talk namespace block, allowing user talk' => [
1465  self::USER_TALK_PAGE, false, [
1466  'allowUsertalk' => true,
1467  'namespaceRestrictions' => [ NS_USER_TALK ],
1468  ]
1469  ],
1470  'Partial user talk namespace block, where $wgBlockAllowsUTEdit is false' => [
1471  self::USER_TALK_PAGE, true, [
1472  'allowUsertalk' => true,
1473  'namespaceRestrictions' => [ NS_USER_TALK ],
1474  'blockAllowsUTEdit' => false,
1475  ]
1476  ],
1477  ];
1478  }
1479 
1485  public function testIpBlockCookieSet() {
1486  $this->setMwGlobals( [
1487  'wgCookieSetOnIpBlock' => true,
1488  'wgCookiePrefix' => 'wiki',
1489  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
1490  ] );
1491 
1492  // setup block
1493  $block = new DatabaseBlock( [
1494  'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
1495  ] );
1496  $block->setTarget( '1.2.3.4' );
1497  $block->setBlocker( $this->getTestSysop()->getUser() );
1498  $block->insert();
1499 
1500  // setup request
1501  $request = new FauxRequest();
1502  $request->setIP( '1.2.3.4' );
1503 
1504  // get user
1506  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $user );
1507 
1508  // test cookie was set
1509  $cookies = $request->response()->getCookies();
1510  $this->assertArrayHasKey( 'wikiBlockID', $cookies );
1511 
1512  // clean up
1513  $block->delete();
1514  }
1515 
1521  public function testIpBlockCookieNotSet() {
1522  $this->setMwGlobals( [
1523  'wgCookieSetOnIpBlock' => false,
1524  'wgCookiePrefix' => 'wiki',
1525  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
1526  ] );
1527 
1528  // setup block
1529  $block = new DatabaseBlock( [
1530  'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 5 * 60 * 60 ) ),
1531  ] );
1532  $block->setTarget( '1.2.3.4' );
1533  $block->setBlocker( $this->getTestSysop()->getUser() );
1534  $block->insert();
1535 
1536  // setup request
1537  $request = new FauxRequest();
1538  $request->setIP( '1.2.3.4' );
1539 
1540  // get user
1542  MediaWikiServices::getInstance()->getBlockManager()->trackBlockWithCookie( $user );
1543 
1544  // test cookie was not set
1545  $cookies = $request->response()->getCookies();
1546  $this->assertArrayNotHasKey( 'wikiBlockID', $cookies );
1547 
1548  // clean up
1549  $block->delete();
1550  }
1551 
1558  $this->setMwGlobals( [
1559  'wgAutoblockExpiry' => 8000,
1560  'wgCookieSetOnIpBlock' => true,
1561  'wgCookiePrefix' => 'wiki',
1562  'wgSecretKey' => MWCryptRand::generateHex( 64, true ),
1563  ] );
1564 
1565  // setup block
1566  $block = new DatabaseBlock( [
1567  'expiry' => wfTimestamp( TS_MW, wfTimestamp() + ( 40 * 60 * 60 ) ),
1568  ] );
1569  $block->setTarget( '1.2.3.4' );
1570  $block->setBlocker( $this->getTestSysop()->getUser() );
1571  $block->insert();
1572 
1573  // setup request
1574  $request = new FauxRequest();
1575  $request->setIP( '1.2.3.4' );
1576  $request->getSession()->setUser( $this->getTestUser()->getUser() );
1577  $request->setCookie( 'BlockID', $block->getCookieValue() );
1578 
1579  // setup user
1581 
1582  // logged in users should be inmune to cookie block of type ip/range
1583  $this->assertNull( $user->getBlock() );
1584 
1585  // cookie is being cleared
1586  $cookies = $request->response()->getCookies();
1587  $this->assertEquals( '', $cookies['wikiBlockID']['value'] );
1588 
1589  // clean up
1590  $block->delete();
1591  }
1592 
1598  $clock = MWTimestamp::convert( TS_UNIX, '20100101000000' );
1599  MWTimestamp::setFakeTime( function () use ( &$clock ) {
1600  return $clock += 1000;
1601  } );
1602  try {
1603  $user = $this->getTestUser()->getUser();
1604  $firstRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'one', 'test' );
1605  $secondRevision = self::makeEdit( $user, 'Help:UserTest_GetEditTimestamp', 'two', 'test' );
1606  // Sanity check: revisions timestamp are different
1607  $this->assertNotEquals( $firstRevision->getTimestamp(), $secondRevision->getTimestamp() );
1608 
1609  $this->assertEquals( $firstRevision->getTimestamp(), $user->getFirstEditTimestamp() );
1610  $this->assertEquals( $secondRevision->getTimestamp(), $user->getLatestEditTimestamp() );
1611  } finally {
1612  MWTimestamp::setFakeTime( false );
1613  }
1614  }
1615 
1623  private static function makeEdit( User $user, $title, $content, $comment ) {
1625  $content = ContentHandler::makeContent( $content, $page->getTitle() );
1626  $updater = $page->newPageUpdater( $user );
1627  $updater->setContent( 'main', $content );
1628  return $updater->saveRevision( CommentStoreComment::newUnsavedComment( $comment ) );
1629  }
1630 
1634  public function testExistingIdFromName() {
1635  $this->assertTrue(
1636  array_key_exists( $this->user->getName(), User::$idCacheByName ),
1637  'Test user should already be in the id cache.'
1638  );
1639  $this->assertSame(
1640  $this->user->getId(), User::idFromName( $this->user->getName() ),
1641  'Id is correctly retreived from the cache.'
1642  );
1643  $this->assertSame(
1644  $this->user->getId(), User::idFromName( $this->user->getName(), User::READ_LATEST ),
1645  'Id is correctly retreived from the database.'
1646  );
1647  }
1648 
1652  public function testNonExistingIdFromName() {
1653  $this->assertFalse(
1654  array_key_exists( 'NotExisitngUser', User::$idCacheByName ),
1655  'Non exisitng user should not be in the id cache.'
1656  );
1657  $this->assertSame( null, User::idFromName( 'NotExisitngUser' ) );
1658  $this->assertTrue(
1659  array_key_exists( 'NotExisitngUser', User::$idCacheByName ),
1660  'Username will be cached when requested once.'
1661  );
1662  $this->assertSame( null, User::idFromName( 'NotExisitngUser' ) );
1663  }
1664 }
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:138
testGetGroupsWithPermission( $expected, $right)
provideGetGroupsWithPermission User::getGroupsWithPermission
Definition: UserTest.php:171
provideExperienceLevel()
Definition: UserTest.php:937
isHidden()
Check if user account is hidden.
Definition: User.php:2267
testIsIP( $value, $result, $message)
provideIPs User::isIP
Definition: UserTest.php:204
static getMainWANInstance()
Get the main WAN cache object.
setSessionUser(User $user, WebRequest $request)
Definition: UserTest.php:70
testLoggedIn()
User::isRegistered User::isLoggedIn User::isAnon.
Definition: UserTest.php:517
testGetId()
User::getId.
Definition: UserTest.php:507
clearInstanceCache( $reloadFrom=false)
Clear various cached data stored in this object.
Definition: User.php:1708
User $user
Definition: UserTest.php:26
$wgDefaultUserOptions
Settings added to this array will override the default globals for the user preferences used by anony...
const NS_MAIN
Definition: Defines.php:60
testRevokePermissions()
User::getGroupPermissions.
Definition: UserTest.php:99
static getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
Definition: User.php:4888
saveSettings()
Save this user&#39;s settings into the database.
Definition: User.php:4061
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
getFirstEditTimestamp()
Get the timestamp of the first edit.
Definition: User.php:4837
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
testExperienceLevelAnon()
User::getExperienceLevel.
Definition: UserTest.php:982
testNonExistingIdFromName()
User::idFromName.
Definition: UserTest.php:1652
testUserPermissions()
User::getRights.
Definition: UserTest.php:110
getOption( $oname, $defaultOverride=null, $ignoreHidden=false)
Get the user&#39;s current setting for a given option.
Definition: User.php:3028
blockedBy()
If user is blocked, return the name of the user who placed the block.
Definition: User.php:2174
testCheckPasswordValidity()
Test password validity checks.
Definition: UserTest.php:382
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2148
getExperienceLevel()
Compute experienced level based on edit count and registration date.
Definition: User.php:3939
markTestSkippedIfDbType( $type)
Skip the test if using the specified database type.
$value
checkAndSetTouched()
Bump user_touched if it didn&#39;t change since this object was loaded.
Definition: User.php:1670
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:3667
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:923
getSession()
Return the session for this request.
Definition: WebRequest.php:750
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new user object...
Definition: User.php:5457
mergeMwGlobalArrayValue( $name, $values)
Merges the given values into a MW global array variable.
testIsBlockedFrom( $title, $expect, array $options=[])
User::isBlockedFrom provideIsBlockedFrom.
Definition: UserTest.php:1360
getDBTouched()
Get the user_touched timestamp field (time of last DB updates)
Definition: User.php:2727
A helper class for throttling authentication attempts.
$wgRevokePermissions
Permission keys revoked from users in each group.
testIsPingLimitable()
User::isPingLimitable.
Definition: UserTest.php:915
static newFromActorId( $id)
Static factory method for creation from a given actor ID.
Definition: User.php:633
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:3115
testGetEditCountForAnons()
Test User::editCount medium User::getEditCount.
Definition: UserTest.php:298
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
testIpBlockCookieNotSet()
Block cookie should NOT be set when wgCookieSetOnIpBlock is disabled User::trackBlockWithCookie.
Definition: UserTest.php:1521
const DB_MASTER
Definition: defines.php:26
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2311
static selectFields()
Return the list of user fields that should be selected to create a new user object.
Definition: User.php:5431
testIncEditCount()
Test User::editCount medium User::incEditCount.
Definition: UserTest.php:319
static newFromAnyId( $userId, $userName, $actorId, $wikiId=false)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:686
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1980
$wgGroupPermissions
Permission keys given to users in each group.
static findUsersByGroup( $groups, $limit=5000, $after=null)
Return the users who are members of the given group(s).
Definition: User.php:1095
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
static provideGetCanonicalName()
Definition: UserTest.php:461
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:1982
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1233
testIpBlockCookieSet()
Block cookie should be set for IP Blocks if wgCookieSetOnIpBlock is set to true User::trackBlockWithC...
Definition: UserTest.php:1485
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static getTestSysop()
Convenience method for getting an immutable admin test user.
static getMain()
Get the RequestContext object associated with the main request.
static provideIPs()
Definition: UserTest.php:208
const USER_TALK_PAGE
Constant for self::testIsBlockedFrom.
Definition: UserTest.php:21
testCheckAndSetTouched()
User::checkAndSetTouched.
Definition: UserTest.php:538
testActorId_old()
Actor tests with SCHEMA_COMPAT_READ_OLD.
Definition: UserTest.php:1103
isValidPassword( $password)
Is the input a valid password for this user?
Definition: User.php:1165
static getGroupsWithPermission( $role)
Get all the groups who have a given permission.
Definition: User.php:4915
isAnon()
Get whether the user is anonymous.
Definition: User.php:3675
testIpBlockCookieIgnoredWhenUserLoggedIn()
When an ip user is blocked and then they log in, cookie block should be invalid and the cookie remove...
Definition: UserTest.php:1557
static int [] $idCacheByName
Definition: User.php:312
static isLocallyBlockedProxy( $ip)
Check if an IP address is in the local proxy list.
Definition: User.php:1914
static purge( $wikiId, $userId)
Definition: User.php:497
static getMutableTestUser( $groups=[])
Convenience method for getting a mutable test user.
$res
Definition: database.txt:21
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:598
load( $flags=self::READ_NORMAL)
Load the user table data for this object from the source given by mFrom.
Definition: User.php:367
equals(UserIdentity $user)
Checks if two user objects point to the same user.
Definition: User.php:5543
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5164
testBlockInstanceCache()
User::getBlockedStatus User::getBlock User::blockedBy User::blockedFor User::isHidden User::isBlocked...
Definition: UserTest.php:1279
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Wikitext formatted, in the key only.
Definition: distributors.txt:9
getLatestEditTimestamp()
Get the timestamp of the latest edit.
Definition: User.php:4848
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:1982
testFindUsersByGroup()
User::findUsersByGroup.
Definition: UserTest.php:559
static provideUserNames()
Definition: UserTest.php:233
testGetFirstLatestEditTimestamp()
User::getFirstEditTimestamp User::getLatestEditTimestamp.
Definition: UserTest.php:1597
setUpPermissionGlobals()
Definition: UserTest.php:43
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:36
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:780
Value object representing a user&#39;s identity.
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
isBlockedFrom( $title, $fromReplica=false)
Check if user is blocked from editing a particular article.
Definition: User.php:2165
isRegistered()
Alias of isLoggedIn() with a name that describes its actual functionality.
Definition: User.php:3659
static makeEdit(User $user, $title, $content, $comment)
Definition: UserTest.php:1623
testIsLocallyBlockedProxy( $ip, $blockListEntry)
provideIsLocallyBlockedProxy User::isLocallyBlockedProxy
Definition: UserTest.php:999
testAnonOptions()
T39963 Make sure defaults are loaded when setOption is called.
Definition: UserTest.php:367
static provideIsBlockedFrom()
Definition: UserTest.php:1403
overrideMwServices(Config $configOverrides=null, array $services=[])
Stashes the global instance of MediaWikiServices, and installs a new one, allowing test cases to over...
testExperienceLevel( $editCount, $memberSince, $expLevel)
User::getExperienceLevel provideExperienceLevel.
Definition: UserTest.php:954
setMwGlobals( $pairs, $value=null)
Sets a global, maintaining a stashed version of the previous global to be restored in tearDown...
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:661
const SCHEMA_COMPAT_WRITE_BOTH
Definition: Defines.php:284
hideDeprecated( $function)
Don&#39;t throw a warning if $function is deprecated and called later.
static TestUser [] $users
static provideIsLocallyBlockedProxy()
Definition: UserTest.php:988
testNewFromIdentity()
User::newFromIdentity.
Definition: UserTest.php:1240
testGetCanonicalName( $name, $expectedArray)
User::getCanonicalName() provideGetCanonicalName.
Definition: UserTest.php:437
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:589
testSoftBlockRanges()
User::getBlockedStatus.
Definition: UserTest.php:791
getRequest()
Get the WebRequest object to use with this object.
Definition: User.php:3777
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:780
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:618
testExistingIdFromName()
User::idFromName.
Definition: UserTest.php:1634
testAutoblockCookieNoSecretKey()
The BlockID cookie is normally verified with a HMAC, but not if wgSecretKey is not set...
Definition: UserTest.php:868
static getDummySession( $backend=null, $index=-1, $logger=null)
If you need a Session for testing but don&#39;t want to create a backend to construct one...
Definition: TestUtils.php:86
getId()
Get the user&#39;s ID.
Definition: User.php:2284
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getActorId(IDatabase $dbw=null)
Get the user&#39;s actor ID.
Definition: User.php:2350
testAutoblockCookieInauthentic()
Test that a modified BlockID cookie doesn&#39;t actually load the relevant block (T152951).
Definition: UserTest.php:823
static newFromRow( $row, $data=null)
Create a new user object from a user row.
Definition: User.php:790
testGetEditCount()
Test User::editCount medium User::getEditCount.
Definition: UserTest.php:261
$page->newPageUpdater($user) $updater
Definition: pageupdater.txt:63
static newFromSession(WebRequest $request=null)
Create a new user object using data from session.
Definition: User.php:768
getEditCount()
Get the user&#39;s edit count.
Definition: User.php:3553
const SCHEMA_COMPAT_NEW
Definition: Defines.php:287
testUserGetRightsHooks()
User::getRights.
Definition: UserTest.php:121
getBlockId()
If user is blocked, return the ID for the block.
Definition: User.php:2192
Database $db
Primary database.
testIsValidUserName( $username, $result, $message)
provideUserNames User::isValidUserName
Definition: UserTest.php:229
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
testAutoblockCookiesDisabled()
Make sure that no cookie is set to track autoblocked users when $wgCookieSetOnAutoblock is false...
Definition: UserTest.php:681
static provideGetGroupsWithPermission()
Definition: UserTest.php:179
checkPasswordValidity( $password)
Check if this is a valid password for this user.
Definition: User.php:1191
const SCHEMA_COMPAT_READ_OLD
Definition: Defines.php:281
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:727
static createNew( $name, $params=[])
Add a user to the database, return the user object.
Definition: User.php:4174
getExistingTestPage( $title=null)
Returns a WikiPage representing an existing page.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:594
testActorId()
User::newFromActorId.
Definition: UserTest.php:1039
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
$content
Definition: pageupdater.txt:72
const NS_USER_TALK
Definition: Defines.php:63
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4432
isPingLimitable()
Is this user subject to rate limiting?
Definition: User.php:1962
static getTestUser( $groups=[])
Convenience method for getting an immutable test user.
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:2633
testOptions()
Test changing user options.
Definition: UserTest.php:338
testGroupPermissions()
User::getGroupPermissions.
Definition: UserTest.php:82
testNewFromAnyId()
User::newFromAnyId.
Definition: UserTest.php:1174
Database.
Definition: UserTest.php:18
getRights()
Get the permissions this user has.
Definition: User.php:3406
blockedFor()
If user is blocked, return the specified reason for the block.
Definition: User.php:2183
testCompositeBlocks()
User::getBlockedStatus.
Definition: UserTest.php:1324
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319
testEquals()
User::equals.
Definition: UserTest.php:483