forked from berhsi/matrix-register
278 lines
9.7 KiB
PHP
278 lines
9.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* file: lib/request.php
|
|
* date: 28.02.2021
|
|
* user: bernd@nr18.space
|
|
* desc:
|
|
*/
|
|
|
|
|
|
if (!defined('INCLUDES_ALLOWED'))
|
|
die('Access denied.');
|
|
|
|
require("base.php");
|
|
require("static/mail.php");
|
|
require_once("common.php");
|
|
|
|
|
|
class Request extends BaseClass {
|
|
|
|
/**
|
|
* Klasse zur Bearbeitung einer Anfrage nach einem Matrix Account. Erbt
|
|
* aus der Klasse BaseClass ein Konfigurations- und ein Datenbankobjekt
|
|
* ($this->config, $this->db), die Funktion generateToken() und sowie
|
|
* die Variable $this-token.
|
|
* @param string $message
|
|
* @return bool
|
|
*/
|
|
public function checkRequest(string &$message): bool {
|
|
|
|
/**
|
|
* Hauptfunktion der Klasse Request - steuert alle Prüfungen, das
|
|
* Speichern des Requests und das Versenden der Email. Gibt True
|
|
* oder False zurück und setzt die Variable "message", welche auf
|
|
* der Webseite ausgegeben wird.
|
|
*/
|
|
|
|
if (!isset($this->db)) {
|
|
$this->log->e("There is no database");
|
|
$message = "Something goes wrong";
|
|
return false;
|
|
}
|
|
|
|
$ip = getRemoteHexIP();
|
|
$this->log->i("Request started for nick: {$_POST['login']}");
|
|
try {
|
|
if (false === $this->checkCaptcha()) {
|
|
$message = "Captcha invalid";
|
|
return false;
|
|
} else if (false === $this->checkEmail()) {
|
|
$message = "Email invalid";
|
|
return false;
|
|
} else if (false === $this->checkMXID($this->config->getMxDomain())) {
|
|
$message = "User ID invalid";
|
|
return false;
|
|
} else if (false === $this->checkUser($_POST['login'] ?? '')) {
|
|
$message = "User Id is already taken";
|
|
return false;
|
|
} else if (false === $this->checkRequests($ip)) {
|
|
$message = "Too many requests";
|
|
return false;
|
|
} else {
|
|
if ($this->generateToken(16) === true) {
|
|
if ($this->saveRequest($ip) === true) {
|
|
if ($this->sendVerificationMail() === true) {
|
|
$login = htmlspecialchars($_POST['login']);
|
|
$message = "Your request for '{$login}' is saved and a
|
|
verification mail is send";
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (Throwable $e) {
|
|
$this->log->e($e->getMessage());
|
|
$message = "unexpected error";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function checkCaptcha(): bool {
|
|
|
|
/**
|
|
* Prüfen, ob das Captcha die korrekte Hausnummer abbildet.
|
|
* Filter_input gibt im Erfolgsfall einen Integer zurück.
|
|
*/
|
|
|
|
$this->log->d("Checking captcha");
|
|
$captcha = filter_input(INPUT_POST, 'captcha', FILTER_VALIDATE_INT);
|
|
if ($captcha == 26) {
|
|
$this->log->d("Captcha valid");
|
|
return true;
|
|
}
|
|
$this->log->e("Invalid captcha");
|
|
return false;
|
|
}
|
|
|
|
private function checkEmail(): bool {
|
|
|
|
/**
|
|
* Prüfen, ob die Emailadresse schematisch gültig ist. filter_input
|
|
* gibt im Erfolgsfall den Wert, im Fehlerfall false oder null, wenn
|
|
* die Variable nicht gestzt ist, zurück. Letzteres wird beim
|
|
* vorangehenden Test checkForms geprüft und kann daher nicht
|
|
* auftreten.
|
|
*/
|
|
|
|
$this->log->d("Checking email schema");
|
|
if (filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL)) {
|
|
$this->log->d("Email schema is valid");
|
|
return true;
|
|
}
|
|
$this->log->e("Email schema invalid");
|
|
return false;
|
|
}
|
|
|
|
private function checkMXID(string $mxid): bool {
|
|
|
|
/**
|
|
* Prüft, ob der gewünschte localpart nur Zeichen enthält, die von
|
|
* der Matrix-Spezifikation erlaubt sind. Die Spezifikation erlaubt
|
|
* nur Kleinbuchstaben, Ziffern, Minus, Punkt, Gleichheitszeichen,
|
|
* Unterstrich und Schrägstrich. Geprüft wird auch, ob die Länge des
|
|
* gewünschten localpart zusammen mit dem domainpart sowie dem @ und
|
|
* : die Länge von 255 Zeichen nicht überschreiten.
|
|
*/
|
|
|
|
$this->log->d("Check MXID localpart");
|
|
$str_array = str_split($_POST['login']);
|
|
$max_length = 255 - (mb_strlen($mxid) + 2);
|
|
$specials = array('-', '.', '=', '_', '/');
|
|
$numbers = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
|
|
$possibles = array_merge(range('a', 'z'), $numbers, $specials);
|
|
|
|
foreach ($str_array as $item) {
|
|
if (in_array($item, $possibles, true) === false) {
|
|
$this->log->e("MXID localpart: invalid character: {$item}");
|
|
return false;
|
|
}
|
|
}
|
|
if (mb_strlen($_POST['login']) > $max_length) {
|
|
$this->log->e("MXID localpart: too long");
|
|
return false;
|
|
}
|
|
$this->log->d("MXID localpart is valid");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param string $nick
|
|
* @return bool
|
|
*/
|
|
private function userExistsInRequestsOrUsers(string $nick): bool
|
|
{
|
|
return $this->db->UserExistsInRequests($nick) || $this->db->UserExistsInUsers($nick);
|
|
}
|
|
|
|
/**
|
|
* @param string $login
|
|
* @return bool
|
|
*/
|
|
private function checkUser(string $login): bool {
|
|
/**
|
|
* Prüft, ob der gewünschte Nutzernamen nicht bereits vergeben ist.
|
|
* Dazu wird in den Datenbanktabellen users (bereits registrierte
|
|
* benutzer) und requests (bereits beantragte benutzer) nach einer
|
|
* eventuellen Übereinstimmung geschaut. Eine spezielle Behandlung
|
|
* des Suchstrings ist nach meiner Meinung hier nicht nötig, da der
|
|
* String vorher auf Matrix-konforme Zeichen getestet wurde und die
|
|
* Datenbankfuntionen prepared Statements verwendet.
|
|
*/
|
|
|
|
$this->log->d("Checking if username is available");
|
|
return ! $this->userExistsInRequestsOrUsers($login);
|
|
}
|
|
|
|
private function checkRequests(string $ip): 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($ip);
|
|
$now = getNow();
|
|
|
|
// Wenn es der erste Request ist -> return true
|
|
$count = count($timestamps);
|
|
if ($count === 0) {
|
|
$this->log->d("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 5 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 bei mehr
|
|
// als 3 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(string $ip): 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 {
|
|
return $this->db->saveRequest($this->token, $ip);
|
|
} catch (Exception $e) {
|
|
$this->log->e("Error: Database returns: {$e->getMessage()}");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function sendVerificationMail(): bool {
|
|
|
|
/**
|
|
* Verschickt die Mail mit dem Verifizierungslink.
|
|
* TODO: Reicht filter_input()? Was kann hier passieren? Beim Check
|
|
* der Mailadresse verwenden wir FILTER_VALIDATE_EMAIL. SANITZE
|
|
* könnte eine Mail an eine andere Adresse schicken, als dann in der
|
|
* Datenbankn gespeichert wäre. Besser hier auch VALIDATE benutzen?
|
|
* TODO: Wie sieht das mit dem Risiko aus, daß jemand über die
|
|
* Konfigurationsdatei Code einschleust? Was kann man dagegen tun?
|
|
*/
|
|
|
|
$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->n("Validationmail successfull send to {$mailTo}");
|
|
return true;
|
|
}
|
|
$this->log->e("Sending validation mail failed");
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
|