]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Add ldap connection gitolite_local/ldap
authorIsmaël Bouya <ismael.bouya@normalesup.org>
Sun, 3 Feb 2019 19:58:18 +0000 (20:58 +0100)
committerIsmaël Bouya <ismael.bouya@normalesup.org>
Mon, 8 Feb 2021 00:23:56 +0000 (01:23 +0100)
.htaccess
application/ApplicationUtils.php
application/config/ConfigManager.php
application/security/LoginManager.php
application/security/SessionManager.php
index.php

index 4c00427195ecb0998589fc09dd02fa8580d97a3e..5acd708e76c6480ef9dedb71ab6ff9c1509bad90 100644 (file)
--- a/.htaccess
+++ b/.htaccess
@@ -6,10 +6,23 @@ RewriteEngine On
 # Prevent accessing subdirectories not managed by SCM
 RewriteRule ^(.git|doxygen|vendor) - [F]
 
+RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
+RewriteRule ^(.*) - [E=BASE:%1]
+
+RewriteCond %{ENV:REDIRECT_BASE} (.+)
+RewriteRule .* - [E=BASE:%1]
+
 # Forward the "Authorization" HTTP header
 RewriteCond %{HTTP:Authorization} ^(.*)
 RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
 
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^((?!api/)[^/]*)/?(.*)$ $2?%{QUERY_STRING} [E=USERSPACE:$1]
+
+RewriteCond %{ENV:REDIRECT_USERSPACE} (.+)
+RewriteRule .* - [E=USERSPACE:%1]
+
 # REST API
 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
index 911873a071ca7813f7ef9e3fa75d8ce18b2f8ec8..f21a1ef3fcbde7a7911dfdf60c1bf4c6da30806e 100644 (file)
@@ -191,6 +191,9 @@ class ApplicationUtils
             $conf->get('resource.page_cache'),
             $conf->get('resource.raintpl_tmp'),
         ) as $path) {
+            if (! is_dir($path)) {
+                mkdir($path, 0755, true);
+            }
             if (! is_readable(realpath($path))) {
                 $errors[] = '"'.$path.'" '. t('directory is not readable');
             }
index 32aaea48e870ab13af7714c5a53d84cf0356d613..99efc1567c85bd7b0f398a1d194a9200d5dc9adb 100644 (file)
@@ -21,6 +21,11 @@ class ConfigManager
 
     public static $DEFAULT_PLUGINS = array('qrcode');
 
+    /**
+     * @var string User space.
+     */
+    protected $userSpace;
+
     /**
      * @var string Config folder.
      */
@@ -41,12 +46,36 @@ class ConfigManager
      *
      * @param string $configFile Configuration file path without extension.
      */
-    public function __construct($configFile = 'data/config')
+    public function __construct($configFile = null, $userSpace = null)
     {
-        $this->configFile = $configFile;
+        $this->userSpace = $this->findLDAPUser($userSpace);
+        if ($configFile !== null) {
+            $this->configFile = $configFile;
+        } else {
+            $this->configFile = ($this->userSpace === null) ? 'data/config' : 'data/' . $this->userSpace . '/config';
+        }
         $this->initialize();
     }
 
+    public function findLDAPUser($login, $password = null) {
+        $connect = ldap_connect(getenv('SHAARLI_LDAP_HOST'));
+        ldap_set_option($connect, LDAP_OPT_PROTOCOL_VERSION, 3);
+        if (!$connect || !ldap_bind($connect, getenv('SHAARLI_LDAP_DN'), getenv('SHAARLI_LDAP_PASSWORD'))) {
+            return false;
+        }
+
+        $search_query = str_replace('%login%', ldap_escape($login), getenv('SHAARLI_LDAP_FILTER'));
+
+        $search = ldap_search($connect, getenv('SHAARLI_LDAP_BASE'), $search_query);
+        $info = ldap_get_entries($connect, $search);
+
+        if (ldap_count_entries($connect, $search) == 1 && (is_null($password) || ldap_bind($connect, $info[0]["dn"], $password))) {
+            return $login;
+        } else {
+            return null;
+        }
+    }
+
     /**
      * Reset the ConfigManager instance.
      */
@@ -269,6 +298,16 @@ class ConfigManager
         return $this->configFile . $this->configIO->getExtension();
     }
 
