diff options
-rw-r--r-- | application/security/LoginManager.php | 79 | ||||
-rw-r--r-- | doc/md/Shaarli-configuration.md | 9 | ||||
-rw-r--r-- | tests/security/LoginManagerTest.php | 34 |
3 files changed, 113 insertions, 9 deletions
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php index 0b0ce0b1..39ec9b2e 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,86 @@ 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 | $useLdapLogin = !empty($this->configManager->get('ldap.host')); |
151 | if ((false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password)) | ||
152 | || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password)) | ||
153 | ) { | ||
154 | $this->sessionManager->storeLoginInfo($clientIpId); | ||
155 | logm( | ||
156 | $this->configManager->get('resource.log'), | ||
157 | $remoteIp, | ||
158 | 'Login successful' | ||
159 | ); | ||
160 | return true; | ||
161 | } | ||
162 | } | ||
163 | catch(Exception $exception) { | ||
147 | logm( | 164 | logm( |
148 | $this->configManager->get('resource.log'), | 165 | $this->configManager->get('resource.log'), |
149 | $remoteIp, | 166 | $remoteIp, |
150 | 'Login failed for user ' . $login | 167 | 'Exception while checking credentials: ' . $exception |
151 | ); | 168 | ); |
152 | return false; | ||
153 | } | 169 | } |
154 | 170 | ||
155 | $this->sessionManager->storeLoginInfo($clientIpId); | ||
156 | logm( | 171 | logm( |
157 | $this->configManager->get('resource.log'), | 172 | $this->configManager->get('resource.log'), |
158 | $remoteIp, | 173 | $remoteIp, |
159 | 'Login successful' | 174 | 'Login failed for user ' . $login |
175 | ); | ||
176 | return false; | ||
177 | } | ||
178 | |||
179 | |||
180 | /** | ||
181 | * Check user credentials from local config | ||
182 | * | ||
183 | * @param string $login Username | ||
184 | * @param string $password Password | ||
185 | * | ||
186 | * @return bool true if the provided credentials are valid, false otherwise | ||
187 | */ | ||
188 | public function checkCredentialsFromLocalConfig($login, $password) { | ||
189 | $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); | ||
190 | |||
191 | return $login == $this->configManager->get('credentials.login') | ||
192 | && $hash == $this->configManager->get('credentials.hash'); | ||
193 | } | ||
194 | |||
195 | /** | ||
196 | * Check user credentials are valid through LDAP bind | ||
197 | * | ||
198 | * @param string $remoteIp Remote client IP address | ||
199 | * @param string $clientIpId Client IP address identifier | ||
200 | * @param string $login Username | ||
201 | * @param string $password Password | ||
202 | * | ||
203 | * @return bool true if the provided credentials are valid, false otherwise | ||
204 | */ | ||
205 | public function checkCredentialsFromLdap($login, $password, $connect = null, $bind = null) | ||
206 | { | ||
207 | $connect = $connect ?? function($host) { | ||
208 | $resource = ldap_connect($host); | ||
209 | |||
210 | ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3); | ||
211 | |||
212 | return $resource; | ||
213 | }; | ||
214 | $bind = $bind ?? function($handle, $dn, $password) { | ||
215 | return ldap_bind($handle, $dn, $password); | ||
216 | }; | ||
217 | |||
218 | return $bind( | ||
219 | $connect($this->configManager->get('ldap.host')), | ||
220 | sprintf($this->configManager->get('ldap.dn'), $login), | ||
221 | $password | ||
160 | ); | 222 | ); |
161 | return true; | ||
162 | } | 223 | } |
163 | 224 | ||
164 | /** | 225 | /** |
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..8fd1698c 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->configManager->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 | } |