<commit_before><?php

namespace Bolt;

use Silex;

/**
 * Class to handle things dealing with users..
 */
class Users
{
    const ANONYMOUS = 0;
    const EDITOR = 2;
    const ADMIN = 4;
    const DEVELOPER = 6;

    public $db;
    public $config;
    public $usertable;
    public $sessiontable;
    public $users;
    public $session;
    public $currentuser;
    public $allowed;
    private $hash_strength;

    public function __construct(Silex\Application $app)
    {
        $this->app = $app;

        $prefix = isset($this->config['general']['database']['prefix']) ? $this->config['general']['database']['prefix'] : "bolt_";

        // Hashstrength has a default of '10', don't allow less than '8'.
        $this->hash_strength = max($this->config['general']['hash_strength'], 8);

        $this->db = $app['db'];
        $this->config = $app['config'];
        $this->usertable = $prefix . "users";
        $this->users = array();
        $this->session = $app['session'];

        $this->checkValidSession();

        $this->allowed = array(
            'dashboard' => self::EDITOR,
            'settings' => self::ADMIN,
            'login' => self::ANONYMOUS,
            'logout' => self::EDITOR,
            'dbupdate' => self::ADMIN,
            'clearcache' => self::ADMIN,
            'prefill' => self::DEVELOPER,
            'users' => self::ADMIN,
            'useredit' => self::ADMIN,
            'useraction' => self::ADMIN,
            'overview' => self::EDITOR,
            'editcontent' => self::EDITOR,
            'editcontent:own' => self::EDITOR,
            'editcontent:all' => self::ADMIN,
            'contentaction' => self::EDITOR,
            'about' => self::EDITOR,
            'extensions' => self::DEVELOPER,
            'files' => self::ADMIN,
            'files:config' => self::DEVELOPER,
            'files:theme' => self::DEVELOPER,
            'files:uploads' => self::ADMIN,
            'activitylog' => self::ADMIN,
            'fileedit' => self::ADMIN
        );

    }

    /**
     * Save changes to a user to the database. (re)hashing the password, if needed.
     *
     * @param  array $user
     * @return mixed
     */
    public function saveUser($user)
    {
        // Make an array with the allowed columns. these are the columns that are always present.
        $allowedcolumns = array('id', 'username', 'password', 'email', 'lastseen', 'lastip', 'displayname', 'userlevel', 'enabled', 'contenttypes');

        // unset columns we don't need to store..
        foreach ($user as $key => $value) {
            if (!in_array($key, $allowedcolumns)) {
                unset($user[$key]);
            }
        }

        if (!empty($user['password']) && $user['password']!="**<PASSWORD>**") {
            $hasher = new \Hautelook\Phpass\PasswordHash($this->hash_strength, true);
            $user['password'] = $hasher->HashPassword($user['password']);
        } else {
            unset($user['password']);
        }

        // make sure the username is slug-like
        $user['username'] = makeSlug($user['username']);

        if (!isset($user['lastseen'])) {
            $user['lastseen'] = "0000-00-00";
        }

        if (!isset($user['userlevel'])) {
            $user['userlevel'] = key(array_slice($this->getUserLevels(), -1));
        }

        if (!isset($user['enabled'])) {
            $user['enabled'] = 1;
        }

        // Serialize the contenttypes..
        $user['contenttypes'] = serialize($user['contenttypes']);

        // Decide whether to insert a new record, or update an existing one.
        if (empty($user['id'])) {
            return $this->db->insert($this->usertable, $user);
        } else {
            return $this->db->update($this->usertable, $user, array('id' => $user['id']));
        }

    }

    /**
     * We will not allow tampering with sessions, so we make sure the current session
     * is still valid for the device on which it was created, and that the username,
     * ip-address are still the same.
     *
     */
    public function checkValidSession()
    {
        if ($this->app['session']->get('user')) {
            $session = $this->app['session']->get('user');
            $database = $this->getUser($session['id']);
            $this->currentuser = array_merge($session, $database);
        } else {
            // no current user, return without doing the rest.
            return false;
        }

        if (intval($this->currentuser['userlevel']) <= self::ANONYMOUS ) {
            $this->logout();
            return false;
        }

        // set the rights for each of the contenttypes for this user.
        foreach ($this->app['config']['contenttypes'] as $key => $contenttype) {
            if (in_array($key, $this->currentuser['contenttypes'])) {
                $this->allowed['contenttype:' . $key] = self::EDITOR;
            } else {
                $this->allowed['contenttype:' . $key] = self::ADMIN;
            }
        }

        $key = $this->getSessionKey($this->currentuser['username']);

        if ($key != $this->currentuser['sessionkey']) {
            $this->app['log']->add("keys don't match. Invalidating session: $key != " . $this->currentuser['sessionkey'], 2);
            $this->app['log']->add("Automatically logged out user '".$this->currentuser['username']."': Session data didn't match.", 3, '', 'issue');
            $this->logout();
            return false;
        }

        // Check if user is _still_ allowed to log on..
        if ( ($this->currentuser['userlevel'] < self::EDITOR) || !$this->currentuser['enabled'] ) {
            $this->logout();
            return false;
        }

        return true;

    }

