Add block restriction classes
[lhc/web/wiklou.git] / tests / phpunit / includes / BlockTest.php
1 <?php
2
3 use MediaWiki\Block\Restriction\PageRestriction;
4
5 /**
6 * @group Database
7 * @group Blocking
8 */
9 class BlockTest extends MediaWikiLangTestCase {
10
11 /**
12 * @return User
13 */
14 private function getUserForBlocking() {
15 $testUser = $this->getMutableTestUser();
16 $user = $testUser->getUser();
17 $user->addToDatabase();
18 TestUser::setPasswordForUser( $user, 'UTBlockeePassword' );
19 $user->saveSettings();
20 return $user;
21 }
22
23 /**
24 * @param User $user
25 *
26 * @return Block
27 * @throws MWException
28 */
29 private function addBlockForUser( User $user ) {
30 // Delete the last round's block if it's still there
31 $oldBlock = Block::newFromTarget( $user->getName() );
32 if ( $oldBlock ) {
33 // An old block will prevent our new one from saving.
34 $oldBlock->delete();
35 }
36
37 $blockOptions = [
38 'address' => $user->getName(),
39 'user' => $user->getId(),
40 'by' => $this->getTestSysop()->getUser()->getId(),
41 'reason' => 'Parce que',
42 'expiry' => time() + 100500,
43 ];
44 $block = new Block( $blockOptions );
45
46 $block->insert();
47 // save up ID for use in assertion. Since ID is an autoincrement,
48 // its value might change depending on the order the tests are run.
49 // ApiBlockTest insert its own blocks!
50 if ( !$block->getId() ) {
51 throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
52 }
53
54 $this->addXffBlocks();
55
56 return $block;
57 }
58
59 /**
60 * @covers Block::newFromTarget
61 */
62 public function testINewFromTargetReturnsCorrectBlock() {
63 $user = $this->getUserForBlocking();
64 $block = $this->addBlockForUser( $user );
65
66 $this->assertTrue(
67 $block->equals( Block::newFromTarget( $user->getName() ) ),
68 "newFromTarget() returns the same block as the one that was made"
69 );
70 }
71
72 /**
73 * @covers Block::newFromID
74 */
75 public function testINewFromIDReturnsCorrectBlock() {
76 $user = $this->getUserForBlocking();
77 $block = $this->addBlockForUser( $user );
78
79 $this->assertTrue(
80 $block->equals( Block::newFromID( $block->getId() ) ),
81 "newFromID() returns the same block as the one that was made"
82 );
83 }
84
85 /**
86 * per T28425
87 * @covers Block::__construct
88 */
89 public function testT28425BlockTimestampDefaultsToTime() {
90 $user = $this->getUserForBlocking();
91 $block = $this->addBlockForUser( $user );
92 $madeAt = wfTimestamp( TS_MW );
93
94 // delta to stop one-off errors when things happen to go over a second mark.
95 $delta = abs( $madeAt - $block->mTimestamp );
96 $this->assertLessThan(
97 2,
98 $delta,
99 "If no timestamp is specified, the block is recorded as time()"
100 );
101 }
102
103 /**
104 * CheckUser since being changed to use Block::newFromTarget started failing
105 * because the new function didn't accept empty strings like Block::load()
106 * had. Regression T31116.
107 *
108 * @dataProvider provideT31116Data
109 * @covers Block::newFromTarget
110 */
111 public function testT31116NewFromTargetWithEmptyIp( $vagueTarget ) {
112 $user = $this->getUserForBlocking();
113 $initialBlock = $this->addBlockForUser( $user );
114 $block = Block::newFromTarget( $user->getName(), $vagueTarget );
115
116 $this->assertTrue(
117 $initialBlock->equals( $block ),
118 "newFromTarget() returns the same block as the one that was made when "
119 . "given empty vagueTarget param " . var_export( $vagueTarget, true )
120 );
121 }
122
123 public static function provideT31116Data() {
124 return [
125 [ null ],
126 [ '' ],
127 [ false ]
128 ];
129 }
130
131 /**
132 * @covers Block::prevents
133 */
134 public function testBlockedUserCanNotCreateAccount() {
135 $username = 'BlockedUserToCreateAccountWith';
136 $u = User::newFromName( $username );
137 $u->addToDatabase();
138 $userId = $u->getId();
139 $this->assertNotEquals( 0, $userId, 'sanity' );
140 TestUser::setPasswordForUser( $u, 'NotRandomPass' );
141 unset( $u );
142
143 // Sanity check
144 $this->assertNull(
145 Block::newFromTarget( $username ),
146 "$username should not be blocked"
147 );
148
149 // Reload user
150 $u = User::newFromName( $username );
151 $this->assertFalse(
152 $u->isBlockedFromCreateAccount(),
153 "Our sandbox user should be able to create account before being blocked"
154 );
155
156 // Foreign perspective (blockee not on current wiki)...
157 $blockOptions = [
158 'address' => $username,
159 'user' => $userId,
160 'reason' => 'crosswiki block...',
161 'timestamp' => wfTimestampNow(),
162 'expiry' => $this->db->getInfinity(),
163 'createAccount' => true,
164 'enableAutoblock' => true,
165 'hideName' => true,
166 'blockEmail' => true,
167 'byText' => 'm>MetaWikiUser',
168 ];
169 $block = new Block( $blockOptions );
170 $block->insert();
171
172 // Reload block from DB
173 $userBlock = Block::newFromTarget( $username );
174 $this->assertTrue(
175 (bool)$block->prevents( 'createaccount' ),
176 "Block object in DB should prevents 'createaccount'"
177 );
178
179 $this->assertInstanceOf(
180 Block::class,
181 $userBlock,
182 "'$username' block block object should be existent"
183 );
184
185 // Reload user
186 $u = User::newFromName( $username );
187 $this->assertTrue(
188 (bool)$u->isBlockedFromCreateAccount(),
189 "Our sandbox user '$username' should NOT be able to create account"
190 );
191 }
192
193 /**
194 * @covers Block::insert
195 */
196 public function testCrappyCrossWikiBlocks() {
197 // Delete the last round's block if it's still there
198 $oldBlock = Block::newFromTarget( 'UserOnForeignWiki' );
199 if ( $oldBlock ) {
200 // An old block will prevent our new one from saving.
201 $oldBlock->delete();
202 }
203
204 // Local perspective (blockee on current wiki)...
205 $user = User::newFromName( 'UserOnForeignWiki' );
206 $user->addToDatabase();
207 $userId = $user->getId();
208 $this->assertNotEquals( 0, $userId, 'sanity' );
209
210 // Foreign perspective (blockee not on current wiki)...
211 $blockOptions = [
212 'address' => 'UserOnForeignWiki',
213 'user' => $user->getId(),
214 'reason' => 'crosswiki block...',
215 'timestamp' => wfTimestampNow(),
216 'expiry' => $this->db->getInfinity(),
217 'createAccount' => true,
218 'enableAutoblock' => true,
219 'hideName' => true,
220 'blockEmail' => true,
221 'byText' => 'Meta>MetaWikiUser',
222 ];
223 $block = new Block( $blockOptions );
224
225 $res = $block->insert( $this->db );
226 $this->assertTrue( (bool)$res['id'], 'Block succeeded' );
227
228 $user = null; // clear
229
230 $block = Block::newFromID( $res['id'] );
231 $this->assertEquals(
232 'UserOnForeignWiki',
233 $block->getTarget()->getName(),
234 'Correct blockee name'
235 );
236 $this->assertEquals( $userId, $block->getTarget()->getId(), 'Correct blockee id' );
237 $this->assertEquals( 'Meta>MetaWikiUser', $block->getBlocker()->getName(),
238 'Correct blocker name' );
239 $this->assertEquals( 'Meta>MetaWikiUser', $block->getByName(), 'Correct blocker name' );
240 $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' );
241 }
242
243 protected function addXffBlocks() {
244 static $inited = false;
245
246 if ( $inited ) {
247 return;
248 }
249
250 $inited = true;
251
252 $blockList = [
253 [ 'target' => '70.2.0.0/16',
254 'type' => Block::TYPE_RANGE,
255 'desc' => 'Range Hardblock',
256 'ACDisable' => false,
257 'isHardblock' => true,
258 'isAutoBlocking' => false,
259 ],
260 [ 'target' => '2001:4860:4001::/48',
261 'type' => Block::TYPE_RANGE,
262 'desc' => 'Range6 Hardblock',
263 'ACDisable' => false,
264 'isHardblock' => true,
265 'isAutoBlocking' => false,
266 ],
267 [ 'target' => '60.2.0.0/16',
268 'type' => Block::TYPE_RANGE,
269 'desc' => 'Range Softblock with AC Disabled',
270 'ACDisable' => true,
271 'isHardblock' => false,
272 'isAutoBlocking' => false,
273 ],
274 [ 'target' => '50.2.0.0/16',
275 'type' => Block::TYPE_RANGE,
276 'desc' => 'Range Softblock',
277 'ACDisable' => false,
278 'isHardblock' => false,
279 'isAutoBlocking' => false,
280 ],
281 [ 'target' => '50.1.1.1',
282 'type' => Block::TYPE_IP,
283 'desc' => 'Exact Softblock',
284 'ACDisable' => false,
285 'isHardblock' => false,
286 'isAutoBlocking' => false,
287 ],
288 ];
289
290 $blocker = $this->getTestUser()->getUser();
291 foreach ( $blockList as $insBlock ) {
292 $target = $insBlock['target'];
293
294 if ( $insBlock['type'] === Block::TYPE_IP ) {
295 $target = User::newFromName( IP::sanitizeIP( $target ), false )->getName();
296 } elseif ( $insBlock['type'] === Block::TYPE_RANGE ) {
297 $target = IP::sanitizeRange( $target );
298 }
299
300 $block = new Block();
301 $block->setTarget( $target );
302 $block->setBlocker( $blocker );
303 $block->mReason = $insBlock['desc'];
304 $block->mExpiry = 'infinity';
305 $block->prevents( 'createaccount', $insBlock['ACDisable'] );
306 $block->isHardblock( $insBlock['isHardblock'] );
307 $block->isAutoblocking( $insBlock['isAutoBlocking'] );
308 $block->insert();
309 }
310 }
311
312 public static function providerXff() {
313 return [
314 [ 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
315 'count' => 2,
316 'result' => 'Range Hardblock'
317 ],
318 [ 'xff' => '1.2.3.4, 50.2.1.1, 60.2.1.1, 2.3.4.5',
319 'count' => 2,
320 'result' => 'Range Softblock with AC Disabled'
321 ],
322 [ 'xff' => '1.2.3.4, 70.2.1.1, 50.1.1.1, 2.3.4.5',
323 'count' => 2,
324 'result' => 'Exact Softblock'
325 ],
326 [ 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 50.1.1.1, 2.3.4.5',
327 'count' => 3,
328 'result' => 'Exact Softblock'
329 ],
330 [ 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 2.3.4.5',
331 'count' => 2,
332 'result' => 'Range Hardblock'
333 ],
334 [ 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
335 'count' => 2,
336 'result' => 'Range Hardblock'
337 ],
338 [ 'xff' => '50.2.1.1, 60.2.1.1, 2.3.4.5',
339 'count' => 2,
340 'result' => 'Range Softblock with AC Disabled'
341 ],
342 [ 'xff' => '1.2.3.4, 50.1.1.1, 60.2.1.1, 2.3.4.5',
343 'count' => 2,
344 'result' => 'Exact Softblock'
345 ],
346 [ 'xff' => '1.2.3.4, <$A_BUNCH-OF{INVALID}TEXT\>, 60.2.1.1, 2.3.4.5',
347 'count' => 1,
348 'result' => 'Range Softblock with AC Disabled'
349 ],
350 [ 'xff' => '1.2.3.4, 50.2.1.1, 2001:4860:4001:802::1003, 2.3.4.5',
351 'count' => 2,
352 'result' => 'Range6 Hardblock'
353 ],
354 ];
355 }
356
357 /**
358 * @dataProvider providerXff
359 * @covers Block::getBlocksForIPList
360 * @covers Block::chooseBlock
361 */
362 public function testBlocksOnXff( $xff, $exCount, $exResult ) {
363 $user = $this->getUserForBlocking();
364 $this->addBlockForUser( $user );
365
366 $list = array_map( 'trim', explode( ',', $xff ) );
367 $xffblocks = Block::getBlocksForIPList( $list, true );
368 $this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff );
369 $block = Block::chooseBlock( $xffblocks, $list );
370 $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff );
371 }
372
373 /**
374 * @covers Block::__construct
375 */
376 public function testDeprecatedConstructor() {
377 $this->hideDeprecated( 'Block::__construct with multiple arguments' );
378 $username = 'UnthinkablySecretRandomUsername';
379 $reason = 'being irrational';
380
381 # Set up the target
382 $u = User::newFromName( $username );
383 if ( $u->getId() == 0 ) {
384 $u->addToDatabase();
385 TestUser::setPasswordForUser( $u, 'TotallyObvious' );
386 }
387 unset( $u );
388
389 # Make sure the user isn't blocked
390 $this->assertNull(
391 Block::newFromTarget( $username ),
392 "$username should not be blocked"
393 );
394
395 # Perform the block
396 $block = new Block(
397 /* address */ $username,
398 /* user */ 0,
399 /* by */ $this->getTestSysop()->getUser()->getId(),
400 /* reason */ $reason,
401 /* timestamp */ 0,
402 /* auto */ false,
403 /* expiry */ 0
404 );
405 $block->insert();
406
407 # Check target
408 $this->assertEquals(
409 $block->getTarget()->getName(),
410 $username,
411 "Target should be set properly"
412 );
413
414 # Check supplied parameter
415 $this->assertEquals(
416 $block->mReason,
417 $reason,
418 "Reason should be non-default"
419 );
420
421 # Check default parameter
422 $this->assertFalse(
423 (bool)$block->prevents( 'createaccount' ),
424 "Account creation should not be blocked by default"
425 );
426 }
427
428 /**
429 * @covers Block::getSystemBlockType
430 * @covers Block::insert
431 * @covers Block::doAutoblock
432 */
433 public function testSystemBlocks() {
434 $user = $this->getUserForBlocking();
435 $this->addBlockForUser( $user );
436
437 $blockOptions = [
438 'address' => $user->getName(),
439 'reason' => 'test system block',
440 'timestamp' => wfTimestampNow(),
441 'expiry' => $this->db->getInfinity(),
442 'byText' => 'MediaWiki default',
443 'systemBlock' => 'test',
444 'enableAutoblock' => true,
445 ];
446 $block = new Block( $blockOptions );
447
448 $this->assertSame( 'test', $block->getSystemBlockType() );
449
450 try {
451 $block->insert();
452 $this->fail( 'Expected exception not thrown' );
453 } catch ( MWException $ex ) {
454 $this->assertSame( 'Cannot insert a system block into the database', $ex->getMessage() );
455 }
456
457 try {
458 $block->doAutoblock( '192.0.2.2' );
459 $this->fail( 'Expected exception not thrown' );
460 } catch ( MWException $ex ) {
461 $this->assertSame( 'Cannot autoblock from a system block', $ex->getMessage() );
462 }
463 }
464
465 /**
466 * @covers Block::newFromRow
467 */
468 public function testNewFromRow() {
469 $badActor = $this->getTestUser()->getUser();
470 $sysop = $this->getTestSysop()->getUser();
471
472 $block = new Block( [
473 'address' => $badActor->getName(),
474 'user' => $badActor->getId(),
475 'by' => $sysop->getId(),
476 'expiry' => 'infinity',
477 ] );
478 $block->insert();
479
480 $blockQuery = Block::getQueryInfo();
481 $row = $this->db->select(
482 $blockQuery['tables'],
483 $blockQuery['fields'],
484 [
485 'ipb_id' => $block->getId(),
486 ],
487 __METHOD__,
488 [],
489 $blockQuery['joins']
490 )->fetchObject();
491
492 $block = Block::newFromRow( $row );
493 $this->assertInstanceOf( Block::class, $block );
494 $this->assertEquals( $block->getBy(), $sysop->getId() );
495 $this->assertEquals( $block->getTarget()->getName(), $badActor->getName() );
496 $block->delete();
497 }
498
499 /**
500 * @covers Block::equals
501 */
502 public function testEquals() {
503 $block = new Block();
504
505 $this->assertTrue( $block->equals( $block ) );
506
507 $partial = new Block( [
508 'sitewide' => false,
509 ] );
510 $this->assertFalse( $block->equals( $partial ) );
511 }
512
513 /**
514 * @covers Block::isSitewide
515 */
516 public function testIsSitewide() {
517 $block = new Block();
518 $this->assertTrue( $block->isSitewide() );
519
520 $block = new Block( [
521 'sitewide' => true,
522 ] );
523 $this->assertTrue( $block->isSitewide() );
524
525 $block = new Block( [
526 'sitewide' => false,
527 ] );
528 $this->assertFalse( $block->isSitewide() );
529
530 $block = new Block( [
531 'sitewide' => false,
532 ] );
533 $block->isSitewide( true );
534 $this->assertTrue( $block->isSitewide() );
535 }
536
537 /**
538 * @covers Block::getRestrictions
539 * @covers Block::setRestrictions
540 */
541 public function testRestrictions() {
542 $block = new Block();
543 $restrictions = [
544 new PageRestriction( 0, 1 )
545 ];
546 $block->setRestrictions( $restrictions );
547
548 $this->assertSame( $restrictions, $block->getRestrictions() );
549 }
550
551 /**
552 * @covers Block::getRestrictions
553 * @covers Block::insert
554 */
555 public function testRestrictionsFromDatabase() {
556 $badActor = $this->getTestUser()->getUser();
557 $sysop = $this->getTestSysop()->getUser();
558
559 $block = new Block( [
560 'address' => $badActor->getName(),
561 'user' => $badActor->getId(),
562 'by' => $sysop->getId(),
563 'expiry' => 'infinity',
564 ] );
565 $page = $this->getExistingTestPage( 'Foo' );
566 $restriction = new PageRestriction( 0, $page->getId() );
567 $block->setRestrictions( [ $restriction ] );
568 $block->insert();
569
570 // Refresh the block from the database.
571 $block = Block::newFromID( $block->getId() );
572 $restrictions = $block->getRestrictions();
573 $this->assertCount( 1, $restrictions );
574 $this->assertTrue( $restriction->equals( $restrictions[0] ) );
575 $block->delete();
576 }
577
578 /**
579 * @covers Block::insert
580 */
581 public function testInsertExistingBlock() {
582 $badActor = $this->getTestUser()->getUser();
583 $sysop = $this->getTestSysop()->getUser();
584
585 $block = new Block( [
586 'address' => $badActor->getName(),
587 'user' => $badActor->getId(),
588 'by' => $sysop->getId(),
589 'expiry' => 'infinity',
590 ] );
591 $page = $this->getExistingTestPage( 'Foo' );
592 $restriction = new PageRestriction( 0, $page->getId() );
593 $block->setRestrictions( [ $restriction ] );
594 $block->insert();
595
596 // Insert the block again, which should result in a failur
597 $result = $block->insert();
598
599 $this->assertFalse( $result );
600
601 // Ensure that there are no restrictions where the blockId is 0.
602 $count = $this->db->selectRowCount(
603 'ipblocks_restrictions',
604 '*',
605 [ 'ir_ipb_id' => 0 ],
606 __METHOD__
607 );
608 $this->assertSame( 0, $count );
609
610 $block->delete();
611 }
612
613 }