Files
matrix-register-bot/database.php
Krombel facdad126b fix language reference
it was the case that the language string for Exceptions was not
resolved successfully in some cases.
Now we switch to passing the internal string via Exceptions which
then will be used to translate to the correct language when needed
2018-03-19 14:40:20 +01:00

338 lines
11 KiB
PHP

<?php
require_once("config.php");
if (!isset($config["databaseURI"])) {
throw new Exception ("malformed configuration: databaseURI not defined");
}
abstract class RegisterState
{
// Sending an E-Mail failed in the first attempt. Will retry later
const PendingEmailSend = 0;
// User got a mail. We wait for it to verfiy
const PendingEmailVerify = 1;
// Sending a message to the register room failed on first attempt
const PendingAdminSend = 5;
// No admin has verified the registration yet
const PendingAdminVerify = 6;
// Registration failed on first attempt. Will retry
const PendingRegistration = 7;
// in this case we have to reset the password of the user (or should we store it for this case?)
const PendingSendRegistrationMail = 8;
// State to allow persisting in the database although an admin declined it.
// Will be removed regularly
const RegistrationAccepted = 7;
const RegistrationDeclined = 13;
// User got successfully registered. Will be cleaned up later
const AllDone = 100;
}
class mxDatabase
{
private $db = NULL;
/**
* Creates mxDatabase object
* @param config object which has following members:
* databaseURI: path to the sqlite file where the credentials should be stored
* or a param which can be used to connect to a database with PDO
* databaseUser and databasePass when authentication is required
* register_email which email does the register bot have (here used for providing lookup)
*/
function __construct($config) {
if (empty($config)) {
throw new Exception("config is empty");
}
if (!isset($config["databaseURI"])) {
throw new Exception("'databaseURI' not defined");
}
$db_input = $config["databaseURI"];
$user = '';
$password = '';
if (isset($config["databaseUser"]) && isset($config["databasePass"])) {
// only use it when both are defined
$user = $config["databaseUser"];
$password = $config["databasePass"];
}
// create database file when not existent yet
$this->db = new PDO($db_input, $user, $password);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db->exec("CREATE TABLE IF NOT EXISTS registrations(
id SERIAL PRIMARY KEY,
state INT DEFAULT 0,
first_name TEXT,
last_name TEXT,
username TEXT,
password_hash TEXT DEFAULT '',
note TEXT,
email TEXT,
verify_token TEXT,
admin_token TEXT,
request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
$this->db->exec("CREATE TABLE IF NOT EXISTS logins (
id SERIAL PRIMARY KEY,
active INT DEFAULT 1,
first_name TEXT,
last_name TEXT,
localpart TEXT,
password_hash TEXT,
email TEXT,
create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
// make sure the bot is allowed to login
if (!$this->userRegistered("register_bot")) {
$password = $this->addUser("Register", "Bot", "register_bot", $config["register_email"]);
$config["register_password"] = $password;
$myfile = fopen(dirname(__FILE__) . "/config.json", "w");
fwrite($myfile, json_encode($config, JSON_PRETTY_PRINT));
fclose($myfile);
}
// set writeable when not set already
if (strpos($db_input, "sqlite") === 0) {
$sqlite_file = substr($db_input, strlen("sqlite:"));
if (!is_writable($sqlite_file)) {
chmod($sqlite_file, 0660);
}
unset($sqlite_file);
}
}
/**
* WARNING: This allows accessing the database directly.
* This was only be added for convenience. You are advised to not use this function extensively
*
* @param sql String wich will be passed directly to the database
* @return Response of PDO::query()
*/
function query($sql) {
return $this->db->query($sql);
}
function setRegistrationStateVerify($state, $token) {
$sql = "UPDATE registrations SET state = " . $state
. " WHERE verify_token = '" . $token . "';";
return $this->db->exec($sql);
}
function setRegistrationStateById($state, $id) {
$sql = "UPDATE registrations SET state = " . $state
. " WHERE id = '" . $id . "';";
return $this->db->exec($sql);
}
function setRegistrationStateAdmin($state, $token) {
$sql = "UPDATE registrations SET state = " . $state
. " WHERE admin_token = '" . $token . "';";
return $this->db->exec($sql);
}
function setRegistrationState($state, $token) {
$sql = "UPDATE registrations SET state = " . $state
. " WHERE verify_token = '" . $token . "' OR admin_token = '" . $token . "';";
return $this->db->exec($sql);
}
function userPendingRegistrations($username) {
$sql = "SELECT COUNT(*) FROM registrations WHERE username = '" . $username . "' AND NOT state = "
. RegisterState::RegistrationDeclined . " LIMIT 1;";
$res = $this->db->query($sql);
if ($res->fetchColumn() > 0) {
return true;
}
return false;
}
function userRegistered($username) {
$sql = "SELECT COUNT(*) FROM logins WHERE localpart = '" . $username . "' LIMIT 1;";
$res = $this->db->query($sql);
if ($res->fetchColumn() > 0) {
return true;
}
return false;
}
/**
* Adds user to the database. Next steps should be sending a verify-mail to the user
* @param first_name First name of the user
* @param last_name Sirname of the user
* @param username the future localpart of that user
* @param note Note the user typed in to give a hint
* @param email E-Mail-Adress which will be stored into the database.
* This will be send to the server on first login
*
* @return ["verify_token"]
*/
function addRegistration($first_name, $last_name, $username, $note, $email) {
if ($this->userPendingRegistrations($username)) {
throw new Exception("USERNAME_PENDING_REGISTRATION");
}
if ($this->userRegistered($username)) {
throw new Exception("USERNAME_REGISTERED");
}
$verify_token = bin2hex(random_bytes(16));
$admin_token = bin2hex(random_bytes(16));
$this->db->exec("INSERT INTO registrations
(first_name, last_name, username, note, email, verify_token, admin_token)
VALUES ('" . $first_name."','" . $last_name . "','" . $username . "','" . $note . "','"
. $email."','" .$verify_token."','" .$admin_token."')");
return [
"verify_token"=> $verify_token,
];
}
/**
* Gets the user for the verify_admin page.
*
* @return ArrayOfUser|NULL Array with "first_name, last_name, username, note and email"
* as members
*/
function getUserForApproval($admin_token) {
$sql = "SELECT COUNT(*) FROM registrations WHERE admin_token = '" . $admin_token . "'"
. " AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;";
$res = $this->db->query($sql);
$first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL;
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, username, note, email FROM registrations"
. " WHERE admin_token = '" . $admin_token . "'"
. " AND state = " . RegisterState::PendingAdminVerify
. " LIMIT 1;";
foreach ($this->db->query($sql) as $row) {
// will only be executed once
return $row;
}
}
return NULL;
}
/**
* Gets the user when it opens the page to verify its mail
*
* @return ArrayOfUser|NULL Array with "first_name, last_name, note, email and admin_token"
* as members
*/
function getUserForVerify($verify_token) {
$sql = "SELECT COUNT(*) FROM registrations WHERE verify_token = '" . $verify_token . "'"
. " AND state = " . RegisterState::PendingEmailVerify . " LIMIT 1;";
$res = $this->db->query($sql);
$first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL;
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, note, email, admin_token FROM registrations "
. " WHERE verify_token = '" . $verify_token . "'"
. " AND state = " . RegisterState::PendingEmailVerify . " LIMIT 1;";
foreach ($this->db->query($sql) as $row) {
// will only be executed once
return $row;
}
}
return NULL;
}
function getUserForLogin($localpart, $password) {
$sql = "SELECT COUNT(*) FROM logins WHERE localpart = '" . $localpart
. "' AND active = 1 LIMIT 1;";
$res = $this->db->query($sql);
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, email, password_hash FROM logins "
. " WHERE localpart = '" . $localpart . "' AND active = 1 LIMIT 1;";
foreach ($this->db->query($sql) as $row) {
if (password_verify($password, $row["password_hash"])) {
return $row;
}
}
}
return NULL;
}
/**
* adds User to be able to login afterwards.
* @param first_name First name of the user
* @param last_name Sirname of the user
* @param username the future localpart of that user
* @param email E-Mail-Adress which will be stored into the database.
* This will be send to the server on first login
*
* @return password|NULL with member password as this method generates a
* password and saves that into the database
* NULL when failed
*
*/
function addUser($first_name, $last_name, $username, $email) {
// check if user already exists and abort in that case
if ($this->userRegistered($username)) {
return NULL;
}
// generate a password with 10 characters
$password = bin2hex(openssl_random_pseudo_bytes(5));
$password_hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>12]);
$sql = "INSERT INTO logins (first_name, last_name, localpart, password_hash, email) VALUES "
. "('" . $first_name."','" . $last_name . "','" . $username . "','"
. $password_hash . "','" . $email . "');";
if ($this->db->exec($sql)) {
return $password;
}
return NULL;
}
function searchUserByName($search_term) {
$term = filter_var($search_term, FILTER_SANITIZE_STRING);
$result = array();
$sql = "SELECT COUNT(*) FROM logins WHERE"
. " localpart LIKE '" . $term . "%' AND active = 1;";
$res = $this->db->query($sql);
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, localpart FROM logins WHERE"
. " localpart LIKE '" . $term . "%' AND active = 1;";
foreach ($this->db->query($sql) as $row) {
array_push($result, [
"display_name" => $row["first_name"] . " " . $row["last_name"],
"user_id" => $row["localpart"],
]);
}
}
return $result;
}
function searchUserByEmail($search_term) {
$term = filter_var($search_term, FILTER_SANITIZE_STRING);
$result = array();
$sql = "SELECT COUNT(*) FROM logins WHERE"
. " email = '" . $term . "' AND active = 1;";
$res = $this->db->query($sql);
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, localpart FROM logins WHERE"
. " email = '" . $term . "' AND active = 1;";
foreach ($this->db->query($sql) as $row) {
array_push($result, [
"display_name" => $row["first_name"] . " " . $row["last_name"],
"user_id" => $row["localpart"],
]);
}
}
return $result;
}
}
if (!isset($mx_db)) {
$mx_db = new mxDatabase($config);
}
?>