]>
Commit | Line | Data |
---|---|---|
1 | <?php | |
2 | ||
3 | ||
4 | namespace Shaarli\Security; | |
5 | ||
6 | use PHPUnit\Framework\TestCase; | |
7 | use Shaarli\FileUtils; | |
8 | ||
9 | /** | |
10 | * Test coverage for BanManager | |
11 | */ | |
12 | class BanManagerTest extends TestCase | |
13 | { | |
14 | /** @var BanManager Ban Manager instance */ | |
15 | protected $banManager; | |
16 | ||
17 | /** @var string Banned IP filename */ | |
18 | protected $banFile = 'sandbox/ipbans.php'; | |
19 | ||
20 | /** @var string Log filename */ | |
21 | protected $logFile = 'sandbox/shaarli.log'; | |
22 | ||
23 | /** @var string Local client IP address */ | |
24 | protected $ipAddr = '127.0.0.1'; | |
25 | ||
26 | /** @var string Trusted proxy IP address */ | |
27 | protected $trustedProxy = '10.1.1.100'; | |
28 | ||
29 | /** @var array Simulates the $_SERVER array */ | |
30 | protected $server = []; | |
31 | ||
32 | /** | |
33 | * Prepare or reset test resources | |
34 | */ | |
35 | public function setUp() | |
36 | { | |
37 | if (file_exists($this->banFile)) { | |
38 | unlink($this->banFile); | |
39 | } | |
40 | ||
41 | $this->banManager = $this->getNewBanManagerInstance(); | |
42 | $this->server['REMOTE_ADDR'] = $this->ipAddr; | |
43 | } | |
44 | ||
45 | /** | |
46 | * Test constructor with initial file. | |
47 | */ | |
48 | public function testInstantiateFromFile() | |
49 | { | |
50 | $time = time() + 10; | |
51 | FileUtils::writeFlatDB( | |
52 | $this->banFile, | |
53 | [ | |
54 | 'failures' => [ | |
55 | $this->ipAddr => 2, | |
56 | $ip = '1.2.3.4' => 1, | |
57 | ], | |
58 | 'bans' => [ | |
59 | $ip2 = '8.8.8.8' => $time, | |
60 | $ip3 = '1.1.1.1' => $time + 1, | |
61 | ], | |
62 | ] | |
63 | ); | |
64 | $this->banManager = $this->getNewBanManagerInstance(); | |
65 | ||
66 | $this->assertCount(2, $this->banManager->getFailures()); | |
67 | $this->assertEquals(2, $this->banManager->getFailures()[$this->ipAddr]); | |
68 | $this->assertEquals(1, $this->banManager->getFailures()[$ip]); | |
69 | $this->assertCount(2, $this->banManager->getBans()); | |
70 | $this->assertEquals($time, $this->banManager->getBans()[$ip2]); | |
71 | $this->assertEquals($time + 1, $this->banManager->getBans()[$ip3]); | |
72 | } | |
73 | ||
74 | /** | |
75 | * Test constructor with initial file with invalid values | |
76 | */ | |
77 | public function testInstantiateFromCrappyFile() | |
78 | { | |
79 | FileUtils::writeFlatDB($this->banFile, 'plop'); | |
80 | $this->banManager = $this->getNewBanManagerInstance(); | |
81 | ||
82 | $this->assertEquals([], $this->banManager->getFailures()); | |
83 | $this->assertEquals([], $this->banManager->getBans()); | |
84 | } | |
85 | ||
86 | /** | |
87 | * Test failed attempt with a direct IP. | |
88 | */ | |
89 | public function testHandleFailedAttempt() | |
90 | { | |
91 | $this->assertCount(0, $this->banManager->getFailures()); | |
92 | ||
93 | $this->banManager->handleFailedAttempt($this->server); | |
94 | $this->assertCount(1, $this->banManager->getFailures()); | |
95 | $this->assertEquals(1, $this->banManager->getFailures()[$this->ipAddr]); | |
96 | ||
97 | $this->banManager->handleFailedAttempt($this->server); | |
98 | $this->assertCount(1, $this->banManager->getFailures()); | |
99 | $this->assertEquals(2, $this->banManager->getFailures()[$this->ipAddr]); | |
100 | } | |
101 | ||
102 | /** | |
103 | * Test failed attempt behind a trusted proxy IP (with proper IP forwarding). | |
104 | */ | |
105 | public function testHandleFailedAttemptBehingProxy() | |
106 | { | |
107 | $server = [ | |
108 | 'REMOTE_ADDR' => $this->trustedProxy, | |
109 | 'HTTP_X_FORWARDED_FOR' => $this->ipAddr, | |
110 | ]; | |
111 | $this->assertCount(0, $this->banManager->getFailures()); | |
112 | ||
113 | $this->banManager->handleFailedAttempt($server); | |
114 | $this->assertCount(1, $this->banManager->getFailures()); | |
115 | $this->assertEquals(1, $this->banManager->getFailures()[$this->ipAddr]); | |
116 | ||
117 | $this->banManager->handleFailedAttempt($server); | |
118 | $this->assertCount(1, $this->banManager->getFailures()); | |
119 | $this->assertEquals(2, $this->banManager->getFailures()[$this->ipAddr]); | |
120 | } | |
121 | ||
122 | /** | |
123 | * Test failed attempt behind a trusted proxy IP but without IP forwarding. | |
124 | */ | |
125 | public function testHandleFailedAttemptBehindNotConfiguredProxy() | |
126 | { | |
127 | $server = [ | |
128 | 'REMOTE_ADDR' => $this->trustedProxy, | |
129 | ]; | |
130 | $this->assertCount(0, $this->banManager->getFailures()); | |
131 | ||
132 | $this->banManager->handleFailedAttempt($server); | |
133 | $this->assertCount(0, $this->banManager->getFailures()); | |
134 | ||
135 | $this->banManager->handleFailedAttempt($server); | |
136 | $this->assertCount(0, $this->banManager->getFailures()); | |
137 | } | |
138 | ||
139 | /** | |
140 | * Test failed attempts with multiple direct IP. | |
141 | */ | |
142 | public function testHandleFailedAttemptMultipleIp() | |
143 | { | |
144 | $this->assertCount(0, $this->banManager->getFailures()); | |
145 | $this->banManager->handleFailedAttempt($this->server); | |
146 | $this->server['REMOTE_ADDR'] = '1.2.3.4'; | |
147 | $this->banManager->handleFailedAttempt($this->server); | |
148 | $this->banManager->handleFailedAttempt($this->server); | |
149 | $this->assertCount(2, $this->banManager->getFailures()); | |
150 | $this->assertEquals(1, $this->banManager->getFailures()[$this->ipAddr]); | |
151 | $this->assertEquals(2, $this->banManager->getFailures()[$this->server['REMOTE_ADDR']]); | |
152 | } | |
153 | ||
154 | /** | |
155 | * Test clear failure for provided IP without any additional data. | |
156 | */ | |
157 | public function testClearFailuresEmpty() | |
158 | { | |
159 | $this->assertCount(0, $this->banManager->getFailures()); | |
160 | $this->banManager->clearFailures($this->server); | |
161 | $this->assertCount(0, $this->banManager->getFailures()); | |
162 | } | |
163 | ||
164 | /** | |
165 | * Test clear failure for provided IP with failed attempts. | |
166 | */ | |
167 | public function testClearFailuresFromFile() | |
168 | { | |
169 | FileUtils::writeFlatDB( | |
170 | $this->banFile, | |
171 | [ | |
172 | 'failures' => [ | |
173 | $this->ipAddr => 2, | |
174 | $ip = '1.2.3.4' => 1, | |
175 | ] | |
176 | ] | |
177 | ); | |
178 | $this->banManager = $this->getNewBanManagerInstance(); | |
179 | ||
180 | $this->assertCount(2, $this->banManager->getFailures()); | |
181 | $this->banManager->clearFailures($this->server); | |
182 | $this->assertCount(1, $this->banManager->getFailures()); | |
183 | $this->assertEquals(1, $this->banManager->getFailures()[$ip]); | |
184 | } | |
185 | ||
186 | /** | |
187 | * Test clear failure for provided IP with failed attempts, behind a reverse proxy. | |
188 | */ | |
189 | public function testClearFailuresFromFileBehindProxy() | |
190 | { | |
191 | $server = [ | |
192 | 'REMOTE_ADDR' => $this->trustedProxy, | |
193 | 'HTTP_X_FORWARDED_FOR' => $this->ipAddr, | |
194 | ]; | |
195 | ||
196 | FileUtils::writeFlatDB( | |
197 | $this->banFile, | |
198 | [ | |
199 | 'failures' => [ | |
200 | $this->ipAddr => 2, | |
201 | $ip = '1.2.3.4' => 1, | |
202 | ] | |
203 | ] | |
204 | ); | |
205 | $this->banManager = $this->getNewBanManagerInstance(); | |
206 | ||
207 | $this->assertCount(2, $this->banManager->getFailures()); | |
208 | $this->banManager->clearFailures($server); | |
209 | $this->assertCount(1, $this->banManager->getFailures()); | |
210 | $this->assertEquals(1, $this->banManager->getFailures()[$ip]); | |
211 | } | |
212 | ||
213 | /** | |
214 | * Test clear failure for provided IP with failed attempts, | |
215 | * behind a reverse proxy without forwarding. | |
216 | */ | |
217 | public function testClearFailuresFromFileBehindNotConfiguredProxy() | |
218 | { | |
219 | $server = [ | |
220 | 'REMOTE_ADDR' => $this->trustedProxy, | |
221 | ]; | |
222 | ||
223 | FileUtils::writeFlatDB( | |
224 | $this->banFile, | |
225 | [ | |
226 | 'failures' => [ | |
227 | $this->ipAddr => 2, | |
228 | $ip = '1.2.3.4' => 1, | |
229 | ] | |
230 | ] | |
231 | ); | |
232 | $this->banManager = $this->getNewBanManagerInstance(); | |
233 | ||
234 | $this->assertCount(2, $this->banManager->getFailures()); | |
235 | $this->banManager->clearFailures($server); | |
236 | $this->assertCount(2, $this->banManager->getFailures()); | |
237 | } | |
238 | ||
239 | /** | |
240 | * Test isBanned without any data | |
241 | */ | |
242 | public function testIsBannedEmpty() | |
243 | { | |
244 | $this->assertFalse($this->banManager->isBanned($this->server)); | |
245 | } | |
246 | ||
247 | /** | |
248 | * Test isBanned with banned IP from file data | |
249 | */ | |
250 | public function testBannedFromFile() | |
251 | { | |
252 | FileUtils::writeFlatDB( | |
253 | $this->banFile, | |
254 | [ | |
255 | 'bans' => [ | |
256 | $this->ipAddr => time() + 10, | |
257 | ] | |
258 | ] | |
259 | ); | |
260 | $this->banManager = $this->getNewBanManagerInstance(); | |
261 | ||
262 | $this->assertCount(1, $this->banManager->getBans()); | |
263 | $this->assertTrue($this->banManager->isBanned($this->server)); | |
264 | } | |
265 | ||
266 | /** | |
267 | * Test isBanned with banned IP from file data behind a reverse proxy | |
268 | */ | |
269 | public function testBannedFromFileBehindProxy() | |
270 | { | |
271 | $server = [ | |
272 | 'REMOTE_ADDR' => $this->trustedProxy, | |
273 | 'HTTP_X_FORWARDED_FOR' => $this->ipAddr, | |
274 | ]; | |
275 | FileUtils::writeFlatDB( | |
276 | $this->banFile, | |
277 | [ | |
278 | 'bans' => [ | |
279 | $this->ipAddr => time() + 10, | |
280 | ] | |
281 | ] | |
282 | ); | |
283 | $this->banManager = $this->getNewBanManagerInstance(); | |
284 | ||
285 | $this->assertCount(1, $this->banManager->getBans()); | |
286 | $this->assertTrue($this->banManager->isBanned($server)); | |
287 | } | |
288 | ||
289 | /** | |
290 | * Test isBanned with banned IP from file data behind a reverse proxy, | |
291 | * without IP forwarding | |
292 | */ | |
293 | public function testBannedFromFileBehindNotConfiguredProxy() | |
294 | { | |
295 | $server = [ | |
296 | 'REMOTE_ADDR' => $this->trustedProxy, | |
297 | ]; | |
298 | FileUtils::writeFlatDB( | |
299 | $this->banFile, | |
300 | [ | |
301 | 'bans' => [ | |
302 | $this->ipAddr => time() + 10, | |
303 | ] | |
304 | ] | |
305 | ); | |
306 | $this->banManager = $this->getNewBanManagerInstance(); | |
307 | ||
308 | $this->assertCount(1, $this->banManager->getBans()); | |
309 | $this->assertFalse($this->banManager->isBanned($server)); | |
310 | } | |
311 | ||
312 | /** | |
313 | * Test isBanned with an expired ban | |
314 | */ | |
315 | public function testLiftBan() | |
316 | { | |
317 | FileUtils::writeFlatDB( | |
318 | $this->banFile, | |
319 | [ | |
320 | 'bans' => [ | |
321 | $this->ipAddr => time() - 10, | |
322 | ] | |
323 | ] | |
324 | ); | |
325 | $this->banManager = $this->getNewBanManagerInstance(); | |
326 | ||
327 | $this->assertCount(1, $this->banManager->getBans()); | |
328 | $this->assertFalse($this->banManager->isBanned($this->server)); | |
329 | } | |
330 | ||
331 | /** | |
332 | * Test isBanned with an expired ban behind a reverse proxy | |
333 | */ | |
334 | public function testLiftBanBehindProxy() | |
335 | { | |
336 | $server = [ | |
337 | 'REMOTE_ADDR' => $this->trustedProxy, | |
338 | 'HTTP_X_FORWARDED_FOR' => $this->ipAddr, | |
339 | ]; | |
340 | ||
341 | FileUtils::writeFlatDB( | |
342 | $this->banFile, | |
343 | [ | |
344 | 'bans' => [ | |
345 | $this->ipAddr => time() - 10, | |
346 | ] | |
347 | ] | |
348 | ); | |
349 | $this->banManager = $this->getNewBanManagerInstance(); | |
350 | ||
351 | $this->assertCount(1, $this->banManager->getBans()); | |
352 | $this->assertFalse($this->banManager->isBanned($server)); | |
353 | } | |
354 | ||
355 | /** | |
356 | * Test isBanned with an expired ban behind a reverse proxy | |
357 | */ | |
358 | public function testLiftBanBehindNotConfiguredProxy() | |
359 | { | |
360 | $server = [ | |
361 | 'REMOTE_ADDR' => $this->trustedProxy, | |
362 | ]; | |
363 | ||
364 | FileUtils::writeFlatDB( | |
365 | $this->banFile, | |
366 | [ | |
367 | 'bans' => [ | |
368 | $this->ipAddr => time() - 10, | |
369 | ] | |
370 | ] | |
371 | ); | |
372 | $this->banManager = $this->getNewBanManagerInstance(); | |
373 | ||
374 | $this->assertCount(1, $this->banManager->getBans()); | |
375 | $this->assertFalse($this->banManager->isBanned($server)); | |
376 | } | |
377 | ||
378 | /** | |
379 | * Build a new instance of BanManager, which will reread the ban file. | |
380 | * | |
381 | * @return BanManager instance | |
382 | */ | |
383 | protected function getNewBanManagerInstance() | |
384 | { | |
385 | return new BanManager( | |
386 | [$this->trustedProxy], | |
387 | 3, | |
388 | 1800, | |
389 | $this->banFile, | |
390 | $this->logFile | |
391 | ); | |
392 | } | |
393 | } |