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