]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - tests/security/LoginManagerTest.php
Merge pull request #1698 from ArthurHoaro/feature/plugins-search-filter
[github/shaarli/Shaarli.git] / tests / security / LoginManagerTest.php
1 <?php
2
3 namespace Shaarli\Security;
4
5 use Psr\Log\LoggerInterface;
6 use Shaarli\FakeConfigManager;
7 use Shaarli\TestCase;
8
9 /**
10 * Test coverage for LoginManager
11 */
12 class LoginManagerTest extends TestCase
13 {
14 /** @var FakeConfigManager Configuration Manager instance */
15 protected $configManager = null;
16
17 /** @var LoginManager Login Manager instance */
18 protected $loginManager = null;
19
20 /** @var SessionManager Session Manager instance */
21 protected $sessionManager = null;
22
23 /** @var string Banned IP filename */
24 protected $banFile = 'sandbox/ipbans.php';
25
26 /** @var string Log filename */
27 protected $logFile = 'sandbox/shaarli.log';
28
29 /** @var array Simulates the $_COOKIE array */
30 protected $cookie = [];
31
32 /** @var array Simulates the $GLOBALS array */
33 protected $globals = [];
34
35 /** @var array Simulates the $_SERVER array */
36 protected $server = [];
37
38 /** @var array Simulates the $_SESSION array */
39 protected $session = [];
40
41 /** @var string Advertised client IP address */
42 protected $clientIpAddress = '10.1.47.179';
43
44 /** @var string Local client IP address */
45 protected $ipAddr = '127.0.0.1';
46
47 /** @var string Trusted proxy IP address */
48 protected $trustedProxy = '10.1.1.100';
49
50 /** @var string User login */
51 protected $login = 'johndoe';
52
53 /** @var string User password */
54 protected $password = 'IC4nHazL0g1n?';
55
56 /** @var string Hash of the salted user password */
57 protected $passwordHash = '';
58
59 /** @var string Salt used by hash functions */
60 protected $salt = '669e24fa9c5a59a613f98e8e38327384504a4af2';
61
62 /** @var CookieManager */
63 protected $cookieManager;
64
65 /** @var BanManager */
66 protected $banManager;
67
68 /**
69 * Prepare or reset test resources
70 */
71 protected function setUp(): void
72 {
73 if (file_exists($this->banFile)) {
74 unlink($this->banFile);
75 }
76
77 $this->passwordHash = sha1($this->password . $this->login . $this->salt);
78
79 $this->configManager = new FakeConfigManager([
80 'credentials.login' => $this->login,
81 'credentials.hash' => $this->passwordHash,
82 'credentials.salt' => $this->salt,
83 'resource.ban_file' => $this->banFile,
84 'resource.log' => $this->logFile,
85 'security.ban_after' => 2,
86 'security.ban_duration' => 3600,
87 'security.trusted_proxies' => [$this->trustedProxy],
88 'ldap.host' => '',
89 ]);
90
91 $this->cookie = [];
92 $this->session = [];
93
94 $this->cookieManager = $this->createMock(CookieManager::class);
95 $this->cookieManager->method('getCookieParameter')->willReturnCallback(function (string $key) {
96 return $this->cookie[$key] ?? null;
97 });
98 $this->sessionManager = new SessionManager($this->session, $this->configManager, 'session_path');
99 $this->banManager = $this->createMock(BanManager::class);
100 $this->loginManager = new LoginManager(
101 $this->configManager,
102 $this->sessionManager,
103 $this->cookieManager,
104 $this->banManager,
105 $this->createMock(LoggerInterface::class)
106 );
107 $this->server['REMOTE_ADDR'] = $this->ipAddr;
108 }
109
110 /**
111 * Record a failed login attempt
112 */
113 public function testHandleFailedLogin(): void
114 {
115 $this->banManager->expects(static::exactly(2))->method('handleFailedAttempt');
116 $this->banManager->method('isBanned')->willReturn(true);
117
118 $this->loginManager->handleFailedLogin($this->server);
119 $this->loginManager->handleFailedLogin($this->server);
120
121 static::assertFalse($this->loginManager->canLogin($this->server));
122 }
123
124 /**
125 * Record a failed login attempt - IP behind a trusted proxy
126 */
127 public function testHandleFailedLoginBehindTrustedProxy()
128 {
129 $server = [
130 'REMOTE_ADDR' => $this->trustedProxy,
131 'HTTP_X_FORWARDED_FOR' => $this->ipAddr,
132 ];
133
134 $this->banManager->expects(static::exactly(2))->method('handleFailedAttempt');
135 $this->banManager->method('isBanned')->willReturn(true);
136
137 $this->loginManager->handleFailedLogin($server);
138 $this->loginManager->handleFailedLogin($server);
139
140 $this->assertFalse($this->loginManager->canLogin($server));
141 }
142
143 /**
144 * Record a failed login attempt - IP behind a trusted proxy but not forwarded
145 */
146 public function testHandleFailedLoginBehindTrustedProxyNoIp()
147 {
148 $server = [
149 'REMOTE_ADDR' => $this->trustedProxy,
150 ];
151 $this->loginManager->handleFailedLogin($server);
152 $this->loginManager->handleFailedLogin($server);
153 $this->assertTrue($this->loginManager->canLogin($server));
154 }
155
156 /**
157 * Nothing to do
158 */
159 public function testHandleSuccessfulLogin()
160 {
161 $this->assertTrue($this->loginManager->canLogin($this->server));
162
163 $this->loginManager->handleSuccessfulLogin($this->server);
164 $this->assertTrue($this->loginManager->canLogin($this->server));
165 }
166
167 /**
168 * Erase failure records after successfully logging in from this IP
169 */
170 public function testHandleSuccessfulLoginAfterFailure()
171 {
172 $this->loginManager->handleFailedLogin($this->server);
173 $this->assertTrue($this->loginManager->canLogin($this->server));
174
175 $this->loginManager->handleSuccessfulLogin($this->server);
176 $this->loginManager->handleFailedLogin($this->server);
177 $this->assertTrue($this->loginManager->canLogin($this->server));
178 }
179
180 /**
181 * The IP is not banned
182 */
183 public function testCanLoginIpNotBanned()
184 {
185 $this->assertTrue($this->loginManager->canLogin($this->server));
186 }
187
188 /**
189 * Generate a token depending on the user credentials and client IP
190 */
191 public function testGenerateStaySignedInToken()
192 {
193 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
194
195 $this->assertEquals(
196 sha1($this->passwordHash . $this->clientIpAddress . $this->salt),
197 $this->loginManager->getStaySignedInToken()
198 );
199 }
200
201 /**
202 * Generate a token depending on the user credentials with session protected disabled
203 */
204 public function testGenerateStaySignedInTokenSessionProtectionDisabled()
205 {
206 $this->configManager->set('security.session_protection_disabled', true);
207 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
208
209 $this->assertEquals(
210 sha1($this->passwordHash . $this->salt),
211 $this->loginManager->getStaySignedInToken()
212 );
213 }
214
215 /**
216 * Check user login - Shaarli has not yet been configured
217 */
218 public function testCheckLoginStateNotConfigured()
219 {
220 $configManager = new FakeConfigManager([
221 'resource.ban_file' => $this->banFile,
222 ]);
223 $loginManager = new LoginManager(
224 $configManager,
225 $this->sessionManager,
226 $this->cookieManager,
227 $this->banManager,
228 $this->createMock(LoggerInterface::class)
229 );
230 $loginManager->checkLoginState('');
231
232 $this->assertFalse($loginManager->isLoggedIn());
233 }
234
235 /**
236 * Check user login - the client cookie does not match the server token
237 */
238 public function testCheckLoginStateStaySignedInWithInvalidToken()
239 {
240 // simulate a previous login
241 $this->session = [
242 'ip' => $this->clientIpAddress,
243 'expires_on' => time() + 100,
244 ];
245 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
246 $this->cookie[CookieManager::STAY_SIGNED_IN] = 'nope';
247
248 $this->loginManager->checkLoginState($this->clientIpAddress);
249
250 $this->assertTrue($this->loginManager->isLoggedIn());
251 $this->assertTrue(empty($this->session['username']));
252 }
253
254 /**
255 * Check user login - the client cookie matches the server token
256 */
257 public function testCheckLoginStateStaySignedInWithValidToken()
258 {
259 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
260 $this->cookie[CookieManager::STAY_SIGNED_IN] = $this->loginManager->getStaySignedInToken();
261
262 $this->loginManager->checkLoginState($this->clientIpAddress);
263
264 $this->assertTrue($this->loginManager->isLoggedIn());
265 $this->assertEquals($this->login, $this->session['username']);
266 $this->assertEquals($this->clientIpAddress, $this->session['ip']);
267 }
268
269 /**
270 * Check user login - the session has expired
271 */
272 public function testCheckLoginStateSessionExpired()
273 {
274 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
275 $this->session['expires_on'] = time() - 100;
276
277 $this->loginManager->checkLoginState($this->clientIpAddress);
278
279 $this->assertFalse($this->loginManager->isLoggedIn());
280 }
281
282 /**
283 * Check user login - the remote client IP has changed
284 */
285 public function testCheckLoginStateClientIpChanged()
286 {
287 $this->loginManager->generateStaySignedInToken($this->clientIpAddress);
288
289 $this->loginManager->checkLoginState('10.7.157.98');
290
291 $this->assertFalse($this->loginManager->isLoggedIn());
292 }
293
294 /**
295 * Check user credentials - wrong login supplied
296 */
297 public function testCheckCredentialsWrongLogin()
298 {
299 $this->assertFalse(
300 $this->loginManager->checkCredentials('', 'b4dl0g1n', $this->password)
301 );
302 }
303
304 /**
305 * Check user credentials - wrong password supplied
306 */
307 public function testCheckCredentialsWrongPassword()
308 {
309 $this->assertFalse(
310 $this->loginManager->checkCredentials('', $this->login, 'b4dp455wd')
311 );
312 }
313
314 /**
315 * Check user credentials - wrong login and password supplied
316 */
317 public function testCheckCredentialsWrongLoginAndPassword()
318 {
319 $this->assertFalse(
320 $this->loginManager->checkCredentials('', 'b4dl0g1n', 'b4dp455wd')
321 );
322 }
323
324 /**
325 * Check user credentials - correct login and password supplied
326 */
327 public function testCheckCredentialsGoodLoginAndPassword()
328 {
329 $this->assertTrue(
330 $this->loginManager->checkCredentials('', $this->login, $this->password)
331 );
332 }
333
334 /**
335 * Check user credentials through LDAP - server unreachable
336 */
337 public function testCheckCredentialsFromUnreachableLdap()
338 {
339 $this->configManager->set('ldap.host', 'dummy');
340 $this->assertFalse(
341 $this->loginManager->checkCredentials('', $this->login, $this->password)
342 );
343 }
344
345 /**
346 * Check user credentials through LDAP - wrong login and password supplied
347 */
348 public function testCheckCredentialsFromLdapWrongLoginAndPassword()
349 {
350 $this->configManager->set('ldap.host', 'dummy');
351 $this->assertFalse(
352 $this->loginManager->checkCredentialsFromLdap($this->login, $this->password, function() { return null; }, function() { return false; })
353 );
354 }
355
356 /**
357 * Check user credentials through LDAP - correct login and password supplied
358 */
359 public function testCheckCredentialsFromLdapGoodLoginAndPassword()
360 {
361 $this->configManager->set('ldap.host', 'dummy');
362 $this->assertTrue(
363 $this->loginManager->checkCredentialsFromLdap($this->login, $this->password, function() { return null; }, function() { return true; })
364 );
365 }
366 }