]> git.immae.eu Git - perso/Immae/Config/Nix/NUR.git/blob - overlays/shaarli/shaarli_ldap.patch
Initial commit published for NUR
[perso/Immae/Config/Nix/NUR.git] / overlays / shaarli / shaarli_ldap.patch
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);