diff options
-rw-r--r-- | application/security/LoginManager.php | 64 | ||||
-rw-r--r-- | doc/md/Shaarli-configuration.md | 9 | ||||
-rw-r--r-- | tests/security/LoginManagerTest.php | 34 |
3 files changed, 98 insertions, 9 deletions
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php index 0b0ce0b1..2cea3f10 100644 --- a/application/security/LoginManager.php +++ b/application/security/LoginManager.php | |||
@@ -1,6 +1,7 @@ | |||
1 | <?php | 1 | <?php |
2 | namespace Shaarli\Security; | 2 | namespace Shaarli\Security; |
3 | 3 | ||
4 | use Exception; | ||
4 | use Shaarli\Config\ConfigManager; | 5 | use Shaarli\Config\ConfigManager; |
5 | 6 | ||
6 | /** | 7 | /** |
@@ -139,26 +140,71 @@ class LoginManager | |||
139 | */ | 140 | */ |
140 | public function checkCredentials($remoteIp, $clientIpId, $login, $password) | 141 | public function checkCredentials($remoteIp, $clientIpId, $login, $password) |
141 | { | 142 | { |
142 | $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); | 143 | // Check login matches config |
144 | if ($login != $this->configManager->get('credentials.login')) { | ||
145 | return false; | ||
146 | } | ||
143 | 147 | ||
144 | if ($login != $this->configManager->get('credentials.login') | 148 | // Check credentials |
145 | || $hash != $this->configManager->get('credentials.hash') | 149 | try { |
146 | ) { | 150 | if (($this->configManager->get('ldap.host') != "" && $this->checkCredentialsFromLdap($login, $password)) |
151 | || ($this->configManager->get('ldap.host') == "" && $this->checkCredentialsFromLocalConfig($login, $password))) { | ||
152 | $this->sessionManager->storeLoginInfo($clientIpId); | ||
153 | logm( | ||
154 | $this->configManager->get('resource.log'), | ||
155 | $remoteIp, | ||
156 | 'Login successful' | ||
157 | ); | ||
158 | return true; | ||
159 | } | ||
160 | } | ||
161 | catch(Exception $exception) { | ||
147 | logm( | 162 | logm( |
148 | $this->configManager->get('resource.log'), | 163 | $this->configManager->get('resource.log'), |
149 | $remoteIp, | 164 | $remoteIp, |
150 | 'Login failed for user ' . $login | 165 | 'Exception while checking credentials: ' . $exception |
151 | ); | 166 | ); |
152 | return false; | ||
153 | } | 167 | } |
154 | 168 | ||
155 | $this->sessionManager->storeLoginInfo($clientIpId); | ||
156 | logm( | 169 | logm( |
157 | $this->configManager->get('resource.log'), | 170 | $this->configManager->get('resource.log'), |
158 | $remoteIp, | 171 | $remoteIp, |
159 | 'Login successful' | 172 | 'Login failed for user ' . $login |
160 | ); | 173 | ); |
161 | return true; | 174 | return false; |
175 | } | ||
176 | |||
177 | |||
178 | /** | ||
179 | * Check user credentials from local config | ||
180 | * | ||
181 | * @param string $login Username | ||
182 | * @param string $password Password | ||
183 | * | ||
184 | * @return bool true if the provided credentials are valid, false otherwise | ||
185 | */ | ||
186 | public function checkCredentialsFromLocalConfig($login, $password) { | ||
187 | $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); | ||
188 | |||
189 | return $login == $this->configManager->get('credentials.login') | ||
190 | && $hash == $this->configManager->get('credentials.hash'); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * Check user credentials are valid through LDAP bind | ||
195 | * | ||
196 | * @param string $remoteIp Remote client IP address | ||
197 | * @param string $clientIpId Client IP address identifier | ||
198 | * @param string $login Username | ||
199 | * @param string $password Password | ||
200 | * | ||
201 | * @return bool true if the provided credentials are valid, false otherwise | ||
202 | */ | ||
203 | public function checkCredentialsFromLdap($login, $password, $connect = null, $bind = null) | ||
204 | { | ||
205 | $connect = $connect ?? function($host) { return ldap_connect($host); }; | ||
206 | $bind = $bind ?? function($handle, $dn, $password) { return ldap_bind($handle, $dn, $password); }; | ||
207 | return $bind($connect($this->configManager->get('ldap.host')), sprintf($this->configManager->get('ldap.dn'), $login), $password); | ||
162 | } | 208 | } |
163 | 209 | ||
164 | /** | 210 | /** |
diff --git a/doc/md/Shaarli-configuration.md b/doc/md/Shaarli-configuration.md index 664e36dd..2462e20e 100644 --- a/doc/md/Shaarli-configuration.md +++ b/doc/md/Shaarli-configuration.md | |||
@@ -122,6 +122,11 @@ Must be an associative array: `translation domain => translation path`. | |||
122 | - **enable_thumbnails**: Enable or disable thumbnail display. | 122 | - **enable_thumbnails**: Enable or disable thumbnail display. |
123 | - **enable_localcache**: Enable or disable local cache. | 123 | - **enable_localcache**: Enable or disable local cache. |
124 | 124 | ||
125 | ### LDAP | ||
126 | |||
127 | - **host**: LDAP host used for user authentication | ||
128 | - **dn**: user DN template (`sprintf` format, `%s` being replaced by user login) | ||
129 | |||
125 | ## Configuration file example | 130 | ## Configuration file example |
126 | 131 | ||
127 | ```json | 132 | ```json |
@@ -223,6 +228,10 @@ Must be an associative array: `translation domain => translation path`. | |||
223 | "extensions": { | 228 | "extensions": { |
224 | "demo": "plugins/demo_plugin/languages/" | 229 | "demo": "plugins/demo_plugin/languages/" |
225 | } | 230 | } |
231 | }, | ||
232 | "ldap": { | ||
233 | "host": "ldap://localhost", | ||
234 | "dn": "uid=%s,ou=people,dc=example,dc=org" | ||
226 | } | 235 | } |
227 | } ?> | 236 | } ?> |
228 | ``` | 237 | ``` |
diff --git a/tests/security/LoginManagerTest.php b/tests/security/LoginManagerTest.php index eef0f22a..f2d78802 100644 --- a/tests/security/LoginManagerTest.php +++ b/tests/security/LoginManagerTest.php | |||
@@ -78,6 +78,7 @@ class LoginManagerTest extends TestCase | |||
78 | 'security.ban_after' => 2, | 78 | 'security.ban_after' => 2, |
79 | 'security.ban_duration' => 3600, | 79 | 'security.ban_duration' => 3600, |
80 | 'security.trusted_proxies' => [$this->trustedProxy], | 80 | 'security.trusted_proxies' => [$this->trustedProxy], |
81 | 'ldap.host' => '', | ||
81 | ]); | 82 | ]); |
82 | 83 | ||
83 | $this->cookie = []; | 84 | $this->cookie = []; |
@@ -296,4 +297,37 @@ class LoginManagerTest extends TestCase | |||
296 | $this->loginManager->checkCredentials('', '', $this->login, $this->password) | 297 | $this->loginManager->checkCredentials('', '', $this->login, $this->password) |
297 | ); | 298 | ); |
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 | } | ||
299 | } | 333 | } |