Merge "API: Avoid duplicate IDs in API documentation"
[lhc/web/wiklou.git] / tests / phpunit / includes / auth / ThrottlerTest.php
1 <?php
2
3 namespace MediaWiki\Auth;
4
5 use BagOStuff;
6 use HashBagOStuff;
7 use InvalidArgumentException;
8 use Psr\Log\AbstractLogger;
9 use Psr\Log\LoggerInterface;
10 use Psr\Log\NullLogger;
11
12 /**
13 * @group AuthManager
14 * @covers MediaWiki\Auth\Throttler
15 */
16 class ThrottlerTest extends \MediaWikiTestCase {
17 protected function setUp() {
18 global $wgDisableAuthManager;
19
20 parent::setUp();
21 if ( $wgDisableAuthManager ) {
22 $this->markTestSkipped( '$wgDisableAuthManager is set' );
23 }
24 }
25
26 public function testConstructor() {
27 $cache = new \HashBagOStuff();
28 $logger = $this->getMockBuilder( AbstractLogger::class )
29 ->setMethods( [ 'log' ] )
30 ->getMockForAbstractClass();
31
32 $throttler = new Throttler(
33 [ [ 'count' => 123, 'seconds' => 456 ] ],
34 [ 'type' => 'foo', 'cache' => $cache ]
35 );
36 $throttler->setLogger( $logger );
37 $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
38 $this->assertSame( [ [ 'count' => 123, 'seconds' => 456 ] ], $throttlerPriv->conditions );
39 $this->assertSame( 'foo', $throttlerPriv->type );
40 $this->assertSame( $cache, $throttlerPriv->cache );
41 $this->assertSame( $logger, $throttlerPriv->logger );
42
43 $throttler = new Throttler( [ [ 'count' => 123, 'seconds' => 456 ] ] );
44 $throttler->setLogger( new NullLogger() );
45 $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
46 $this->assertSame( [ [ 'count' => 123, 'seconds' => 456 ] ], $throttlerPriv->conditions );
47 $this->assertSame( 'custom', $throttlerPriv->type );
48 $this->assertInstanceOf( BagOStuff::class, $throttlerPriv->cache );
49 $this->assertInstanceOf( LoggerInterface::class, $throttlerPriv->logger );
50
51 $this->setMwGlobals( [ 'wgPasswordAttemptThrottle' => [ [ 'count' => 321,
52 'seconds' => 654 ] ] ] );
53 $throttler = new Throttler();
54 $throttler->setLogger( new NullLogger() );
55 $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
56 $this->assertSame( [ [ 'count' => 321, 'seconds' => 654 ] ], $throttlerPriv->conditions );
57 $this->assertSame( 'password', $throttlerPriv->type );
58 $this->assertInstanceOf( BagOStuff::class, $throttlerPriv->cache );
59 $this->assertInstanceOf( LoggerInterface::class, $throttlerPriv->logger );
60
61 try {
62 new Throttler( [], [ 'foo' => 1, 'bar' => 2, 'baz' => 3 ] );
63 $this->fail( 'Expected exception not thrown' );
64 } catch ( \InvalidArgumentException $ex ) {
65 $this->assertSame( 'unrecognized parameters: foo, bar, baz', $ex->getMessage() );
66 }
67 }
68
69 /**
70 * @dataProvider provideNormalizeThrottleConditions
71 */
72 public function testNormalizeThrottleConditions( $condition, $normalized ) {
73 $throttler = new Throttler( $condition );
74 $throttler->setLogger( new NullLogger() );
75 $throttlerPriv = \TestingAccessWrapper::newFromObject( $throttler );
76 $this->assertSame( $normalized, $throttlerPriv->conditions );
77 }
78
79 public function provideNormalizeThrottleConditions() {
80 return [
81 [
82 [],
83 [],
84 ],
85 [
86 [ 'count' => 1, 'seconds' => 2 ],
87 [ [ 'count' => 1, 'seconds' => 2 ] ],
88 ],
89 [
90 [ [ 'count' => 1, 'seconds' => 2 ], [ 'count' => 2, 'seconds' => 3 ] ],
91 [ [ 'count' => 1, 'seconds' => 2 ], [ 'count' => 2, 'seconds' => 3 ] ],
92 ],
93 ];
94 }
95
96 public function testNormalizeThrottleConditions2() {
97 $priv = \TestingAccessWrapper::newFromClass( Throttler::class );
98 $this->assertSame( [], $priv->normalizeThrottleConditions( null ) );
99 $this->assertSame( [], $priv->normalizeThrottleConditions( 'bad' ) );
100 }
101
102 public function testIncrease() {
103 $cache = new \HashBagOStuff();
104 $throttler = new Throttler( [
105 [ 'count' => 2, 'seconds' => 10, ],
106 [ 'count' => 4, 'seconds' => 15, 'allIPs' => true ],
107 ], [ 'cache' => $cache ] );
108 $throttler->setLogger( new NullLogger() );
109
110 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
111 $this->assertFalse( $result, 'should not throttle' );
112
113 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
114 $this->assertFalse( $result, 'should not throttle' );
115
116 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
117 $this->assertSame( [ 'throttleIndex' => 0, 'count' => 2, 'wait' => 10 ], $result );
118
119 $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
120 $this->assertFalse( $result, 'should not throttle' );
121
122 $result = $throttler->increase( 'SomeUser', '2.3.4.5' );
123 $this->assertFalse( $result, 'should not throttle' );
124
125 $result = $throttler->increase( 'SomeUser', '3.4.5.6' );
126 $this->assertFalse( $result, 'should not throttle' );
127
128 $result = $throttler->increase( 'SomeUser', '3.4.5.6' );
129 $this->assertSame( [ 'throttleIndex' => 1, 'count' => 4, 'wait' => 15 ], $result );
130 }
131
132 public function testZeroCount() {
133 $cache = new \HashBagOStuff();
134 $throttler = new Throttler( [ [ 'count' => 0, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
135 $throttler->setLogger( new NullLogger() );
136
137 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
138 $this->assertFalse( $result, 'should not throttle, count=0 is ignored' );
139
140 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
141 $this->assertFalse( $result, 'should not throttle, count=0 is ignored' );
142
143 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
144 $this->assertFalse( $result, 'should not throttle, count=0 is ignored' );
145 }
146
147 public function testNamespacing() {
148 $cache = new \HashBagOStuff();
149 $throttler1 = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ],
150 [ 'cache' => $cache, 'type' => 'foo' ] );
151 $throttler2 = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ],
152 [ 'cache' => $cache, 'type' => 'foo' ] );
153 $throttler3 = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ],
154 [ 'cache' => $cache, 'type' => 'bar' ] );
155 $throttler1->setLogger( new NullLogger() );
156 $throttler2->setLogger( new NullLogger() );
157 $throttler3->setLogger( new NullLogger() );
158
159 $throttled = [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ];
160
161 $result = $throttler1->increase( 'SomeUser', '1.2.3.4' );
162 $this->assertFalse( $result, 'should not throttle' );
163
164 $result = $throttler1->increase( 'SomeUser', '1.2.3.4' );
165 $this->assertEquals( $throttled, $result, 'should throttle' );
166
167 $result = $throttler2->increase( 'SomeUser', '1.2.3.4' );
168 $this->assertEquals( $throttled, $result, 'should throttle, same namespace' );
169
170 $result = $throttler3->increase( 'SomeUser', '1.2.3.4' );
171 $this->assertFalse( $result, 'should not throttle, different namespace' );
172 }
173
174 public function testExpiration() {
175 $cache = $this->getMock( HashBagOStuff::class, [ 'add' ] );
176 $throttler = new Throttler( [ [ 'count' => 3, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
177 $throttler->setLogger( new NullLogger() );
178
179 $cache->expects( $this->once() )->method( 'add' )->with( $this->anything(), 1, 10 );
180 $throttler->increase( 'SomeUser' );
181 }
182
183 /**
184 * @expectedException \InvalidArgumentException
185 */
186 public function testException() {
187 $throttler = new Throttler( [ [ 'count' => 3, 'seconds' => 10 ] ] );
188 $throttler->setLogger( new NullLogger() );
189 $throttler->increase();
190 }
191
192 public function testLog() {
193 $cache = new \HashBagOStuff();
194 $throttler = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
195
196 $logger = $this->getMockBuilder( AbstractLogger::class )
197 ->setMethods( [ 'log' ] )
198 ->getMockForAbstractClass();
199 $logger->expects( $this->never() )->method( 'log' );
200 $throttler->setLogger( $logger );
201 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
202 $this->assertFalse( $result, 'should not throttle' );
203
204 $logger = $this->getMockBuilder( AbstractLogger::class )
205 ->setMethods( [ 'log' ] )
206 ->getMockForAbstractClass();
207 $logger->expects( $this->once() )->method( 'log' )->with( $this->anything(), $this->anything(), [
208 'type' => 'custom',
209 'index' => 0,
210 'ip' => '1.2.3.4',
211 'username' => 'SomeUser',
212 'count' => 1,
213 'expiry' => 10,
214 'method' => 'foo',
215 ] );
216 $throttler->setLogger( $logger );
217 $result = $throttler->increase( 'SomeUser', '1.2.3.4', 'foo' );
218 $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
219 }
220
221 public function testClear() {
222 $cache = new \HashBagOStuff();
223 $throttler = new Throttler( [ [ 'count' => 1, 'seconds' => 10 ] ], [ 'cache' => $cache ] );
224 $throttler->setLogger( new NullLogger() );
225
226 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
227 $this->assertFalse( $result, 'should not throttle' );
228
229 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
230 $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
231
232 $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
233 $this->assertFalse( $result, 'should not throttle' );
234
235 $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
236 $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
237
238 $throttler->clear( 'SomeUser', '1.2.3.4' );
239
240 $result = $throttler->increase( 'SomeUser', '1.2.3.4' );
241 $this->assertFalse( $result, 'should not throttle' );
242
243 $result = $throttler->increase( 'OtherUser', '1.2.3.4' );
244 $this->assertSame( [ 'throttleIndex' => 0, 'count' => 1, 'wait' => 10 ], $result );
245 }
246 }