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 updatePassword($localpart, $old_password, $new_password) { $user = $this->getUserForLogin($localpart, $old_password); if ($user != NULL) { throw new Exception ("user with that credentials not found"); } // The credentials were fine. So now set the new password $password_hash = password_hash($new_password, PASSWORD_BCRYPT, ["cost"=>12]); $sql = "UPDATE logins SET password_hash = '" . $password_hash . "'" . "WHERE localpart = '" . $localpart . "'"; if ($this->db->exec($sql)) { return true; } return false; } 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); } ?>