    /**
     * Get a key to identify the session with.
     *
     * @param  string $name
     * @return string
     */
    private function getSessionKey($name = "")
    {

        if (empty($name)) {
            return false;
        }

        $key = $name;

        if ($this->app['config']['general']['cookies_use_remoteaddr']) {
            $key .= "-". $_SERVER['REMOTE_ADDR'];
        }
        if ($this->app['config']['general']['cookies_use_browseragent']) {
            $key .= "-". $_SERVER['HTTP_USER_AGENT'];
        }
        if ($this->app['config']['general']['cookies_use_httphost']) {
            $key .= "-". $_SERVER['HTTP_HOST'];
        }

        $key = md5($key);

        return $key;

    }


    /**
     * Remove a user from the database.
     *
     * @param  int  $id
     * @return bool
     */
    public function deleteUser($id)
    {
        $user = $this->getUser($id);

        if (empty($user['id'])) {
            $this->session->getFlashBag()->set('error', 'That user does not exist.');

            return false;
        } else {
            return $this->db->delete($this->usertable, array('id' => $user['id']));
        }

    }

    /**
     * Attempt to login a user with the given password
     *
     * @param  string $user
     * @param  string $password
     * @return bool
     */
    public function login($user, $password)
    {
        $userslug = makeSlug($user);

        // for once we don't use getUser(), because we need the password.
        $user = $this->db->fetchAssoc("SELECT * FROM " . $this->usertable . " WHERE username='$userslug'");

        if (empty($user)) {
            $this->session->getFlashBag()->set('error', 'Username or password not correct. Please check your input.');

            return false;
        }

        $hasher = new \Hautelook\Phpass\PasswordHash($this->hash_strength, true);

        if ($hasher->CheckPassword($password, $user['password'])) {

            if (!$user['enabled']) {
                $this->session->getFlashBag()->set('error', 'Your account is disabled. Sorry about that.');

                return false;
            }

            $update = array(
                'lastseen' => date('Y-m-d H:i:s'),
                'lastip' => $_SERVER['REMOTE_ADDR']
            );

            $this->db->update($this->usertable, $update, array('id' => $user['id']));

            $user = $this->getUser($user['id']);

            $user['sessionkey'] = $this->getSessionKey($user['username']);<commit_msg>Use prepared statement. Working on #203.<commit_after><?php

namespace Bolt;

use Silex;

/**
 * Class to handle things dealing with users..
 */
class Users
{
    const ANONYMOUS = 0;
    const EDITOR = 2;
    const ADMIN = 4;
    const DEVELOPER = 6;

    public $db;
    public $config;
    public $usertable;
    public $sessiontable;
    public $users;
    public $session;
    public $currentuser;
    public $allowed;
    private $hash_strength;

    public function __construct(Silex\Application $app)
    {
        $this->app = $app;

        $prefix = isset($this->config['general']['database']['prefix']) ? $this->config['general']['database']['prefix'] : "bolt_";

        // Hashstrength has a default of '10', don't allow less than '8'.
        $this->hash_strength = max($this->config['general']['hash_strength'], 8);

        $this->db = $app['db'];
        $this->config = $app['config'];
        $this->usertable = $prefix . "users";
        $this->users = array();
        $this->session = $app['session'];

        $this->checkValidSession();

        $this->allowed = array(
            'dashboard' => self::EDITOR,
            'settings' => self::ADMIN,
            'login' => self::ANONYMOUS,
            'logout' => self::EDITOR,
            'dbupdate' => self::ADMIN,
            'clearcache' => self::ADMIN,
            'prefill' => self::DEVELOPER,
            'users' => self::ADMIN,
            'useredit' => self::ADMIN,
            'useraction' => self::ADMIN,
            'overview' => self::EDITOR,
            'editcontent' => self::EDITOR,
            'editcontent:own' => self::EDITOR,
            'editcontent:all' => self::ADMIN,
            'contentaction' => self::EDITOR,
            'about' => self::EDITOR,
            'extensions' => self::DEVELOPER,
            'files' => self::ADMIN,
            'files:config' => self::DEVELOPER,
            'files:theme' => self::DEVELOPER,
            'files:uploads' => self::ADMIN,
            'activitylog' => self::ADMIN,
            'fileedit' => self::ADMIN
        );

    }

    /**
     * Save changes to a user to the database. (re)hashing the password, if needed.
     *
     * @param  array $user
     * @return mixed
     */
    public function saveUser($user)
    {
        // Make an array with the allowed columns. these are the columns that are always present.
        $allowedcolumns = array('id', 'username', 'password', 'email', 'lastseen', 'lastip', 'displayname', 'userlevel', 'enabled', 'contenttypes');

        // unset columns we don't need to store..
        foreach ($user as $key => $value) {
            if (!in_array($key, $allowedcolumns)) {
                unset($user[$key]);
            }
        }

        if (!empty($user['password']) && $user['password']!="**<PASSWORD>**") {
            $hasher = new \Hautelook\Phpass\PasswordHash($this->hash_strength, true);
            $user['password'] = $hasher->HashPassword($user['password']);
        } else {
            unset($user['password']);
        }

        // make sure the username is slug-like
        $user['username'] = makeSlug($user['username']);

        if (!isset($user['lastseen'])) {
            $user['lastseen'] = "0000-00-00";
        }

        if (!isset($user['userlevel'])) {
            $user['userlevel'] = key(array_slice($this->getUserLevels(), -1));
        }

        if (!isset($user['enabled'])) {
            $user['enabled'] = 1;
        }

        // Serialize the contenttypes..
        $user['contenttypes'] = serialize($user['contenttypes']);

        // Decide whether to insert a new record, or update an existing one.
        if (empty($user['id'])) {
            return $this->db->insert($this->usertable, $user);
        } else {
            return $this->db->update($this->usertable, $user, array('id' => $user['id']));
        }

    }

    /**
     * We will not allow tampering with sessions, so we make sure the current session
     * is still valid for the device on which it was created, and that the username,
     * ip-address are still the same.
     *
     */
    public function checkValidSession()
    {
        if ($this->app['session']->get('user')) {
            $session = $this->app['session']->get('user');
            $database = $this->getUser($session['id']);
            $this->currentuser = array_merge($session, $database);
        } else {
            // no current user, return without doing the rest.
            return false;
        }

        if (intval($this->currentuser['userlevel']) <= self::ANONYMOUS ) {
            $this->logout();
            return false;
        }

        // set the rights for each of the contenttypes for this user.
        foreach ($this->app['config']['contenttypes'] as $key => $contenttype) {
            if (in_array($key, $this->currentuser['contenttypes'])) {
                $this->allowed['contenttype:' . $key] = self::EDITOR;
            } else {
                $this->allowed['contenttype:' . $key] = self::ADMIN;
            }
        }

        $key = $this->getSessionKey($this->currentuser['username']);

        if ($key != $this->currentuser['sessionkey']) {
            $this->app['log']->add("keys don't match. Invalidating session: $key != " . $this->currentuser['sessionkey'], 2);
            $this->app['log']->add("Automatically logged out user '".$this->currentuser['username']."': Session data didn't match.", 3, '', 'issue');
            $this->logout();
            return false;
        }

        // Check if user is _still_ allowed to log on..
        if ( ($this->currentuser['userlevel'] < self::EDITOR) || !$this->currentuser['enabled'] ) {
            $this->logout();
            return false;
        }

        return true;

    }

    /**
     * Get a key to identify the session with.
     *
     * @param  string $name
     * @return string
     */
    private function getSessionKey($name = "")
    {

        if (empty($name)) {
            return false;
        }

        $key = $name;

        if ($this->app['config']['general']['cookies_use_remoteaddr']) {
            $key .= "-". $_SERVER['REMOTE_ADDR'];
        }
        if ($this->app['config']['general']['cookies_use_browseragent']) {
            $key .= "-". $_SERVER['HTTP_USER_AGENT'];
        }
        if ($this->app['config']['general']['cookies_use_httphost']) {
            $key .= "-". $_SERVER['HTTP_HOST'];
        }

        $key = md5($key);

        return $key;

    }


    /**
     * Remove a user from the database.
     *
     * @param  int  $id
     * @return bool
     */
    public function deleteUser($id)
    {
        $user = $this->getUser($id);

        if (empty($user['id'])) {
            $this->session->getFlashBag()->set('error', 'That user does not exist.');

            return false;
        } else {
            return $this->db->delete($this->usertable, array('id' => $user['id']));
        }

    }

    /**
     * Attempt to login a user with the given password
     *
     * @param  string $user
     * @param  string $password
     * @return bool
     */
    public function login($user, $password)
    {
        $userslug = makeSlug($user);

        // for once we don't use getUser(), because we need the password.
        $query = "SELECT * FROM " . $this->usertable . " WHERE username=? LIMIT 1";
        $user = $this->db->executeQuery($query, array($userslug))->fetch();

        if (empty($user)) {
            $this->session->getFlashBag()->set('error', 'Username or password not correct. Please check your input.');

            return false;
        }

        $hasher = new \Hautelook\Phpass\PasswordHash($this->hash_strength, true);

        if ($hasher->CheckPassword($password, $user['password'])) {

            if (!$user['enabled']) {
                $this->session->getFlashBag()->set('error', 'Your account is disabled. Sorry about that.');

                return false;
            }

            $update = array(
                'lastseen' => date('Y-m-d H:i:s'),
                'lastip' => $_SERVER['REMOTE_ADDR']
            );

            $this->db->update($this->usertable, $update, array('id' => $user['id']));

            $user = $this->getUser($user['id']);

            $user['sessionkey'] = $this->getSessionKey($user['username']);
{'fraction_non_alphanumeric': 0.17310374098102224, 'fraction_numerical': 0.002789062026314194, 'mean_word_length': 2.042612064194798, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 210, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4082413', 'n_tokens_mistral': 4897, 'n_tokens_neox': 4710, 'n_words': 1093}