aboutsummaryrefslogblamecommitdiffhomepage
path: root/application/LoginManager.php
blob: 8f6bf0da82a0d19c7665f650d220044ede81f57f (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                    
                                     
                            

                                   



                  


                                                                           
       
                                                                            


                                              
                                                

                                                                                           

































































































                                                                                           













































































































                                                                                                                      
<?php
namespace Shaarli;

/**
 * User login management
 */
class LoginManager
{
    protected $globals = [];
    protected $configManager = null;
    protected $sessionManager = null;
    protected $banFile = '';
    protected $isLoggedIn = false;
    protected $openShaarli = false;

    /**
     * Constructor
     *
     * @param array          $globals        The $GLOBALS array (reference)
     * @param ConfigManager  $configManager  Configuration Manager instance
     * @param SessionManager $sessionManager SessionManager instance
     */
    public function __construct(& $globals, $configManager, $sessionManager)
    {
        $this->globals = &$globals;
        $this->configManager = $configManager;
        $this->sessionManager = $sessionManager;
        $this->banFile = $this->configManager->get('resource.ban_file', 'data/ipbans.php');
        $this->readBanFile();
        if ($this->configManager->get('security.open_shaarli')) {
            $this->openShaarli = true;
        }
    }

    /**
     * Check user session state and validity (expiration)
     *
     * @param array  $server  The $_SERVER array
     * @param array  $session The $_SESSION array (reference)
     * @param array  $cookie  The $_COOKIE array
     * @param string $webPath Path on the server in which the cookie will be available on
     * @param string $token   Session token
     *
     * @return bool true if the user session is valid, false otherwise
     */
    public function checkLoginState($server, & $session, $cookie, $webPath, $token)
    {
        if (! $this->configManager->exists('credentials.login')) {
            // Shaarli is not configured yet
            $this->isLoggedIn = false;
            return;
        }

        if (isset($cookie[SessionManager::$LOGGED_IN_COOKIE])
            && $cookie[SessionManager::$LOGGED_IN_COOKIE] === $token
        ) {
            $this->sessionManager->storeLoginInfo($server);
            $this->isLoggedIn = true;
        }

        // Logout when:
        // - the session does not exist on the server side
        // - the session has expired
        // - the client IP address has changed
        if (empty($session['uid'])
            || ($this->configManager->get('security.session_protection_disabled') === false
                && $session['ip'] != client_ip_id($server))
            || time() >= $session['expires_on']
        ) {
            $this->sessionManager->logout($webPath);
            $this->isLoggedIn = false;
            return;
        }

        // Extend session validity
        if (! empty($session['longlastingsession'])) {
            // "Stay signed in" is enabled
            $session['expires_on'] = time() + $session['longlastingsession'];
        } else {
            $session['expires_on'] = time() + SessionManager::$INACTIVITY_TIMEOUT;
        }
    }

    /**
     * Return whether the user is currently logged in
     *
     * @return true when the user is logged in, false otherwise
     */
    public function isLoggedIn()
    {
        if ($this->openShaarli) {
            return true;
        }
        return $this->isLoggedIn;
    }

    /**
     * Check user credentials are valid
     *
     * @param array  $server   The $_SERVER array
     * @param string $login    Username
     * @param string $password Password
     *
     * @return bool true if the provided credentials are valid, false otherwise
     */
    public function checkCredentials($server, $login, $password)
    {
        $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));

        if ($login != $this->configManager->get('credentials.login')
            || $hash != $this->configManager->get('credentials.hash')
        ) {
            logm(
                $this->configManager->get('resource.log'),
                $server['REMOTE_ADDR'],
                'Login failed for user ' . $login
            );
            return false;
        }

        $this->sessionManager->storeLoginInfo($server);
        logm(
            $this->configManager->get('resource.log'),
            $server['REMOTE_ADDR'],
            'Login successful'
        );
        return true;
    }

    /**
     * Read a file containing banned IPs
     */
    protected function readBanFile()
    {
        if (! file_exists($this->banFile)) {
            return;
        }
        include $this->banFile;
    }

    /**
     * Write the banned IPs to a file
     */
    protected function writeBanFile()
    {
        if (! array_key_exists('IPBANS', $this->globals)) {
            return;
        }
        file_put_contents(
            $this->banFile,
            "<?php\n\$GLOBALS['IPBANS']=" . var_export($this->globals['IPBANS'], true) . ";\n?>"
        );
    }

    /**
     * Handle a failed login and ban the IP after too many failed attempts
     *
     * @param array $server The $_SERVER array
     */
    public function handleFailedLogin($server)
    {
        $ip = $server['REMOTE_ADDR'];
        $trusted = $this->configManager->get('security.trusted_proxies', []);

        if (in_array($ip, $trusted)) {
            $ip = getIpAddressFromProxy($server, $trusted);
            if (! $ip) {
                // the IP is behind a trusted forward proxy, but is not forwarded
                // in the HTTP headers, so we do nothing
                return;
            }
        }

        // increment the fail count for this IP
        if (isset($this->globals['IPBANS']['FAILURES'][$ip])) {
            $this->globals['IPBANS']['FAILURES'][$ip]++;
        } else {
            $this->globals['IPBANS']['FAILURES'][$ip] = 1;
        }

        if ($this->globals['IPBANS']['FAILURES'][$ip] >= $this->configManager->get('security.ban_after')) {
            $this->globals['IPBANS']['BANS'][$ip] = time() + $this->configManager->get('security.ban_duration', 1800);
            logm(
                $this->configManager->get('resource.log'),
                $server['REMOTE_ADDR'],
                'IP address banned from login'
            );
        }
        $this->writeBanFile();
    }

    /**
     * Handle a successful login
     *
     * @param array $server The $_SERVER array
     */
    public function handleSuccessfulLogin($server)
    {
        $ip = $server['REMOTE_ADDR'];
        // FIXME unban when behind a trusted proxy?

        unset($this->globals['IPBANS']['FAILURES'][$ip]);
        unset($this->globals['IPBANS']['BANS'][$ip]);

        $this->writeBanFile();
    }

    /**
     * Check if the user can login from this IP
     *
     * @param array $server The $_SERVER array
     *
     * @return bool true if the user is allowed to login
     */
    public function canLogin($server)
    {
        $ip = $server['REMOTE_ADDR'];

        if (! isset($this->globals['IPBANS']['BANS'][$ip])) {
            // the user is not banned
            return true;
        }

        if ($this->globals['IPBANS']['BANS'][$ip] > time()) {
            // the user is still banned
            return false;
        }

        // the ban has expired, the user can attempt to log in again
        logm($this->configManager->get('resource.log'), $server['REMOTE_ADDR'], 'Ban lifted.');
        unset($this->globals['IPBANS']['FAILURES'][$ip]);
        unset($this->globals['IPBANS']['BANS'][$ip]);

        $this->writeBanFile();
        return true;
    }
}