Files
matrix-register-bot/database.php
2018-03-03 12:49:12 +01:00

267 lines
8.4 KiB
PHP

<?php
$db_file = dirname(__FILE__)."/db_file.sqlite";
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 db_file path to the sqlite file where the credentials should be stored
*/
function __construct($db_file) {
// create database file when not existent yet
if (!file_exists($db_file)) {
$this->db = new PDO('sqlite:' . $db_file);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->db->exec("CREATE TABLE registrations(
id INTEGER PRIMARY KEY AUTOINCREMENT,
state INT DEFAULT 0,
first_name TEXT,
last_name TEXT,
username TEXT,
note TEXT,
email TEXT,
verify_token TEXT,
admin_token TEXT,
request_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP)");
$this->db->exec("CREATE TABLE logins (
id INTEGER PRIMARY KEY AUTOINCREMENT,
active INT DEFAULT 1,
firstname TEXT,
lastname TEXT,
localpart TEXT,
password_hash TEXT,
email TEXT,
create_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
}
else {
// establish connection
$this->db = new PDO('sqlite:' . $db_file);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
// set writeable when not set already
if (!is_writable($db_file)) {
chmod($db_file, 0777);
}
}
/**
* 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)) {
require_once "language.php";
throw new Exception($language["USERNAME_PENDING_REGISTRATION"]);
}
if ($this->userRegistered($username)) {
require_once "language.php";
throw new Exception($language["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);
$first_name = NULL; $last_name = NULL; $username = NULL; $note = NULL; $email = NULL;
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, email, password_hash FROM logins "
. " WHERE verify_token = '" . $token . "' LIMIT 1;";
foreach ($this->db->query($sql) as $row) {
// will only be executed once
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) {
// 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 (firstname, lastname, localpart, password_hash, email) VALUES "
. '("' . $first_name.'","' . $last_name . '","' . $username . '","'
. $password_hash . '","' . $email . '")';
if ($this->db->exec($sql)) {
return $password;
}
return NULL;
}
}
$mx_db = new mxDatabase($db_file);
?>