funktionen in common.php ausgelagert, treiberweichen für datenbankabfragen, spamschutz hinzugefügt

This commit is contained in:
bernd 2021-03-04 19:25:48 +01:00
parent 2a804e985d
commit 4ffaf6f54d
3 changed files with 226 additions and 93 deletions

67
lib/common.php Normal file
View file

@ -0,0 +1,67 @@
<?php
/**
* file: common.php
* date: 03.03.2021
* author: bernd@nr18.space
* desc: Allgemeine Funktionen, die mal in lib/db.php waren aber nichts mit
* der Datenbank zu tun haben.
*/
if (!defined('INCLUDES_ALLOWED'))
die('Access denied.');
function getNow(): int {
/**
* Zeitzone aud Berlin setzen und die aktuelle Zeit in Sekunden seit
* 01.01.1970 zurückgeben.
*/
date_default_timezone_set("Europe/Berlin");
return time();
}
function getNick(string $mid): string
{
/**
* Extrahiert aus einer Matrix-ID den localpart.
* TODO: In eine bibliothek auslagern? (/lib/common)
*/
$uid = "";
$append = false;
$strarray = str_split($mid);
foreach ($strarray as $char)
{
if ($char == '@')
{
$append = true;
}
else if ($char == ':')
{
return $uid;
}
else
{
if ($append === true)
{
$uid = $uid.$char;
}
}
}
}
function getRemoteHexIP() {
/**
* Wandelt die IP des anfragenden Clients in einen Hexadezimalen
* String um.
* TODO: Funktion allgemeiner halten? IP als Parameter übergeben?
*/
return bin2hex(inet_pton($_SERVER['REMOTE_ADDR']));
}

View file

