aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/security
diff options
context:
space:
mode:
Diffstat (limited to 'tests/security')
-rw-r--r--tests/security/BanManagerTest.php393
-rw-r--r--tests/security/LoginManagerTest.php115
-rw-r--r--tests/security/SessionManagerTest.php4
3 files changed, 415 insertions, 97 deletions
diff --git a/tests/security/BanManagerTest.php b/tests/security/BanManagerTest.php
new file mode 100644
index 00000000..bba7c8ad
--- /dev/null
+++ b/tests/security/BanManagerTest.php
@@ -0,0 +1,393 @@
1<?php
2
3
4namespace Shaarli\Security;
5
6use PHPUnit\Framework\TestCase;
7use Shaarli\FileUtils;
8
9/**
10 * Test coverage for BanManager
11 */
12class 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}
diff --git a/tests/security/LoginManagerTest.php b/tests/security/LoginManagerTest.php
index f26cd1eb..eef0f22a 100644
--- a/tests/security/LoginManagerTest.php
+++ b/tests/security/LoginManagerTest.php
@@ -2,7 +2,8 @@
2namespace Shaarli\Security; 2namespace Shaarli\Security;
3 3
4require_once 'tests/utils/FakeConfigManager.php'; 4require_once 'tests/utils/FakeConfigManager.php';
5use \PHPUnit\Framework\TestCase; 5
6use PHPUnit\Framework\TestCase;
6 7
7/** 8/**
8 * Test coverage for LoginManager 9 * Test coverage for LoginManager
@@ -74,54 +75,27 @@ class LoginManagerTest extends TestCase
74 'credentials.salt' => $this->salt, 75 'credentials.salt' => $this->salt,
75 'resource.ban_file' => $this->banFile, 76 'resource.ban_file' => $this->banFile,
76 'resource.log' => $this->logFile, 77 'resource.log' => $this->logFile,
77 'security.ban_after' => 4, 78 'security.ban_after' => 2,
78 'security.ban_duration' => 3600, 79 'security.ban_duration' => 3600,
79 'security.trusted_proxies' => [$this->trustedProxy], 80 'security.trusted_proxies' => [$this->trustedProxy],
80 ]); 81 ]);
81 82
82 $this->cookie = []; 83 $this->cookie = [];
83
84 $this->globals = &$GLOBALS;
85 unset($this->globals['IPBANS']);
86
87 $this->session = []; 84 $this->session = [];
88 85
89 $this->sessionManager = new SessionManager($this->session, $this->configManager); 86 $this->sessionManager = new SessionManager($this->session, $this->configManager);
90 $this->loginManager = new LoginManager($this->globals, $this->configManager, $this->sessionManager); 87 $this->loginManager = new LoginManager($this->configManager, $this->sessionManager);
91 $this->server['REMOTE_ADDR'] = $this->ipAddr; 88 $this->server['REMOTE_ADDR'] = $this->ipAddr;
92 } 89 }
93 90
94 /** 91 /**
95 * Wipe test resources
96 */
97 public function tearDown()
98 {
99 unset($this->globals['IPBANS']);
100 }
101
102 /**
103 * Instantiate a LoginManager and load ban records
104 */
105 public function testReadBanFile()
106 {
107 file_put_contents(
108 $this->banFile,
109 "<?php\n\$GLOBALS['IPBANS']=array('FAILURES' => array('127.0.0.1' => 99));\n?>"
110 );
111 new LoginManager($this->globals, $this->configManager, null);
112 $this->assertEquals(99, $this->globals['IPBANS']['FAILURES']['127.0.0.1']);
113 }
114
115 /**
116 * Record a failed login attempt 92 * Record a failed login attempt
117 */ 93 */
118 public function testHandleFailedLogin() 94 public function testHandleFailedLogin()
119 { 95 {
120 $this->loginManager->handleFailedLogin($this->server); 96 $this->loginManager->handleFailedLogin($this->server);
121 $this->assertEquals(1, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
122
123 $this->loginManager->handleFailedLogin($this->server); 97 $this->loginManager->handleFailedLogin($this->server);
124 $this->assertEquals(2, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]); 98 $this->assertFalse($this->loginManager->canLogin($this->server));
125 } 99 }
126 100
127 /** 101 /**
@@ -134,10 +108,8 @@ class LoginManagerTest extends TestCase
134 'HTTP_X_FORWARDED_FOR' => $this->ipAddr, 108 'HTTP_X_FORWARDED_FOR' => $this->ipAddr,
135 ]; 109 ];
136 $this->loginManager->handleFailedLogin($server); 110 $this->loginManager->handleFailedLogin($server);
137 $this->assertEquals(1, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
138
139 $this->loginManager->handleFailedLogin($server); 111 $this->loginManager->handleFailedLogin($server);
140 $this->assertEquals(2, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]); 112 $this->assertFalse($this->loginManager->canLogin($server));
141 } 113 }
142 114
143 /** 115 /**
@@ -149,39 +121,8 @@ class LoginManagerTest extends TestCase
149 'REMOTE_ADDR' => $this->trustedProxy, 121 'REMOTE_ADDR' => $this->trustedProxy,
150 ]; 122 ];
151 $this->loginManager->handleFailedLogin($server); 123 $this->loginManager->handleFailedLogin($server);
152 $this->assertFalse(isset($this->globals['IPBANS']['FAILURES'][$this->ipAddr]));
153
154 $this->loginManager->handleFailedLogin($server); 124 $this->loginManager->handleFailedLogin($server);
155 $this->assertFalse(isset($this->globals['IPBANS']['FAILURES'][$this->ipAddr])); 125 $this->assertTrue($this->loginManager->canLogin($server));
156 }
157
158 /**
159 * Record a failed login attempt and ban the IP after too many failures
160 */
161 public function testHandleFailedLoginBanIp()
162 {
163 $this->loginManager->handleFailedLogin($this->server);
164 $this->assertEquals(1, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
165 $this->assertTrue($this->loginManager->canLogin($this->server));
166
167 $this->loginManager->handleFailedLogin($this->server);
168 $this->assertEquals(2, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
169 $this->assertTrue($this->loginManager->canLogin($this->server));
170
171 $this->loginManager->handleFailedLogin($this->server);
172 $this->assertEquals(3, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
173 $this->assertTrue($this->loginManager->canLogin($this->server));
174
175 $this->loginManager->handleFailedLogin($this->server);
176 $this->assertEquals(4, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
177 $this->assertFalse($this->loginManager->canLogin($this->server));
178
179 // handleFailedLogin is not supposed to be called at this point:
180 // - no login form should be displayed once an IP has been banned
181 // - yet this could happen when using custom templates / scripts
182 $this->loginManager->handleFailedLogin($this->server);
183 $this->assertEquals(5, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
184 $this->assertFalse($this->loginManager->canLogin($this->server));
185 } 126 }
186 127
187 /** 128 /**
@@ -201,14 +142,11 @@ class LoginManagerTest extends TestCase
201 public function testHandleSuccessfulLoginAfterFailure() 142 public function testHandleSuccessfulLoginAfterFailure()
202 { 143 {
203 $this->loginManager->handleFailedLogin($this->server); 144 $this->loginManager->handleFailedLogin($this->server);
204 $this->loginManager->handleFailedLogin($this->server);
205 $this->assertEquals(2, $this->globals['IPBANS']['FAILURES'][$this->ipAddr]);
206 $this->assertTrue($this->loginManager->canLogin($this->server)); 145 $this->assertTrue($this->loginManager->canLogin($this->server));
207 146
208 $this->loginManager->handleSuccessfulLogin($this->server); 147 $this->loginManager->handleSuccessfulLogin($this->server);
148 $this->loginManager->handleFailedLogin($this->server);
209 $this->assertTrue($this->loginManager->canLogin($this->server)); 149 $this->assertTrue($this->loginManager->canLogin($this->server));
210 $this->assertFalse(isset($this->globals['IPBANS']['FAILURES'][$this->ipAddr]));
211 $this->assertFalse(isset($this->globals['IPBANS']['BANS'][$this->ipAddr]));
212 } 150 }
213 151
214 /** 152 /**
@@ -220,41 +158,28 @@ class LoginManagerTest extends TestCase
220 } 158 }
221 159
222 /** 160 /**
223 * The IP is banned 161 * Generate a token depending on the user credentials and client IP
224 */
225 public function testCanLoginIpBanned()
226 {
227 // ban the IP for an hour
228 $this->globals['IPBANS']['FAILURES'][$this->ipAddr] = 10;
229 $this->globals['IPBANS']['BANS'][$this->ipAddr] = time() + 3600;
230
231 $this->assertFalse($this->loginManager->canLogin($this->server));
232 }
233
234 /**
235 * The IP is banned, and the ban duration is over
236 */ 162 */
237 public function testCanLoginIpBanExpired() 163 public function testGenerateStaySignedInToken()
238 { 164 {
239 // ban the IP for an hour 165 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
240 $this->globals['IPBANS']['FAILURES'][$this->ipAddr] = 10;
241 $this->globals['IPBANS']['BANS'][$this->ipAddr] = time() + 3600;
242 $this->assertFalse($this->loginManager->canLogin($this->server));
243 166
244 // lift the ban 167 $this->assertEquals(
245 $this->globals['IPBANS']['BANS'][$this->ipAddr] = time() - 3600; 168 sha1($this->passwordHash . $this->clientIpAddress . $this->salt),
246 $this->assertTrue($this->loginManager->canLogin($this->server)); 169 $this->loginManager->getStaySignedInToken()
170 );
247 } 171 }
248 172
249 /** 173 /**
250 * Generate a token depending on the user credentials and client IP 174 * Generate a token depending on the user credentials with session protected disabled
251 */ 175 */
252 public function testGenerateStaySignedInToken() 176 public function testGenerateStaySignedInTokenSessionProtectionDisabled()
253 { 177 {
178 $this->configManager->set('security.session_protection_disabled', true);
254 $this->loginManager->generateStaySignedInToken($this->clientIpAddress); 179 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
255 180
256 $this->assertEquals( 181 $this->assertEquals(
257 sha1($this->passwordHash . $this->clientIpAddress . $this->salt), 182 sha1($this->passwordHash . $this->salt),
258 $this->loginManager->getStaySignedInToken() 183 $this->loginManager->getStaySignedInToken()
259 ); 184 );
260 } 185 }
@@ -267,7 +192,7 @@ class LoginManagerTest extends TestCase
267 $configManager = new \FakeConfigManager([ 192 $configManager = new \FakeConfigManager([
268 'resource.ban_file' => $this->banFile, 193 'resource.ban_file' => $this->banFile,
269 ]); 194 ]);
270 $loginManager = new LoginManager($this->globals, $configManager, null); 195 $loginManager = new LoginManager($configManager, null);
271 $loginManager->checkLoginState([], ''); 196 $loginManager->checkLoginState([], '');
272 197
273 $this->assertFalse($loginManager->isLoggedIn()); 198 $this->assertFalse($loginManager->isLoggedIn());
diff --git a/tests/security/SessionManagerTest.php b/tests/security/SessionManagerTest.php
index 7961e771..f264505e 100644
--- a/tests/security/SessionManagerTest.php
+++ b/tests/security/SessionManagerTest.php
@@ -5,8 +5,8 @@ require_once 'tests/utils/FakeConfigManager.php';
5require_once 'tests/utils/ReferenceSessionIdHashes.php'; 5require_once 'tests/utils/ReferenceSessionIdHashes.php';
6ReferenceSessionIdHashes::genAllHashes(); 6ReferenceSessionIdHashes::genAllHashes();
7 7
8use \Shaarli\Security\SessionManager; 8use PHPUnit\Framework\TestCase;
9use \PHPUnit\Framework\TestCase; 9use Shaarli\Security\SessionManager;
10 10
11/** 11/**
12 * Test coverage for SessionManager 12 * Test coverage for SessionManager