]>
Commit | Line | Data |
---|---|---|
95b20e17 IB |
1 | commit bc82ebfd779b8641dadd6787f51639ea9105c3e8 |
2 | Author: Ismaël Bouya <ismael.bouya@normalesup.org> | |
3 | Date: Sun Feb 3 20:58:18 2019 +0100 | |
4 | ||
5 | Add ldap connection | |
6 | ||
7 | diff --git a/.htaccess b/.htaccess | |
8 | index 4c00427..5acd708 100644 | |
9 | --- a/.htaccess | |
10 | +++ b/.htaccess | |
11 | @@ -6,10 +6,23 @@ RewriteEngine On | |
12 | # Prevent accessing subdirectories not managed by SCM | |
13 | RewriteRule ^(.git|doxygen|vendor) - [F] | |
14 | ||
15 | +RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$ | |
16 | +RewriteRule ^(.*) - [E=BASE:%1] | |
17 | + | |
18 | +RewriteCond %{ENV:REDIRECT_BASE} (.+) | |
19 | +RewriteRule .* - [E=BASE:%1] | |
20 | + | |
21 | # Forward the "Authorization" HTTP header | |
22 | RewriteCond %{HTTP:Authorization} ^(.*) | |
23 | RewriteRule .* - [e=HTTP_AUTHORIZATION:%1] | |
24 | ||
25 | +RewriteCond %{REQUEST_FILENAME} !-f | |
26 | +RewriteCond %{REQUEST_FILENAME} !-d | |
27 | +RewriteRule ^((?!api/)[^/]*)/?(.*)$ $2?%{QUERY_STRING} [E=USERSPACE:$1] | |
28 | + | |
29 | +RewriteCond %{ENV:REDIRECT_USERSPACE} (.+) | |
30 | +RewriteRule .* - [E=USERSPACE:%1] | |
31 | + | |
32 | # REST API | |
33 | RewriteCond %{REQUEST_FILENAME} !-f | |
34 | RewriteCond %{REQUEST_FILENAME} !-d | |
35 | diff --git a/application/ApplicationUtils.php b/application/ApplicationUtils.php | |
36 | index 911873a..f21a1ef 100644 | |
37 | --- a/application/ApplicationUtils.php | |
38 | +++ b/application/ApplicationUtils.php | |
39 | @@ -191,6 +191,9 @@ public static function checkResourcePermissions($conf) | |
40 | $conf->get('resource.page_cache'), | |
41 | $conf->get('resource.raintpl_tmp'), | |
42 | ) as $path) { | |
43 | + if (! is_dir($path)) { | |
44 | + mkdir($path, 0755, true); | |
45 | + } | |
46 | if (! is_readable(realpath($path))) { | |
47 | $errors[] = '"'.$path.'" '. t('directory is not readable'); | |
48 | } | |
49 | diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php | |
50 | index 32aaea4..99efc15 100644 | |
51 | --- a/application/config/ConfigManager.php | |
52 | +++ b/application/config/ConfigManager.php | |
53 | @@ -21,6 +21,11 @@ class ConfigManager | |
54 | ||
55 | public static $DEFAULT_PLUGINS = array('qrcode'); | |
56 | ||
57 | + /** | |
58 | + * @var string User space. | |
59 | + */ | |
60 | + protected $userSpace; | |
61 | + | |
62 | /** | |
63 | * @var string Config folder. | |
64 | */ | |
65 | @@ -41,12 +46,36 @@ class ConfigManager | |
66 | * | |
67 | * @param string $configFile Configuration file path without extension. | |
68 | */ | |
69 | - public function __construct($configFile = 'data/config') | |
70 | + public function __construct($configFile = null, $userSpace = null) | |
71 | { | |
72 | - $this->configFile = $configFile; | |
73 | + $this->userSpace = $this->findLDAPUser($userSpace); | |
74 | + if ($configFile !== null) { | |
75 | + $this->configFile = $configFile; | |
76 | + } else { | |
77 | + $this->configFile = ($this->userSpace === null) ? 'data/config' : 'data/' . $this->userSpace . '/config'; | |
78 | + } | |
79 | $this->initialize(); | |
80 | } | |
81 | ||
82 | + public function findLDAPUser($login, $password = null) { | |
83 | + $connect = ldap_connect(getenv('SHAARLI_LDAP_HOST')); | |
84 | + ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3); | |
85 | + if (!$connect || !ldap_bind($connect, getenv('SHAARLI_LDAP_DN'), getenv('SHAARLI_LDAP_PASSWORD'))) { | |
86 | + return false; | |
87 | + } | |
88 | + | |
89 | + $search_query = str_replace('%login%', ldap_escape($login), getenv('SHAARLI_LDAP_FILTER')); | |
90 | + | |
91 | + $search = ldap_search($connect, getenv('SHAARLI_LDAP_BASE'), $search_query); | |
92 | + $info = ldap_get_entries($connect, $search); | |
93 | + | |
94 | + if (ldap_count_entries($connect, $search) == 1 && (is_null($password) || ldap_bind($connect, $info[0]["dn"], $password))) { | |
95 | + return $login; | |
96 | + } else { | |
97 | + return null; | |
98 | + } | |
99 | + } | |
100 | + | |
101 | /** | |
102 | * Reset the ConfigManager instance. | |
103 | */ | |
104 | @@ -269,6 +298,16 @@ public function getConfigFileExt() | |
105 | return $this->configFile . $this->configIO->getExtension(); | |
106 | } | |
107 | ||
108 | + /** | |
109 | + * Get the current userspace. | |
110 | + * | |
111 | + * @return mixed User space. | |
112 | + */ | |
113 | + public function getUserSpace() | |
114 | + { | |
115 | + return $this->userSpace; | |
116 | + } | |
117 | + | |
118 | /** | |
119 | * Recursive function which find asked setting in the loaded config. | |
120 | * | |
121 | @@ -342,19 +381,31 @@ protected static function removeConfig($settings, &$conf) | |
122 | */ | |
123 | protected function setDefaultValues() | |
124 | { | |
125 | - $this->setEmpty('resource.data_dir', 'data'); | |
126 | - $this->setEmpty('resource.config', 'data/config.php'); | |
127 | - $this->setEmpty('resource.datastore', 'data/datastore.php'); | |
128 | - $this->setEmpty('resource.ban_file', 'data/ipbans.php'); | |
129 | - $this->setEmpty('resource.updates', 'data/updates.txt'); | |
130 | - $this->setEmpty('resource.log', 'data/log.txt'); | |
131 | - $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); | |
132 | - $this->setEmpty('resource.history', 'data/history.php'); | |
133 | + if ($this->userSpace === null) { | |
134 | + $data = 'data'; | |
135 | + $tmp = 'tmp'; | |
136 | + $cache = 'cache'; | |
137 | + $pagecache = 'pagecache'; | |
138 | + } else { | |
139 | + $data = 'data/' . ($this->userSpace); | |
140 | + $tmp = 'tmp/' . ($this->userSpace); | |
141 | + $cache = 'cache/' . ($this->userSpace); | |
142 | + $pagecache = 'pagecache/' . ($this->userSpace); | |
143 | + } | |
144 | + | |
145 | + $this->setEmpty('resource.data_dir', $data); | |
146 | + $this->setEmpty('resource.config', $data . '/config.php'); | |
147 | + $this->setEmpty('resource.datastore', $data . '/datastore.php'); | |
148 | + $this->setEmpty('resource.ban_file', $data . '/ipbans.php'); | |
149 | + $this->setEmpty('resource.updates', $data . '/updates.txt'); | |
150 | + $this->setEmpty('resource.log', $data . '/log.txt'); | |
151 | + $this->setEmpty('resource.update_check', $data . '/lastupdatecheck.txt'); | |
152 | + $this->setEmpty('resource.history', $data . '/history.php'); | |
153 | $this->setEmpty('resource.raintpl_tpl', 'tpl/'); | |
154 | $this->setEmpty('resource.theme', 'default'); | |
155 | - $this->setEmpty('resource.raintpl_tmp', 'tmp/'); | |
156 | - $this->setEmpty('resource.thumbnails_cache', 'cache'); | |
157 | - $this->setEmpty('resource.page_cache', 'pagecache'); | |
158 | + $this->setEmpty('resource.raintpl_tmp', $tmp); | |
159 | + $this->setEmpty('resource.thumbnails_cache', $cache); | |
160 | + $this->setEmpty('resource.page_cache', $pagecache); | |
161 | ||
162 | $this->setEmpty('security.ban_after', 4); | |
163 | $this->setEmpty('security.ban_duration', 1800); | |
164 | diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php | |
165 | index d6784d6..bdfaca7 100644 | |
166 | --- a/application/security/LoginManager.php | |
167 | +++ b/application/security/LoginManager.php | |
168 | @@ -32,6 +32,9 @@ class LoginManager | |
169 | /** @var string User sign-in token depending on remote IP and credentials */ | |
170 | protected $staySignedInToken = ''; | |
171 | ||
172 | + protected $lastErrorReason = ''; | |
173 | + protected $lastErrorIsBanishable = false; | |
174 | + | |
175 | /** | |
176 | * Constructor | |
177 | * | |
178 | @@ -83,7 +86,7 @@ public function getStaySignedInToken() | |
179 | */ | |
180 | public function checkLoginState($cookie, $clientIpId) | |
181 | { | |
182 | - if (! $this->configManager->exists('credentials.login')) { | |
183 | + if (! $this->configManager->exists('credentials.login') || (isset($_SESSION['username']) && $_SESSION['username'] && $this->configManager->get('credentials.login') !== $_SESSION['username'])) { | |
184 | // Shaarli is not configured yet | |
185 | $this->isLoggedIn = false; | |
186 | return; | |
187 | @@ -133,20 +136,40 @@ public function isLoggedIn() | |
188 | */ | |
189 | public function checkCredentials($remoteIp, $clientIpId, $login, $password) | |
190 | { | |
191 | - $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); | |
192 | + $this->lastErrorIsBanishable = false; | |
193 | + | |
194 | + if ($this->configManager->getUserSpace() !== null && $this->configManager->getUserSpace() !== $login) { | |
195 | + logm($this->configManager->get('resource.log'), | |
196 | + $remoteIp, | |
197 | + 'Trying to login to wrong user space'); | |
198 | + $this->lastErrorReason = 'You’re trying to access the wrong account.'; | |
199 | + return false; | |
200 | + } | |
201 | ||
202 | - if ($login != $this->configManager->get('credentials.login') | |
203 | - || $hash != $this->configManager->get('credentials.hash') | |
204 | - ) { | |
205 | + logm($this->configManager->get('resource.log'), | |
206 | + $remoteIp, | |
207 | + 'Trying LDAP connection'); | |
208 | + $result = $this->configManager->findLDAPUser($login, $password); | |
209 | + if ($result === false) { | |
210 | logm( | |
211 | $this->configManager->get('resource.log'), | |
212 | $remoteIp, | |
213 | - 'Login failed for user ' . $login | |
214 | + 'Impossible to connect to LDAP' | |
215 | ); | |
216 | + $this->lastErrorReason = 'Server error.'; | |
217 | + return false; | |
218 | + } else if (is_null($result)) { | |
219 | + logm( | |
220 | + $this->configManager->get('resource.log'), | |
221 | + $remoteIp, | |
222 | + 'Login failed for user ' . $login | |
223 | + ); | |
224 | + $this->lastErrorIsBanishable = true; | |
225 | + $this->lastErrorReason = 'Wrong login/password.'; | |
226 | return false; | |
227 | } | |
228 | ||
229 | - $this->sessionManager->storeLoginInfo($clientIpId); | |
230 | + $this->sessionManager->storeLoginInfo($clientIpId, $login); | |
231 | logm( | |
232 | $this->configManager->get('resource.log'), | |
233 | $remoteIp, | |
234 | @@ -187,6 +210,10 @@ protected function writeBanFile() | |
235 | */ | |
236 | public function handleFailedLogin($server) | |
237 | { | |
238 | + if (!$this->lastErrorIsBanishable) { | |
239 | + return $this->lastErrorReason ?: 'Error during login.'; | |
240 | + }; | |
241 | + | |
242 | $ip = $server['REMOTE_ADDR']; | |
243 | $trusted = $this->configManager->get('security.trusted_proxies', []); | |
244 | ||
245 | @@ -215,6 +242,7 @@ public function handleFailedLogin($server) | |
246 | ); | |
247 | } | |
248 | $this->writeBanFile(); | |
249 | + return $this->lastErrorReason ?: 'Error during login.'; | |
250 | } | |
251 | ||
252 | /** | |
253 | diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php | |
254 | index b8b8ab8..5eb4aac 100644 | |
255 | --- a/application/security/SessionManager.php | |
256 | +++ b/application/security/SessionManager.php | |
257 | @@ -111,10 +111,10 @@ public static function checkId($sessionId) | |
258 | * | |
259 | * @param string $clientIpId Client IP address identifier | |
260 | */ | |
261 | - public function storeLoginInfo($clientIpId) | |
262 | + public function storeLoginInfo($clientIpId, $login = null) | |
263 | { | |
264 | $this->session['ip'] = $clientIpId; | |
265 | - $this->session['username'] = $this->conf->get('credentials.login'); | |
266 | + $this->session['username'] = $login ?: $this->conf->get('credentials.login'); | |
267 | $this->extendTimeValidityBy(self::$SHORT_TIMEOUT); | |
268 | } | |
269 | ||
270 | diff --git a/index.php b/index.php | |
271 | index 4b86a3e..85376e8 100644 | |
272 | --- a/index.php | |
273 | +++ b/index.php | |
274 | @@ -121,7 +121,27 @@ | |
275 | $_COOKIE['shaarli'] = session_id(); | |
276 | } | |
277 | ||
278 | -$conf = new ConfigManager(); | |
279 | +$folderBase = getenv("BASE"); | |
280 | + | |
281 | +if (getenv("USERSPACE")) { | |
282 | + if (isset($_GET["do"]) && $_GET["do"] == "login") { | |
283 | + header("Location: $folderBase/?do=login"); | |
284 | + exit; | |
285 | + } | |
286 | + $userspace = preg_replace("/[^-_A-Za-z0-9]/", '', getenv("USERSPACE")); | |
287 | +} else if (isset($_SESSION["username"]) && $_SESSION["username"]) { | |
288 | + header("Location: " . $folderBase . "/" . $_SESSION["username"] . "?"); | |
289 | + exit; | |
290 | +} else if (!isset($_GET["do"]) || $_GET["do"] != "login") { | |
291 | + header("Location: $folderBase/?do=login"); | |
292 | + exit; | |
293 | +} | |
294 | + | |
295 | +if (isset($userspace)) { | |
296 | + $conf = new ConfigManager(null, $userspace); | |
297 | +} else { | |
298 | + $conf = new ConfigManager(); | |
299 | +} | |
300 | $sessionManager = new SessionManager($_SESSION, $conf); | |
301 | $loginManager = new LoginManager($GLOBALS, $conf, $sessionManager); | |
302 | $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']); | |
303 | @@ -175,7 +195,7 @@ | |
304 | } | |
305 | ||
306 | // Display the installation form if no existing config is found | |
307 | - install($conf, $sessionManager, $loginManager); | |
308 | + install($conf, $sessionManager, $loginManager, $userspace); | |
309 | } | |
310 | ||
311 | $loginManager->checkLoginState($_COOKIE, $clientIpId); | |
312 | @@ -205,6 +225,7 @@ function isLoggedIn() | |
313 | && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password']) | |
314 | ) { | |
315 | $loginManager->handleSuccessfulLogin($_SERVER); | |
316 | + $userspace = $_POST['login']; | |
317 | ||
318 | $cookiedir = ''; | |
319 | if (dirname($_SERVER['SCRIPT_NAME']) != '/') { | |
320 | @@ -241,25 +262,25 @@ function isLoggedIn() | |
321 | $uri .= '&'.$param.'='.urlencode($_GET[$param]); | |
322 | } | |
323 | } | |
324 | - header('Location: '. $uri); | |
325 | + header('Location: '. $userspace . $uri); | |
326 | exit; | |
327 | } | |
328 | ||
329 | if (isset($_GET['edit_link'])) { | |
330 | - header('Location: ?edit_link='. escape($_GET['edit_link'])); | |
331 | + header('Location: ' . $userspace . '?edit_link='. escape($_GET['edit_link'])); | |
332 | exit; | |
333 | } | |
334 | ||
335 | if (isset($_POST['returnurl'])) { | |
336 | // Prevent loops over login screen. | |
337 | if (strpos($_POST['returnurl'], 'do=login') === false) { | |
338 | - header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); | |
339 | + header('Location: ' . generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST'])); | |
340 | exit; | |
341 | } | |
342 | } | |
343 | - header('Location: ?'); exit; | |
344 | + header('Location: '. $userspace . '?'); exit; | |
345 | } else { | |
346 | - $loginManager->handleFailedLogin($_SERVER); | |
347 | + $errorReason = $loginManager->handleFailedLogin($_SERVER); | |
348 | $redir = '&username='. urlencode($_POST['login']); | |
349 | if (isset($_GET['post'])) { | |
350 | $redir .= '&post=' . urlencode($_GET['post']); | |
351 | @@ -270,7 +291,7 @@ function isLoggedIn() | |
352 | } | |
353 | } | |
354 | // Redirect to login screen. | |
355 | - echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'?do=login'.$redir.'\';</script>'; | |
356 | + echo '<script>alert("'. t($errorReason) .'");document.location=\'?do=login'.$redir.'\';</script>'; | |
357 | exit; | |
358 | } | |
359 | } | |
360 | @@ -1719,7 +1740,7 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager) | |
361 | * @param SessionManager $sessionManager SessionManager instance | |
362 | * @param LoginManager $loginManager LoginManager instance | |
363 | */ | |
364 | -function install($conf, $sessionManager, $loginManager) { | |
365 | +function install($conf, $sessionManager, $loginManager, $userspace) { | |
366 | // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. | |
367 | if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); | |
368 | ||
369 | @@ -1755,7 +1776,7 @@ function install($conf, $sessionManager, $loginManager) { | |
370 | } | |
371 | ||
372 | ||
373 | - if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) | |
374 | + if (true) | |
375 | { | |
376 | $tz = 'UTC'; | |
377 | if (!empty($_POST['continent']) && !empty($_POST['city']) | |
378 | @@ -1764,15 +1785,15 @@ function install($conf, $sessionManager, $loginManager) { | |
379 | $tz = $_POST['continent'].'/'.$_POST['city']; | |
380 | } | |
381 | $conf->set('general.timezone', $tz); | |
382 | - $login = $_POST['setlogin']; | |
383 | - $conf->set('credentials.login', $login); | |
384 | + $conf->set('credentials.login', $userspace); | |
385 | $salt = sha1(uniqid('', true) .'_'. mt_rand()); | |
386 | $conf->set('credentials.salt', $salt); | |
387 | - $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt)); | |
388 | + $hash = sha1(uniqid('', true) .'_'. mt_rand()); | |
389 | + $conf->set('credentials.hash', $hash); | |
390 | if (!empty($_POST['title'])) { | |
391 | $conf->set('general.title', escape($_POST['title'])); | |
392 | } else { | |
393 | - $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER))); | |
394 | + $conf->set('general.title', ucwords(str_replace("_", " ", $userspace))); | |
395 | } | |
396 | $conf->set('translation.language', escape($_POST['language'])); | |
397 | $conf->set('updates.check_updates', !empty($_POST['updateCheck'])); | |
398 | @@ -1841,7 +1862,12 @@ function install($conf, $sessionManager, $loginManager) { | |
399 | $app = new \Slim\App($container); | |
400 | ||
401 | // REST API routes | |
402 | -$app->group('/api/v1', function() { | |
403 | +if (isset($userspace)) { | |
404 | + $mountpoint = '/' . $userspace . '/api/v1'; | |
405 | +} else { | |
406 | + $mountpoint = '/api/v1'; | |
407 | +} | |
408 | +$app->group($mountpoint, function() { | |
409 | $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo'); | |
410 | $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks'); | |
411 | $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink'); | |
412 | @@ -1860,7 +1886,7 @@ function install($conf, $sessionManager, $loginManager) { | |
413 | $response = $app->run(true); | |
414 | // Hack to make Slim and Shaarli router work together: | |
415 | // If a Slim route isn't found and NOT API call, we call renderPage(). | |
416 | -if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) { | |
417 | +if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], $mountpoint) === false) { | |
418 | // We use UTF-8 for proper international characters handling. | |
419 | header('Content-Type: text/html; charset=utf-8'); | |
420 | renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager); |