+    /**
+     * Get the current userspace.
+     *
+     * @return mixed User space.
+     */
+    public function getUserSpace()
+    {
+      return $this->userSpace;
+    }
+
     /**
      * Recursive function which find asked setting in the loaded config.
      *
@@ -342,19 +381,31 @@ class ConfigManager
      */
     protected function setDefaultValues()
     {
-        $this->setEmpty('resource.data_dir', 'data');
-        $this->setEmpty('resource.config', 'data/config.php');
-        $this->setEmpty('resource.datastore', 'data/datastore.php');
-        $this->setEmpty('resource.ban_file', 'data/ipbans.php');
-        $this->setEmpty('resource.updates', 'data/updates.txt');
-        $this->setEmpty('resource.log', 'data/log.txt');
-        $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
-        $this->setEmpty('resource.history', 'data/history.php');
+        if ($this->userSpace === null) {
+          $data = 'data';
+          $tmp  = 'tmp';
+          $cache = 'cache';
+          $pagecache = 'pagecache';
+        } else {
+          $data = 'data/' . ($this->userSpace);
+          $tmp  = 'tmp/' . ($this->userSpace);
+          $cache = 'cache/' . ($this->userSpace);
+          $pagecache = 'pagecache/' . ($this->userSpace);
+        }
+
+        $this->setEmpty('resource.data_dir', $data);
+        $this->setEmpty('resource.config', $data . '/config.php');
+        $this->setEmpty('resource.datastore', $data . '/datastore.php');
+        $this->setEmpty('resource.ban_file', $data . '/ipbans.php');
+        $this->setEmpty('resource.updates', $data . '/updates.txt');
+        $this->setEmpty('resource.log', $data . '/log.txt');
+        $this->setEmpty('resource.update_check', $data . '/lastupdatecheck.txt');
+        $this->setEmpty('resource.history', $data . '/history.php');
         $this->setEmpty('resource.raintpl_tpl', 'tpl/');
         $this->setEmpty('resource.theme', 'default');
-        $this->setEmpty('resource.raintpl_tmp', 'tmp/');
-        $this->setEmpty('resource.thumbnails_cache', 'cache');
-        $this->setEmpty('resource.page_cache', 'pagecache');
+        $this->setEmpty('resource.raintpl_tmp', $tmp);
+        $this->setEmpty('resource.thumbnails_cache', $cache);
+        $this->setEmpty('resource.page_cache', $pagecache);
 
         $this->setEmpty('security.ban_after', 4);
         $this->setEmpty('security.ban_duration', 1800);
index d6784d6da6424e266a21702b7f0686746e91fe20..bdfaca7b29f05e4c46a8f79e848990bd18d3d33f 100644 (file)
@@ -32,6 +32,9 @@ class LoginManager
     /** @var string User sign-in token depending on remote IP and credentials */
     protected $staySignedInToken = '';
 
+    protected $lastErrorReason = '';
+    protected $lastErrorIsBanishable = false;
+
     /**
      * Constructor
      *
@@ -83,7 +86,7 @@ class LoginManager
      */
     public function checkLoginState($cookie, $clientIpId)
     {
-        if (! $this->configManager->exists('credentials.login')) {
+        if (! $this->configManager->exists('credentials.login') || (isset($_SESSION['username']) && $_SESSION['username'] && $this->configManager->get('credentials.login') !== $_SESSION['username'])) {
             // Shaarli is not configured yet
             $this->isLoggedIn = false;
             return;
@@ -133,20 +136,40 @@ class LoginManager
      */
     public function checkCredentials($remoteIp, $clientIpId, $login, $password)
     {
-        $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
+        $this->lastErrorIsBanishable = false;
+
+        if ($this->configManager->getUserSpace() !== null && $this->configManager->getUserSpace() !== $login) {
+          logm($this->configManager->get('resource.log'),
+               $remoteIp,
+               'Trying to login to wrong user space');
+          $this->lastErrorReason = 'You’re trying to access the wrong account.';
+          return false;
+        }
 
-        if ($login != $this->configManager->get('credentials.login')
-            || $hash != $this->configManager->get('credentials.hash')
-        ) {
+        logm($this->configManager->get('resource.log'),
+             $remoteIp,
+             'Trying LDAP connection');
+        $result = $this->configManager->findLDAPUser($login, $password);
+        if ($result === false) {
             logm(
                 $this->configManager->get('resource.log'),
                 $remoteIp,
-                'Login failed for user ' . $login
+                'Impossible to connect to LDAP'
             );
+            $this->lastErrorReason = 'Server error.';
+            return false;
+        } else if (is_null($result)) {
+            logm(
+              $this->configManager->get('resource.log'),
+              $remoteIp,
+              'Login failed for user ' . $login
+            );
+            $this->lastErrorIsBanishable = true;
+            $this->lastErrorReason = 'Wrong login/password.';
             return false;
         }
 
-        $this->sessionManager->storeLoginInfo($clientIpId);
+        $this->sessionManager->storeLoginInfo($clientIpId, $login);
         logm(
             $this->configManager->get('resource.log'),
             $remoteIp,
@@ -187,6 +210,10 @@ class LoginManager
      */
     public function handleFailedLogin($server)
     {
+        if (!$this->lastErrorIsBanishable) {
+          return $this->lastErrorReason ?: 'Error during login.';
+        };
+
         $ip = $server['REMOTE_ADDR'];
         $trusted = $this->configManager->get('security.trusted_proxies', []);
 
@@ -215,6 +242,7 @@ class LoginManager
             );
         }
         $this->writeBanFile();
+        return $this->lastErrorReason ?: 'Error during login.';
     }
 
     /**
index b8b8ab8d18ccad003f897c78072b12e94d410b22..5eb4aac55d4be4b0681e96470269f85ee09dffaf 100644 (file)
@@ -111,10 +111,10 @@ class SessionManager
      *
      * @param string $clientIpId Client IP address identifier
      */
-    public function storeLoginInfo($clientIpId)
+    public function storeLoginInfo($clientIpId, $login = null)
     {
         $this->session['ip'] = $clientIpId;
-        $this->session['username'] = $this->conf->get('credentials.login');
+        $this->session['username'] = $login ?: $this->conf->get('credentials.login');
         $this->extendTimeValidityBy(self::$SHORT_TIMEOUT);
     }
 
index 4b86a3e2c17ece7338ad04646cedee6150bbbd47..58ae2ddb780a9315755df398c77eaa5773b0e712 100644 (file)
--- a/index.php
+++ b/index.php
@@ -121,7 +121,32 @@ if (isset($_COOKIE['shaarli']) && !SessionManager::checkId($_COOKIE['shaarli']))
     $_COOKIE['shaarli'] = session_id();
 }
 
-$conf = new ConfigManager();
+$folderBase = getenv("BASE");
+
+if (getenv("USERSPACE")) {
+    if (isset($_GET["do"]) && $_GET["do"] == "login") {
+        header("Location: $folderBase/?do=login");
+        exit;
+    }
+    $userspace = preg_replace("/[^-_A-Za-z0-9]/", '', getenv("USERSPACE"));
+} else if (isset($_SESSION["username"]) && $_SESSION["username"]) {
+    header("Location: " . $folderBase . "/" . $_SESSION["username"] . "?");
+    exit;
+} else if (!isset($_GET["do"]) || $_GET["do"] != "login") {
+    header("Location: $folderBase/?do=login");
+    exit;
+}
+
+if (!isset($userspace) && isset($_POST["login"])) {
+    $userspace = preg_replace("/[^-_A-Za-z0-9]/", '', $_POST["login"]);
+    error_log("debugImmae: setting userspace from POST: " . $userspace);
+}
+
+if (isset($userspace)) {
+  $conf = new ConfigManager(null, $userspace);
+} else {
+  $conf = new ConfigManager();
+}
 $sessionManager = new SessionManager($_SESSION, $conf);
 $loginManager = new LoginManager($GLOBALS, $conf, $sessionManager);
 $loginManager->generateStaySignedInToken($_SERVER['REMOTE_ADDR']);
@@ -175,7 +200,7 @@ if (! is_file($conf->getConfigFileExt())) {
     }
 
     // Display the installation form if no existing config is found
-    install($conf, $sessionManager, $loginManager);
+    install($conf, $sessionManager, $loginManager, $userspace);
 }
 
 $loginManager->checkLoginState($_COOKIE, $clientIpId);
@@ -205,6 +230,7 @@ if (isset($_POST['login'])) {
         && $loginManager->checkCredentials($_SERVER['REMOTE_ADDR'], $clientIpId, $_POST['login'], $_POST['password'])
     ) {
         $loginManager->handleSuccessfulLogin($_SERVER);
+        $userspace = $_POST['login'];
 
         $cookiedir = '';
         if (dirname($_SERVER['SCRIPT_NAME']) != '/') {
@@ -241,25 +267,25 @@ if (isset($_POST['login'])) {
                     $uri .= '&'.$param.'='.urlencode($_GET[$param]);
                 }
             }
-            header('Location: '. $uri);
+            header('Location: '. $userspace . $uri);
             exit;
         }
 
         if (isset($_GET['edit_link'])) {
-            header('Location: ?edit_link='. escape($_GET['edit_link']));
+            header('Location: ' . $userspace . '?edit_link='. escape($_GET['edit_link']));
             exit;
         }
 
         if (isset($_POST['returnurl'])) {
             // Prevent loops over login screen.
             if (strpos($_POST['returnurl'], 'do=login') === false) {
-                header('Location: '. generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST']));
+                header('Location: ' . generateLocation($_POST['returnurl'], $_SERVER['HTTP_HOST']));
                 exit;
             }
         }
-        header('Location: ?'); exit;
+        header('Location: '. $userspace . '?'); exit;
     } else {
-        $loginManager->handleFailedLogin($_SERVER);
+        $errorReason = $loginManager->handleFailedLogin($_SERVER);
         $redir = '&username='. urlencode($_POST['login']);
         if (isset($_GET['post'])) {
             $redir .= '&post=' . urlencode($_GET['post']);
@@ -270,7 +296,7 @@ if (isset($_POST['login'])) {
             }
         }
         // Redirect to login screen.
-        echo '<script>alert("'. t("Wrong login/password.") .'");document.location=\'?do=login'.$redir.'\';</script>';
+        echo '<script>alert("'. t($errorReason) .'");document.location=\'?do=login'.$redir.'\';</script>';
         exit;
     }
 }
@@ -1719,7 +1745,7 @@ function buildLinkList($PAGE, $LINKSDB, $conf, $pluginManager, $loginManager)
  * @param SessionManager $sessionManager SessionManager instance
  * @param LoginManager   $loginManager   LoginManager instance
  */
-function install($conf, $sessionManager, $loginManager) {
+function install($conf, $sessionManager, $loginManager, $userspace) {
     // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
     if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
 
@@ -1755,7 +1781,7 @@ function install($conf, $sessionManager, $loginManager) {
     }
 
 
-    if (!empty($_POST['setlogin']) && !empty($_POST['setpassword']))
+    if (true)
     {
         $tz = 'UTC';
         if (!empty($_POST['continent']) && !empty($_POST['city'])
@@ -1764,15 +1790,15 @@ function install($conf, $sessionManager, $loginManager) {
             $tz = $_POST['continent'].'/'.$_POST['city'];
         }
         $conf->set('general.timezone', $tz);
-        $login = $_POST['setlogin'];
-        $conf->set('credentials.login', $login);
+        $conf->set('credentials.login', $userspace);
         $salt = sha1(uniqid('', true) .'_'. mt_rand());
         $conf->set('credentials.salt', $salt);
-        $conf->set('credentials.hash', sha1($_POST['setpassword'] . $login . $salt));
+        $hash = sha1(uniqid('', true) .'_'. mt_rand());
+        $conf->set('credentials.hash', $hash);
         if (!empty($_POST['title'])) {
             $conf->set('general.title', escape($_POST['title']));
         } else {
-            $conf->set('general.title', 'Shared links on '.escape(index_url($_SERVER)));
+            $conf->set('general.title', ucwords(str_replace("_", " ", $userspace)));
         }
         $conf->set('translation.language', escape($_POST['language']));
         $conf->set('updates.check_updates', !empty($_POST['updateCheck']));
@@ -1841,7 +1867,12 @@ $container['history'] = $history;
 $app = new \Slim\App($container);
 
 // REST API routes
-$app->group('/api/v1', function() {
+if (isset($userspace)) {
+  $mountpoint = '/' . $userspace . '/api/v1';
+} else {
+  $mountpoint = '/api/v1';
+}
+$app->group($mountpoint, function() {
     $this->get('/info', '\Shaarli\Api\Controllers\Info:getInfo')->setName('getInfo');
     $this->get('/links', '\Shaarli\Api\Controllers\Links:getLinks')->setName('getLinks');
     $this->get('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:getLink')->setName('getLink');
@@ -1860,7 +1891,7 @@ $app->group('/api/v1', function() {
 $response = $app->run(true);
 // Hack to make Slim and Shaarli router work together:
 // If a Slim route isn't found and NOT API call, we call renderPage().
-if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], '/api/v1') === false) {
+if ($response->getStatusCode() == 404 && strpos($_SERVER['REQUEST_URI'], $mountpoint) === false) {
     // We use UTF-8 for proper international characters handling.
     header('Content-Type: text/html; charset=utf-8');
     renderPage($conf, $pluginManager, $linkDb, $history, $sessionManager, $loginManager);