@ -15,6 +15,9 @@ ini_set("display_startip_errors", "on");
if (!defined('INCLUDES_ALLOWED')) if (!defined('INCLUDES_ALLOWED'))
die('Access denied.'); die('Access denied.');
require_once("common.php");
function testDriver(): bool function testDriver(): bool
{ {
print_r(PDO::getAvailableDrivers()); print_r(PDO::getAvailableDrivers());
@ -112,8 +115,11 @@ class Database {
* und die Instanz des Loggers. Läßt kontrollieren ob die Tabelle zum * und die Instanz des Loggers. Läßt kontrollieren ob die Tabelle zum
* Eintragen der Requests vorhanden ist und stößt die Erstellung der * Eintragen der Requests vorhanden ist und stößt die Erstellung der
* Tabelle an, wenn sie fehlt. * Tabelle an, wenn sie fehlt.
* TODO: Geht das eleganter? So wird das bei jeder Anfrage * TODO: Geht das eleganter? So wird bei jedem Request kontrolliert,
* ausgeführt. * ob die Tabelle vorhanden ist. Anderseits gibt eine Konstruktor
* nichts zurück. Ergo wird bei einem Fehler beim Erstellen der
* Tabelle auch nicht abgebrochen, sondern erst beim Versuch etwas
* hineinzuschreiben.
*/ */
$this->pdo = $pdo; $this->pdo = $pdo;
@ -134,14 +140,18 @@ class Database {
/** /**
* Kontrolliert, ob eine Tabelle zum Eintragen der Requests * Kontrolliert, ob eine Tabelle zum Eintragen der Requests
* vorhanden ist. * vorhanden ist.
* TODO: Die Query ist Postgres spezifisch. Treiber durchreichen und
* eine Weiche anlegen?
*/ */
$this->log->d("Check if table requests exists"); $this->log->d("Check if table requests exists");
$stmt = "SELECT * FROM information_schema.tables WHERE if ($this->pdo->driver === "PDO_PGSQL") {
$stmt = "SELECT * FROM information_schema.tables WHERE
table_type = 'BASE TABLE' and table_type = 'BASE TABLE' and
table_name = 'requests'"; table_name = 'requests'";
} else if ($this->pdo->driver === "PDO_PSQLITE") {
// ungetestet
$stmt = "SELECT 1 FROM sqlite_master WHERE type='table' and
name='requests'";
}
try { try {
$response = $this->pdo->query($stmt); $response = $this->pdo->query($stmt);
} catch (PDOException $e) { } catch (PDOException $e) {
@ -164,13 +174,24 @@ class Database {
*/ */
$this->log->n("Try to create table requests"); $this->log->n("Try to create table requests");
$stmt = "CREATE TABLE IF NOT EXISTS requests ( if ($this->pdo->driver === "PDO_PGSQL") {
$stmt = "CREATE TABLE IF NOT EXISTS requests (
id serial PRIMARY KEY, id serial PRIMARY KEY,
nick varchar(80) NOT NULL UNIQUE, nick varchar(80) NOT NULL UNIQUE,
email varchar(80) NOT NULL, email varchar(80) NOT NULL,
token char(32) NOT NULL UNIQUE, token char(32) NOT NULL UNIQUE,
ip bytea, ip bytea,
time integer NOT NULL);"; time integer NOT NULL);";
} else if ($this->pdo->driver === "PDO_PSQLITE") {
// ungetestet
$stmt = "CREATE TABLE IF NOT EXISTS request (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nick TEXT NUT NULL UNIQUE;
email TEXT NOT NULL,
token TEXT NOT NULL UNIQUE,
ip BLOB,
time INTEGER NOT NULL);";
}
try { try {
$this->pdo->exec($stmt); $this->pdo->exec($stmt);
} catch (PDOException $e) { } catch (PDOException $e) {
@ -191,7 +212,8 @@ class Database {
* Sollte diese eine Exception werfen, wird true (es gibt einen * Sollte diese eine Exception werfen, wird true (es gibt einen
* Benutzer) zurück gegeben. Ansonsten wird der Rückgabewert * Benutzer) zurück gegeben. Ansonsten wird der Rückgabewert
* von der Funktion getNick ausgewertet, die aus der übergebenen * von der Funktion getNick ausgewertet, die aus der übergebenen
* Matrix-ID den localpart extrahiert. * Matrix-ID den localpart extrahiert. Nutzt für die Suche die
* Klassenfunktion searchUser(). Braucht getNick() common.php!
*/ */
$this->log->d("Search for localpart {$nick} in users"); $this->log->d("Search for localpart {$nick} in users");
@ -212,7 +234,7 @@ class Database {
else else
{ {
foreach ($response as $array) { foreach ($response as $array) {
$uid = $this->getNick($array['name']); $uid = getNick($array['name']);
$this->log->d("Compare {$nick} with {$uid}"); $this->log->d("Compare {$nick} with {$uid}");
if ($uid === $nick) { if ($uid === $nick) {
$this->log->i("MXID localpart already exists: {$nick}"); $this->log->i("MXID localpart already exists: {$nick}");
@ -232,7 +254,8 @@ class Database {
* Nutzernamen. Die Abfrage der Datenbank wird an die Funktion * Nutzernamen. Die Abfrage der Datenbank wird an die Funktion
* searchUser delegiert. Sollte diese eine Exception werfen, wird * searchUser delegiert. Sollte diese eine Exception werfen, wird
* true (es gibt einen benutzer) zurück gegeben. Ansonsten wird die * true (es gibt einen benutzer) zurück gegeben. Ansonsten wird die
* Anzahl der Treffer ausgewertet. * Anzahl der Treffer ausgewertet. Nutzt für die Suche die eigene
* Funktion searchUser().
*/ */
$this->log->d("Search for localpart {$nick} in requests"); $this->log->d("Search for localpart {$nick} in requests");
@ -245,7 +268,7 @@ class Database {
} }
$count = count($response); $count = count($response);
if ($count > 0) { if ($count > 0) {
$this->log->d("Search for {$nick}: {$count} hit(s)"); $this->log->n("Search for {$nick}: {$count} hit(s)");
return true; return true;
} }
$this->log->d("Nothing found"); $this->log->d("Nothing found");
@ -278,55 +301,47 @@ class Database {
} }
} }
private function getNick(string $mid): string public function getTimestamps(): array {
{
/** /**
* Extrahiert aus einer Matrix-ID den localpart. * Schaut in der Datenbank, ob es bereits Einträge mit der aktuellen
* TODO: In eine bibliothek auslagern? (/lib/common) * Remote IP gibt und gibt alle Einträge des dazu gehörendem
* Zeitstempels zurück. Braucht getRemoteHexIP() aus common.php!
* TODO: flexibler gestalten? IP als Parameter übergeben?
*/ */
$this->log->d("Extract nick from {$mid}"); $ip = getRemoteHexIP();
$uid = ""; $this->log->i("Search for IP: {$_SERVER['REMOTE_ADDR']}");
$append = false; $stmt = $this->pdo->prepare("SELECT time FROM requests WHERE
$strarray = str_split($mid); ip = :ip");
foreach ($strarray as $char) try {
{ $stmt->BindValue(':ip', $ip, PDO::PARAM_LOB);
if ($char == '@') $stmt->execute();
{ $response = $stmt->fetchAll();
$append = true; } catch (PDOException $e) {
} $this->log->e("getIPs: ERROR: {$e->getMessage()}");
else if ($char == ':')
{
$this->log->d("Extracted: {$uid}");
return $uid;
}
else
{
if ($append === true)
{
$uid = $uid.$char;
}
}
} }
$count = count($response);
$this->log->d("{$count} Timestamps found");;
return $response;
} }
public function saveRequest($token): bool public function saveRequest($token): bool
{ {
/** /**
* Speichert den gewünschten Nick, die Emailadresse, das Token, die * Speichert den gewünschten Nick, die Emailadresse, das Token, die
* IP und einen Zeitstempel in der Tabelle Requests. * IP und einen Zeitstempel in der Tabelle Requests. Braucht die
* Funktionen getRemoteHexIP() und getNow() aus common.php.
* TODO: IP nicht Hexadezimal, sondern Binär speichern. Spart Platz * TODO: IP nicht Hexadezimal, sondern Binär speichern. Spart Platz
* und ist schneller. Bin ich leider zu blöd für. * und ist schneller. Bin ich leider zu blöd für.
* TODO: Sollten/Müssen Nick und Email noch durch htmlspecialchars() * TODO: Sollten/Müssen Nick und Email noch durch htmlspecialchars()
* oder reichen die prepared Statments? * oder reichen die prepared Statments?
*/ */
$bin = inet_pton($_SERVER['REMOTE_ADDR']); $ip = getRemoteHexIP();
$ip = bin2hex($bin);
$nick = $_POST['login']; $nick = $_POST['login'];
$email = $_POST['email']; $email = $_POST['email'];
date_default_timezone_set("Europe/Berlin"); $time = getNow();
$time = time();
$this->log->d("Save request for: {$nick} with {$token} at {$time}"); $this->log->d("Save request for: {$nick} with {$token} at {$time}");
try { try {
$stmt = $this->pdo->prepare("INSERT INTO requests $stmt = $this->pdo->prepare("INSERT INTO requests
@ -343,11 +358,9 @@ class Database {
$this->log->e("Error: {$e->getMessage()}"); $this->log->e("Error: {$e->getMessage()}");
return false; return false;
} }
if ($response === 1) { if ($response) {
$this->log->i("Request saved successfull"); $this->log->n("Request saved successfull");
return true; return true;
} else {
$this->log->e("Database returns: {$response}");
} }
return false; return false;
} }

View file

@ -13,6 +13,7 @@ if (!defined('INCLUDES_ALLOWED'))
require("base.php"); require("base.php");
require("static/mail.php"); require("static/mail.php");
require_once("common.php");
class Request extends BaseClass { class Request extends BaseClass {
@ -52,6 +53,9 @@ class Request extends BaseClass {
} else if (false === $this->checkUser()) { } else if (false === $this->checkUser()) {
$message = "User Id is already taken"; $message = "User Id is already taken";
return false; return false;
} else if (false === $this->checkRequests()) {
$message = "Too many requests";
return false;
} else { } else {
if ($this->generateToken(16) === true) { if ($this->generateToken(16) === true) {
if ($this->saveRequest() === true) { if ($this->saveRequest() === true) {
@ -67,53 +71,6 @@ class Request extends BaseClass {
return false; return false;
} }
private function saveRequest(): bool {
/**
* Veranlaßt die Speicherung der Anfrage in der Tabelle requests.
* Bekommt aus der Datenbank (auch im Falle einer PDO Exception)
* einen Boolean zurück.
*/
try {
$response = $this->db->saveRequest($this->token);
} catch (Exception $e) {
$this->log->e("Error: Database returns: {$e->getMessage()}");
}
if ($response === true) {
return true;
}
return false;
}
private function sendVerificationMail(): bool {
/**
* Verschickt die Mail mit dem Verifizierungslink.
* TODO: Reicht filter_input()? Was kann hier passieren?
*/
$mailTo = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$mxdomain = $this->config->getMxDomain();
$baseurl = $this->config->getBaseURL();
$validator = $this->config->getValidator();
$mailFrom = $this->config->getMailFrom();
$mailSubject = $this->config->getMailSubject();
$mailClosure = $this->config->getMailClosure();
$this->log->d("Try to send verification mail");
$link = $baseurl . $validator . $this->token . "\r\n\r\n";
$mailbody = MAILTEXT1 . " {$mxdomain} " . MAILTEXT2 . "\r\n\r\n" . $link . $mailClosure;
if (mail($mailTo, $mailSubject, $mailbody, $mailFrom))
{
$this->log->i("Validationmail successfull send");
return true;
}
$this->log->e("Sending validation mail failed");
return false;
}
private function checkCaptcha(): bool { private function checkCaptcha(): bool {
/** /**
@ -205,6 +162,102 @@ class Request extends BaseClass {
return true; return true;
} }
private function checkRequests(): bool {
/**
* Prüft, ob für es von der aktuellen Remote IP bereits Anfragen
* gibt. Diese sollten gewisse Limits nicht überschreiten.
*/
$timestamps = $this->db->getTimestamps();
$now = getNow();
// Wenn es der erste Request ist -> return true
$count = count($timestamps);
if ($count === 0) {
$this->log->i("First request from {$_SERVER['REMOTE_ADDR']}");
return true;
}
$first = $timestamps[array_key_first($timestamps)]['time'];
$last = $timestamps[array_key_last($timestamps)]['time'];
$duration = $last - $first;
$average = $duration / $count;
// Maximal 10 Anfragen in 24 Stunden
if ($count > 10 && $duration < 86400) {
$this->log->n("To many requests in 24 hours");
return false;
}
// Wenn der letzte Request weniger als 10 Sekunden her ist -> return
// false
if (($now - $last) < 5) {
$this->log->n("Time between two requests under 5 seconds");
return false;
}
// Wenn die durchschnittliche Dauer zwischen allen Anfragen unter 30
// Sekunden liegt -> return false
if (($average) < 30 && $count > 3) {
$this->log->n("Duration between all requests under 10 seconds");
return false;
}
// nicht mehr als 20 Anfragen von einer IP in der Datenbank
// speichern -> return false
if ($count > 20) {
$this->log->n("To many requests in database");
return false;
}
return true;
}
private function saveRequest(): bool {
/**
* Veranlaßt die Speicherung der Anfrage in der Tabelle requests.
* Bekommt aus der Datenbank (auch im Falle einer PDO Exception)
* einen Boolean zurück.
*/
try {
$response = $this->db->saveRequest($this->token);
} catch (Exception $e) {
$this->log->e("Error: Database returns: {$e->getMessage()}");
}
if ($response === true) {
return true;
}
return false;
}
private function sendVerificationMail(): bool {
/**
* Verschickt die Mail mit dem Verifizierungslink.
* TODO: Reicht filter_input()? Was kann hier passieren?
*/
$mailTo = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$mxdomain = $this->config->getMxDomain();
$baseurl = $this->config->getBaseURL();
$validator = $this->config->getValidator();
$mailFrom = $this->config->getMailFrom();
$mailSubject = $this->config->getMailSubject();
$mailClosure = $this->config->getMailClosure();
$this->log->d("Try to send verification mail");
$link = $baseurl . $validator . $this->token . "\r\n\r\n";
$mailbody = MAILTEXT1 . " {$mxdomain} " . MAILTEXT2 . "\r\n\r\n" . $link . $mailClosure;
if (mail($mailTo, $mailSubject, $mailbody, $mailFrom))
{
$this->log->i("Validationmail successfull send");
return true;
}
$this->log->e("Sending validation mail failed");
return false;
}
} }
?> ?>