This repository has been archived on 2024-09-05. You can view files and clone it, but cannot push or open issues or pull requests.
shimmie2/core/user.php

224 lines
7.1 KiB
PHP
Raw Normal View History

<?php
2014-05-23 23:28:57 +00:00
2019-05-28 16:31:20 +00:00
function _new_user(array $row): User {
2009-05-30 13:47:35 +00:00
return new User($row);
}
2012-02-07 13:44:54 +00:00
2009-07-19 07:38:13 +00:00
/**
2014-04-29 05:33:03 +00:00
* Class User
*
* An object representing a row in the "users" table.
2009-07-19 07:38:13 +00:00
*
2014-04-29 05:33:03 +00:00
* The currently logged in user will always be accessible via the global variable $user.
*/
class User {
/** @var int */
public $id;
/** @var string */
public $name;
/** @var string */
public $email;
public $join_date;
/** @var string */
public $passhash;
/** @var UserClass */
public $class;
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialisation *
* *
* User objects shouldn't be created directly, they should be *
* fetched from the database like so: *
* *
2009-05-30 13:47:35 +00:00
* $user = User::by_name("bob"); *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2009-07-19 07:38:13 +00:00
/**
* One will very rarely construct a user directly, more common
* would be to use User::by_id, User::by_session, etc.
*
2014-12-15 00:06:31 +00:00
* @throws SCoreException
2009-07-19 07:38:13 +00:00
*/
2017-09-19 17:55:43 +00:00
public function __construct(array $row) {
2015-08-02 19:39:41 +00:00
global $_shm_user_classes;
2012-02-14 20:38:19 +00:00
$this->id = int_escape($row['id']);
$this->name = $row['name'];
$this->email = $row['email'];
$this->join_date = $row['joindate'];
2010-05-28 12:04:57 +00:00
$this->passhash = $row['pass'];
2015-08-02 19:39:41 +00:00
if(array_key_exists($row["class"], $_shm_user_classes)) {
$this->class = $_shm_user_classes[$row["class"]];
}
else {
2014-12-07 13:20:36 +00:00
throw new SCoreException("User '{$this->name}' has invalid class '{$row["class"]}'");
}
}
2017-09-19 17:55:43 +00:00
public static function by_session(string $name, string $session) {
global $config, $database;
$row = $database->cache->get("user-session:$name-$session");
2012-06-23 22:38:28 +00:00
if(!$row) {
if($database->get_driver_name() === "mysql") {
$query = "SELECT * FROM users WHERE name = :name AND md5(concat(pass, :ip)) = :sess";
}
else {
$query = "SELECT * FROM users WHERE name = :name AND md5(pass || :ip) = :sess";
}
2012-06-23 22:38:28 +00:00
$row = $database->get_row($query, array("name"=>$name, "ip"=>get_session_ip($config), "sess"=>$session));
$database->cache->set("user-session:$name-$session", $row, 600);
2012-06-23 22:38:28 +00:00
}
return is_null($row) ? null : new User($row);
}
2017-09-19 17:55:43 +00:00
public static function by_id(int $id) {
global $database;
2012-01-11 20:57:00 +00:00
if($id === 1) {
$cached = $database->cache->get('user-id:'.$id);
2011-03-23 11:26:11 +00:00
if($cached) return new User($cached);
}
2011-01-01 15:58:09 +00:00
$row = $database->get_row("SELECT * FROM users WHERE id = :id", array("id"=>$id));
if($id === 1) $database->cache->set('user-id:'.$id, $row, 600);
return is_null($row) ? null : new User($row);
}
2017-09-19 17:55:43 +00:00
public static function by_name(string $name) {
global $database;
2012-12-10 21:28:41 +00:00
$row = $database->get_row($database->scoreql_to_sql("SELECT * FROM users WHERE SCORE_STRNORM(name) = SCORE_STRNORM(:name)"), array("name"=>$name));
return is_null($row) ? null : new User($row);
}
2017-09-19 17:55:43 +00:00
public static function by_name_and_pass(string $name, string $pass) {
2014-05-23 23:28:57 +00:00
$user = User::by_name($name);
if($user) {
if($user->passhash == md5(strtolower($name) . $pass)) {
2019-04-26 09:13:31 +00:00
log_info("core-user", "Migrating from md5 to bcrypt for ".html_escape($name));
2014-05-23 23:28:57 +00:00
$user->set_password($pass);
}
if(password_verify($pass, $user->passhash)) {
2019-04-26 09:13:31 +00:00
log_info("core-user", "Logged in as ".html_escape($name)." ({$user->class->name})");
2014-05-23 23:28:57 +00:00
return $user;
}
2019-04-26 09:13:31 +00:00
else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid password)");
}
}
else {
log_warning("core-user", "Failed to log in as ".html_escape($name)." (Invalid username)");
2014-05-23 23:28:57 +00:00
}
2017-09-19 17:55:43 +00:00
return null;
}
/* useful user object functions start here */
2017-09-19 17:55:43 +00:00
public function can(string $ability): bool {
2012-02-14 20:38:19 +00:00
return $this->class->can($ability);
2012-02-07 13:44:54 +00:00
}
2017-09-19 17:55:43 +00:00
public function is_anonymous(): bool {
global $config;
2012-01-12 19:46:58 +00:00
return ($this->id === $config->get_int('anon_id'));
}
2017-09-19 17:55:43 +00:00
public function is_logged_in(): bool {
2010-01-12 15:01:34 +00:00
global $config;
2012-01-12 19:46:58 +00:00
return ($this->id !== $config->get_int('anon_id'));
2010-01-12 15:01:34 +00:00
}
2017-09-19 17:55:43 +00:00
public function is_admin(): bool {
2012-02-14 21:15:19 +00:00
return ($this->class->name === "admin");
}
2017-09-19 17:55:43 +00:00
public function set_class(string $class) {
global $database;
2012-02-14 20:38:19 +00:00
$database->Execute("UPDATE users SET class=:class WHERE id=:id", array("class"=>$class, "id"=>$this->id));
log_info("core-user", 'Set class for '.$this->name.' to '.$class);
}
2017-09-19 17:55:43 +00:00
public function set_name(string $name) {
2015-07-12 21:14:57 +00:00
global $database;
if(User::by_name($name)) {
throw new Exception("Desired username is already in use");
}
$old_name = $this->name;
$this->name = $name;
$database->Execute("UPDATE users SET name=:name WHERE id=:id", array("name"=>$this->name, "id"=>$this->id));
log_info("core-user", "Changed username for {$old_name} to {$this->name}");
}
2017-09-19 17:55:43 +00:00
public function set_password(string $password) {
global $database;
2016-06-18 18:00:26 +00:00
$hash = password_hash($password, PASSWORD_BCRYPT);
if(is_string($hash)) {
$this->passhash = $hash;
$database->Execute("UPDATE users SET pass=:hash WHERE id=:id", array("hash"=>$this->passhash, "id"=>$this->id));
log_info("core-user", 'Set password for '.$this->name);
}
else {
throw new SCoreException("Failed to hash password");
}
}
2009-08-11 16:07:03 +00:00
2017-09-19 17:55:43 +00:00
public function set_email(string $address) {
2009-08-11 16:07:03 +00:00
global $database;
2011-01-01 15:58:09 +00:00
$database->Execute("UPDATE users SET email=:email WHERE id=:id", array("email"=>$address, "id"=>$this->id));
2012-01-12 19:46:58 +00:00
log_info("core-user", 'Set email for '.$this->name);
2009-08-11 16:07:03 +00:00
}
2009-10-08 01:57:28 +00:00
/**
* Get a snippet of HTML which will render the user's avatar, be that
* a local file, a remote file, a gravatar, a something else, etc.
2009-10-08 01:57:28 +00:00
*/
2017-09-19 17:55:43 +00:00
public function get_avatar_html(): string {
2009-10-08 01:57:28 +00:00
// FIXME: configurable
2009-10-08 11:40:52 +00:00
global $config;
2012-01-11 20:57:00 +00:00
if($config->get_string("avatar_host") === "gravatar") {
2009-10-08 11:40:52 +00:00
if(!empty($this->email)) {
$hash = md5(strtolower($this->email));
2010-04-21 16:56:01 +00:00
$s = $config->get_string("avatar_gravatar_size");
2012-02-09 17:37:41 +00:00
$d = urlencode($config->get_string("avatar_gravatar_default"));
2010-04-21 16:56:01 +00:00
$r = $config->get_string("avatar_gravatar_rating");
2012-12-30 18:08:58 +00:00
$cb = date("Y-m-d");
2018-07-17 00:15:20 +00:00
return "<img class=\"avatar gravatar\" src=\"https://www.gravatar.com/avatar/$hash.jpg?s=$s&d=$d&r=$r&cacheBreak=$cb\">";
2009-10-08 11:40:52 +00:00
}
2009-10-08 01:57:28 +00:00
}
return "";
}
2010-05-28 12:04:57 +00:00
/**
* Get an auth token to be used in POST forms
*
* password = secret, avoid storing directly
2014-05-23 23:28:57 +00:00
* passhash = bcrypt(password), so someone who gets to the database can't get passwords
2010-05-28 12:04:57 +00:00
* sesskey = md5(passhash . IP), so if it gets sniffed it can't be used from another IP,
* and it can't be used to get the passhash to generate new sesskeys
* authtok = md5(sesskey, salt), presented to the user in web forms, to make sure that
* the form was generated within the session. Salted and re-hashed so that
* reading a web page from the user's cache doesn't give access to the session key
*/
2017-09-19 17:55:43 +00:00
public function get_auth_token(): string {
2010-05-28 12:04:57 +00:00
global $config;
$salt = DATABASE_DSN;
2010-05-28 12:04:57 +00:00
$addr = get_session_ip($config);
2010-09-22 12:20:08 +00:00
return md5(md5($this->passhash . $addr) . "salty-csrf-" . $salt);
2010-05-28 12:04:57 +00:00
}
2017-09-19 17:55:43 +00:00
public function get_auth_html(): string {
2010-05-28 12:04:57 +00:00
$at = $this->get_auth_token();
2012-01-11 20:57:00 +00:00
return '<input type="hidden" name="auth_token" value="'.$at.'">';
2010-05-28 12:04:57 +00:00
}
2017-09-19 17:55:43 +00:00
public function check_auth_token(): bool {
2012-01-26 17:36:22 +00:00
return (isset($_POST["auth_token"]) && $_POST["auth_token"] == $this->get_auth_token());
2010-05-28 12:04:57 +00:00
}
}