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 INTEGER PRIMARY KEY AUTOINCREMENT, 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 INTEGER PRIMARY KEY AUTOINCREMENT, 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 require_once("config.php"); if (!$this->userRegistered("register_bot")) { $password = $this->addUser("Register", "Bot", "register_bot", $config["register_email"]); $config["register_password"] = $password; $myfile = fopen("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)) { require_once("language.php"); throw new Exception($language["USERNAME_PENDING_REGISTRATION"]." (requested)"); } if ($this->userRegistered($username)) { require_once("language.php"); throw new Exception($language["USERNAME_REGISTERED"] . " (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 . "' LIMIT 1;"; foreach ($this->db->query($sql) as $row) { error_log($password . "-" . $row["password_hash"]); // 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) { // 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; } } $mx_db = new mxDatabase($db_input); ?>