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