<?php

/**
 * Code related to the lastlogins-blocked.php interface.
 *
 * @package Sucuri Security
 * @subpackage lastlogins-blocked.php
 * @copyright Since 2010 Sucuri Inc.
 */

if (!defined('SUCURISCAN_INIT') || SUCURISCAN_INIT !== true) {
    if (!headers_sent()) {
        /* Report invalid access if possible. */
        header('HTTP/1.1 403 Forbidden');
    }
    exit(1);
}

/**
 * Allows the website owner to block usernames.
 *
 * An admin can select one or more usernames from the list of failed login and
 * block them so the next time someone tries to log into the website with that
 * username the operation will be stopped before the request hits the database.
 *
 * Notice that this feature does not allows the website owner to block requests
 * coming from a specific IP address. This is because the feature already exists
 * in the Firewall service and it also provides a better filtering mechanism for
 * any other suspicious login attempt. We will encourage people to leverage the
 * power of the Firewall.
 */
class SucuriScanBlockedUsers extends SucuriScanLastLogins
{
    /**
     * Renders the page with a list of blocked usernames.
     *
     * @return string HTML code with a list of blocked usernames.
     */
    public static function page()
    {
        $output = array();
        $output['BlockedUsers.List'] = '';
        $output['BlockedUsers.NoItemsVisibility'] = 'visible';

        if (SucuriScanInterface::checkNonce()) {
            $unblockUsers = SucuriScanRequest::post(':unblock_user', '_array');

            if (is_array($unblockUsers) && !empty($unblockUsers)) {
                self::unblock($unblockUsers);
                SucuriScanInterface::info(__('AccountsWereUnblocked', SUCURISCAN_TEXTDOMAIN));
            }
        }

        $cache = new SucuriScanCache('blockedusers', false);
        $blocked = $cache->getAll();

        if (is_array($blocked) && !empty($blocked)) {
            foreach ($blocked as $data) {
                $output['BlockedUsers.List'] .=
                SucuriScanTemplate::getSnippet('lastlogins-blockedusers', array(
                    'BlockedUsers.Username' => $data->username,
                    'BlockedUsers.BlockedAt' => self::datetime($data->blocked_at),
                    'BlockedUsers.FirstAttempt' => self::datetime($data->first_attempt),
                    'BlockedUsers.LastAttempt' => self::datetime($data->last_attempt),
                ));
            }

            $output['BlockedUsers.NoItemsVisibility'] = 'hidden';
        }

        return SucuriScanTemplate::getSection('lastlogins-blockedusers', $output);
    }

    /**
     * Blocks one or more usernames.
     *
     * @param array $users List of usernames.
     */
    public static function block($users = array())
    {
        if (is_array($users) && !empty($users)) {
            $logs = sucuriscan_get_all_failed_logins();
            $cache = new SucuriScanCache('blockedusers');
            $blocked = $cache->getAll();

            foreach ($users as $user) {
                if (array_key_exists($user, $blocked)) {
                    continue;
                }

                $firstAttempt = self::firstAttempt($logs, $user);
                $lastAttempt = self::lastAttempt($logs, $user);
                $data = array(
                    'username' => $user,
                    'blocked_at' => time(),
                    'first_attempt' => $firstAttempt,
                    'last_attempt' => $lastAttempt,
                );
                $cache->add(md5($user), $data);
            }
        }
    }

    /**
     * Unblocks one or more usernames.
     *
     * @param array $users List of usernames.
     */
    public static function unblock($users = array())
    {
        if (is_array($users) && !empty($users)) {
            $cache = new SucuriScanCache('blockedusers');
            $blocked = $cache->getAll();

            foreach ($users as $user) {
                $cache_key = md5($user);

                if (array_key_exists($cache_key, $blocked)) {
                    $cache->delete($cache_key);
                }
            }
        }
    }

    /**
     * Stops an user login attempt.
     *
     * This method will run right after the user submits the login form. We will
     * check to see if the username has been blocked by an admin and proceed
     * according to the expected behavior. Either we will stop the request right
     * here or let it propagate to the authentication checker.
     */
    public static function blockUserLogin()
    {
        if (class_exists('SucuriScanRequest')
            && class_exists('SucuriScanCache')
        ) {
            $username = SucuriScanRequest::post('log');
            $password = SucuriScanRequest::post('pwd');

            if ($username !== false && $password !== false) {
                $cache = new SucuriScanCache('blockedusers');
                $blocked = $cache->getAll();
                $cache_key = md5($username);

                if (is_array($blocked)
                    && is_string($cache_key)
                    && array_key_exists($cache_key, $blocked)
                ) {
                    $blocked[$cache_key]->last_attempt = time();
                    $cache->set($cache_key, $blocked[$cache_key]);

                    if (!headers_sent()) {
                        header('HTTP/1.1 403 Forbidden');
                    }

                    exit(0);
                }
            }
        }
    }

    /**
     * Finds the first login attempt of a specific username.
     *
     * @param array $logs List of failed login attempts.
     * @param string $user Username to be inspected.
     * @return int Timestamp of the first login attempt.
     */
    private static function firstAttempt($logs, $user)
    {
        $attempts = array();

        foreach ($logs['entries'] as $login) {
            if ($login['user_login'] === $user) {
                $attempts[] = $login['attempt_time'];
            }
        }

        if (empty($attempts)) {
            return -1;
        }

        return min($attempts);
    }

    /**
     * Finds the last login attempt of a specific username.
     *
     * @param array $logs List of failed login attempts.
     * @param string $user Username to be inspected.
     * @return int Timestamp of the last login attempt.
     */
    private static function lastAttempt($logs, $user)
    {
        $attempts = array();

        foreach ($logs['entries'] as $login) {
            if ($login['user_login'] === $user) {
                $attempts[] = $login['attempt_time'];
            }
        }

        if (empty($attempts)) {
            return -1;
        }

        return max($attempts);
    }
}
