30 Commits

Author SHA1 Message Date
ff4947a49d update README to contain missing api endpoints; abort execution when no message should be send 2018-03-19 13:48:34 +01:00
eb414e67df use config for using static references to my installation 2018-03-15 16:26:59 +01:00
043e10596a Merge branch 'second_implementation' of gitea.krombel.de:krombel/matrix-register-bot into second_implementation 2018-03-15 15:41:26 +01:00
d58eeafdb5 fix security issue and filter on active users 2018-03-15 15:41:08 +01:00
8d84c99492 give complete config object to mxDatabase to resolve issue with undefined ref to it 2018-03-08 23:32:22 +01:00
b131e6b09e change regex for name-searching to match synapse behaviour 2018-03-08 23:04:58 +01:00
8c854f716d renamed directory_lookup => directory_search 2018-03-08 14:33:53 +01:00
d2b5cfbb5e fixes for internal mxisd endpoints 2018-03-08 13:20:26 +01:00
bce1d01b6d WIP: implement missing endpoints for mxisd 2018-03-08 12:43:17 +01:00
6b98ac4ae7 reformat and cleanup; Add auth error to response on internal login(debug) 2018-03-08 11:46:33 +01:00
d5f2b05d4d make compatible to postgres 2018-03-07 18:55:10 +01:00
cd239847ed fix: Do not publish the secret password of register_bot 2018-03-06 18:25:20 +01:00
8e50ae1bbd language: Fallback to de-de instead of failing 2018-03-06 18:08:44 +01:00
4d7da867ca WIP: capture password on registration (configurable) 2018-03-06 18:03:53 +01:00
2f0d1fc6b3 make database config configurable 2018-03-06 18:01:05 +01:00
771078e1dd Add note that passwort-setting is not available 2018-03-03 16:15:29 +01:00
379aa26e6d set decline_reason to NULL to not be printed when unset; try to fix unaccessible 2018-03-03 16:02:39 +01:00
e92c197e59 fix login 2018-03-03 15:06:51 +01:00
b4ca609c44 Merge branch 'second_implementation' of gitea.krombel.de:krombel/matrix-register-bot into second_implementation 2018-03-03 14:27:17 +01:00
ed15ec5bb5 add register_bot as user on database creation 2018-03-03 14:21:41 +01:00
01f124f744 change config to array 2018-03-03 14:21:08 +01:00
9d2af26681 fixes 2018-03-03 12:49:12 +01:00
78ae932d85 mv register.php index.php 2018-03-03 12:16:58 +01:00
56ae674c9c add HowToInstall to README; add folder internal 2018-03-03 12:15:06 +01:00
50946b73af add HowToInstall to README; add folder internal 2018-03-03 11:45:57 +01:00
9c636cdbcb only allow E-Mail-Verification when in appropriate state 2018-03-02 15:53:24 +01:00
93934cd117 Merge remote-tracking branch 'origin/first_implementation' into second_implementation 2018-03-02 15:32:34 +01:00
88003cb77e added class mxDatabase; store credentials; implement login.php 2018-03-02 14:26:36 +01:00
a1b3f159e4 Merge branch 'first_implementation' into second_implementation 2018-03-02 11:12:21 +01:00
0c38e36aba describe the new behaviour of the second implementation 2018-03-02 00:41:52 +01:00
15 changed files with 716 additions and 192 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
config.php
db_file.sqlite

View File

@@ -18,6 +18,7 @@ class MatrixConnection
$send_message = NULL;
if (!$message) {
error_log("no message to send");
return false;
} elseif(is_array($message)) {
$send_message = $message;
} elseif ($message instanceof MatrixMessage) {
@@ -127,9 +128,7 @@ class MatrixMessage
private $message;
function __construct() {
$this->message = array(
"msgtype" => "m.notice",
);
$this->message = ["msgtype" => "m.notice"];
}
function set_type($msgtype) {

View File

@@ -4,6 +4,27 @@ This bot provides a two-step-registration for matrix.
This is done in several steps:
- potential new user registers on a bot-provided side
- bot sends a message to prefined room with a registration notification.
- bot sends a message to predefined room with a registration notification.
- users in that room now can approve or decline the registration.
- The bot then uses the registration token to register the user or just drops the registration request.
- When approved
- the bot creates credentials
- sends them to the user
- stores them encrypted in own database
- provides that credentials to [matrix-synapse-rest-auth](https://github.com/kamax-io/matrix-synapse-rest-auth#integrate) which has to be configured to query login.php
2nd step: Implement the other apis to integrade [mxisd](https://github.com/kamax-io/mxisd/blob/master/docs/backends/rest.md)
## How to install
- Copy `config.sample.php` to `config.php` and configure the bot as you can find there
- Configure your webserver to publish the folder `public` and configure.
The folder `internal` contains files that can be accessed by mxisd or matrix-synapse-rest-auth
- To integrate with matrix-synapse-rest-auth:
- `/_matrix-internal/identity/v1/check_credentials` should map to `internal/login.php`
- To integrate with mxisd: Have a look at [the docs](https://github.com/kamax-io/mxisd/blob/master/docs/backends/rest.md) and apply as follows:
| Key | file which handles that | Description |
|--------------------------------|-------------------------------|------------------------------------------------------|
| rest.endpoints.auth | internal/login.php | Validate credentials and get user profile |
| rest.endpoints.directory | internal/directory_search.php | Search for users by arbitrary input |
| rest.endpoints.identity.single | internal/identity_single.php | Endpoint to query a single 3PID |
| rest.endpoints.identity.bulk | internal/identity_bulk.php | Endpoint to query a list of 3PID |

View File

@@ -1,9 +1,26 @@
<?php
$homeserver = "example.com";
$access_token = "To be used for sending the registration notification";
$register_room = '$registerRoomID:example.com';
$registration_shared_secret = "To be used for actually register the user";
$config = [
"homeserver" => "example.com",
"access_token" => "To be used for sending the registration notification",
$webroot="https://myregisterdomain.net/";
$howToURL = "https://my-url-for-storing-howTos.net";
// Which e-mail-adresse shall the bot use to send e-mails?
"register_email" => 'register_bot@example.com',
// Where should the bot post registration requests to?
"register_room" => '$registerRoomID:example.com',
// Where is the public part of the bot located? make sure you have a / at the end
"webroot" => "https://myregisterdomain.net/",
// optional: Do you have a place where howTo's are located? If not leave this value out
"howToURL" => "https://my-url-for-storing-howTos.net",
// When you want to collect the password on registration set this to true
"getPasswordOnRegistration" => false,
// to define where the data should be stored:
"databaseURI" => "sqlite:" . dirname(__FILE__) . "/db_file.sqlite",
// credentials for sqlite not used
"databaseUser" => "dbUser123",
"databasePass" => "secretPassword",
]
?>

View File

@@ -10,7 +10,7 @@ $sql = "SELECT id, first_name, last_name, username, email, state, note, verify_t
. " OR state = " . RegisterState::PendingSendRegistrationMail
. " OR state = " . RegisterState::RegistrationDeclined
. " OR state = " . RegisterState::AllDone . ";";
foreach ($db->query($sql) as $row) {
foreach ($mx_db->query($sql) as $row) {
$first_name = $row["first_name"];
$last_name = $row["last_name"];
$username = $row["username"];
@@ -20,39 +20,37 @@ foreach ($db->query($sql) as $row) {
try {
switch ($state) {
case RegisterState::PendingEmailSend:
$verify_url = $webroot . "/verify.php?t=" . $row["verify_token"];
$verify_url = $config["webroot"] . "/verify.php?t=" . $row["verify_token"];
$success = send_mail_pending_verification(
$homeserver,
$config["homeserver"],
$row["first_name"] . " " . $row["last_name"],
$row["email"],
$row["verify_url"]);
$verify_url);
if ($success) {
$db->exec("UPDATE registrations SET state = " . RegisterState::PendingEmailVerify
. " WHERE id = " . $row["id"] . ";");
$mx_db->setRegistrationStateById(RegisterState::PendingEmailVerify, $row["id"]);
} else {
throw new Exception("Could not send mail to ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].")");
}
break;
case RegisterState::PendingAdminSend:
require_once("MatrixConnection.php");
$adminUrl = $webroot . "/verify_admin.php?t=" . $row["admin_token"];
$mxConn = new MatrixConnection($homeserver, $access_token);
$adminUrl = $config["webroot"] . "/verify_admin.php?t=" . $row["admin_token"];
$mxConn = new MatrixConnection($config["homeserver"], $config["access_token"]);
$mxMsg = new MatrixMessage();
$mxMsg->set_body($first_name . ' ' . $last_name . "möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n"
$mxMsg->set_body($first_name . ' ' . $last_name . " möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n"
. $row["note"] . "\r\n"
. "Zum Bearbeiten hier klicken:\r\n" . $adminUrl);
$mxMsg->set_formatted_body($first_name . ' ' . $last_name . " möchte sich registrieren und hat folgende Notiz hinterlassen:<br />"
. $row["note"] . "<br />"
. "Zum Bearbeiten <a href=\"". $adminUrl . "\">hier</a> klicken");
$mxMsg->set_type("m.text");
$response = $mxConn->send($register_room, $mxMsg);
$response = $mxConn->send($config["register_room"], $mxMsg);
if ($response) {
$db->exec("UPDATE registrations SET state = " . RegisterState::PendingAdminVerify
. " WHERE id = " . $row["id"] . ";");
$mx_db->setRegistrationStateById(RegisterState::PendingAdminVerify, $row["id"]);
send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email);
send_mail_pending_approval($config["homeserver"], $first_name . " " . $last_name, $email);
} else {
throw new Exception("Could not send notification for ".$row["first_name"]." ".$row["last_name"]."(".$row["id"].") to admins.");
}
@@ -60,23 +58,21 @@ foreach ($db->query($sql) as $row) {
case RegisterState::PendingRegistration:
// Registration got accepted but registration failed
// register user
require_once("MatrixConnection.php");
$mxConn = new MatrixConnection($homeserver, $access_token);
// generate a password with 8 characters
$password = bin2hex(openssl_random_pseudo_bytes(4));
$res = $mxConn->register($username, $password, $shared_secret);
if ($res) {
$password = $mx_db->addUser($row["first_name"], $row["last_name"], $row["username"], $row["email"]);
if ($password != NULL) {
// send registration_success
send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL);
$res = send_mail_registration_success($config["homeserver"], $first_name . " " . $last_name, $email, $username, $password, $config["howToURL"]);
if ($res) {
$mx_db->setRegistrationStateById(RegisterState::AllDone, $row["id"]);
} else {
$mx_db->setRegistrationStateById(RegisterState::PendingSendRegistrationMail, $row["id"]);
}
} else {
send_mail_registration_allowed_but_failed($homeserver, $first_name . " " . $last_name, $email);
send_mail_registration_allowed_but_failed($config["homeserver"], $first_name . " " . $last_name, $email);
$mxMsg = new MatrixMessage();
$mxMsg->set_type("m.text");
$mxMsg->set_body("Fehler beim Registrieren von " . $first_name . " " . $last_name . ".");
$mxConn->send($register_room, $mxMsg);
$mxConn->send($config["register_room"], $mxMsg);
throw new Exception($language["REGISTRATION_FAILED"]);
}
break;

View File

@@ -1,5 +1,8 @@
<?php
$db_file = dirname(__DIR__)."/db_file.sqlite";
<?php
require_once("config.php");
if (!isset($config["databaseURI"])) {
throw new Exception ("malformed configuration: databaseURI not defined");
}
abstract class RegisterState
{
@@ -26,30 +29,311 @@ abstract class RegisterState
const AllDone = 100;
}
// create database file when not existent yet
if (!file_exists($db_file)) {
$db = new PDO('sqlite:' . $db_file);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$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)");
}
else {
// establish connection
$db = new PDO('sqlite:' . $db_file);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
class mxDatabase
{
private $db = NULL;
// set writeable when not set already
if (!is_writable($db_file)) {
chmod($db_file, 0777);
/**
* Creates mxDatabase object
* @param config object which has following members:
* databaseURI: path to the sqlite file where the credentials should be stored
* or a param which can be used to connect to a database with PDO
* databaseUser and databasePass when authentication is required
* register_email which email does the register bot have (here used for providing lookup)
*/
function __construct($config) {
if (empty($config)) {
throw new Exception("config is empty");
}
if (!isset($config["databaseURI"])) {
throw new Exception("'databaseURI' not defined");
}
$db_input = $config["databaseURI"];
$user = '';
$password = '';
if (isset($config["databaseUser"]) && isset($config["databasePass"])) {
// only use it when both are defined
$user = $config["databaseUser"];
$password = $config["databasePass"];
}
// create database file when not existent yet
$this->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)) {
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 . "' 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 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);
}
?>

View File

@@ -0,0 +1,36 @@
<?php
require_once("../database.php");
$response=[
"limited" => false,
"result" => [],
];
try {
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE);
if (empty($input)) {
throw new Exception('no valid json as input present');
}
if (!isset($input["by"])) {
throw new Exception('"by" is not defined');
}
if (!isset($input["search_term"])) {
throw new Exception('"search_term" is not defined');
}
switch ($input["by"]) {
case "name":
$response["result"] = $mx_db->searchUserByName($input["search_term"]);
break;
case "threepid":
$response["result"] = $mx_db->searchUserByEmail($input["search_term"]);
break;
default:
throw new Exception("unknown type for \"by\" param");
}
} catch (Exception $e) {
error_log("failed with error: " . $e->getMessage());
$response["error"] = $e->getMessage();
}
print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
?>

View File

@@ -0,0 +1,53 @@
<?php
require_once("../database.php");
$response = [
"lookup" => []
];
try {
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE);
if (!isset($input)) {
throw new Exception('request body is no valid json');
}
if (!isset($input["lookup"])) {
throw new Exception('"lookup" is not defined');
}
if (!is_array($input["lookup"])) {
throw new Exception('"lookup" is not an array');
}
foreach ($input["lookup"] as $lookup) {
if (!isset($lookup["medium"])) {
throw new Exception('"lookup.medium" is not defined');
}
if (!isset($lookup["address"])) {
throw new Exception('"lookup.address" is not defined');
}
$res2 = array();
switch ($lookup["medium"]) {
case "email":
$res2 = $mx_db->searchUserByEmail($lookup["address"]);
if (!empty($res2)) {
array_push($response["lookup"], [
"medium" => $lookup["medium"],
"address" => $lookup["address"],
"id" => [
"type" => "localpart",
"value" => $res2[0]["user_id"],
]
]
);
}
break;
case "msisdn":
break;
default:
throw new Exception("unknown type for \"by\" param");
}
}
} catch (Exception $e) {
error_log("ídentity_bulk failed with error: " . $e->getMessage());
$response["error"] = $e->getMessage();
}
print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
?>

View File

@@ -0,0 +1,46 @@
<?php
require_once("../database.php");
$response = new stdClass;
try {
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE);
if (empty($input)) {
throw new Exception('no valid json as input present');
}
if (!isset($input["lookup"])) {
throw new Exception('"lookup" is not defined');
}
if (!isset($input["lookup"]["medium"])) {
throw new Exception('"lookup.medium" is not defined');
}
if (!isset($input["lookup"]["address"])) {
throw new Exception('"lookup.address" is not defined');
}
$res2 = array();
switch ($input["lookup"]["medium"]) {
case "email":
$res2 = $mx_db->searchUserByEmail($input["lookup"]["address"]);
if (!empty($res2)) {
$response = [
"lookup" => [
"medium" => $input["lookup"]["medium"],
"address" => $input["lookup"]["address"],
"id" => [
"type" => "localpart",
"value" => $res2[0]["user_id"],
]
]
];
}
break;
default:
throw new Exception("unknown type for \"by\" param");
}
} catch (Exception $e) {
error_log("ídentity_bulk failed with error: " . $e->getMessage());
$response["error"] = $e->getMessage();
}
print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
?>

98
internal/login.php Normal file
View File

@@ -0,0 +1,98 @@
<?php
$response = [
"auth" => [
"success" => false,
]
];
require_once("../database.php");
abstract class LoginRequester {
const UNDEFINED = 0;
const MXISD = 1;
const RestAuth = 2;
}
$loginRequester = LoginRequester::UNDEFINED;
try {
$inputJSON = file_get_contents('php://input');
$input = json_decode($inputJSON, TRUE);
$mxid = NULL;
$localpart = NULL;
if (isset($input["user"])) {
if (isset($input["user"]["localpart"])) {
$localpart = $input["user"]["localpart"];
$loginRequester = LoginRequester::MXISD;
} elseif (isset($input["user"]["id"])) {
// compatibility for matrix-synapse-rest-auth
$mxid = $input["user"]["id"];
$loginRequester = LoginRequester::RestAuth;
} elseif (isset($input["user"]["mxid"])) {
// compatibility for mxisd
$mxid = $input["user"]["mxid"];
$loginRequester = LoginRequester::MXISD;
}
}
// prefer the localpart attribute of mxisd. But in case of matrix-synapse-rest-auth
// we have to parse it on our own
if (empty($localpart) && !empty($mxid)) {
// A mxid would start with an @ so we start at the 2. position
$sepPos = strpos($mxid,':', 1);
if ($sepPos === false) {
// : not found. Assume mxid is localpart
// TODO: further checks
$localpart = $mxid;
} else {
$localpart = substr($mxid, 1, strpos($mxid,':') - 1 );
}
}
if (empty($localpart)) {
throw new Exception ("localpart cannot be identified");
}
$password = NULL;
if (isset($input["user"]) && isset($input["user"]["password"])) {
$password = $input["user"]["password"];
}
if (empty($password)) {
throw new Exception ("password is not present");
}
$user = $mx_db->getUserForLogin($localpart, $password);
if (!$user) {
throw new Exception("user not found or password did not match");
}
$response["auth"]["success"] = true;
$response["auth"]["profile"] = [
"display_name" => $user["first_name"] . " " . $user["last_name"],
"three_pids" => [
[
"medium" => "email",
"address" => $user["email"],
],
],
];
switch ($loginRequester) {
case LoginRequester::RestAuth:
$response["auth"]["mxid"] = $mxid;
break;
case LoginRequester::MXISD;
$response["auth"]["id"] = [
"type" => "localpart",
"value" => $localpart,
];
break;
default:
// only return that it was successful.
// we do not know how the data shall be transmitted so we do nothing with it
$response["auth"]["success"] = false;
break;
}
} catch (Exception $e) {
error_log("Auth failed with error: " . $e->getMessage());
$response["auth"]["error"] = $e->getMessage();
}
print (json_encode($response, JSON_PRETTY_PRINT) . "\n");
?>

View File

@@ -5,7 +5,8 @@ if(isset($_GET['lang'])){
}
$lang_file = dirname(__FILE__) . "/lang/lang.".$lang.".php";
if (!file_exists($lang_file)) {
throw new Exception("Translation for " . $lang . " not found");
error_log("Translation for " . $lang . " not found. Fallback to 'de-de'");
$lang = "de-de";
}
require_once($lang_file);
unset($lang_file);

View File

@@ -1,7 +1,8 @@
<?php
function send_mail($receiver, $subject, $body) {
$headers = "From: registration@cg-s.tk\r\n"
include("config.php");
$headers = "From: " . $config["register_email"] . "\r\n"
. "Content-Type: text/plain;charset=utf-8";
return mail($receiver, $subject, $body, $headers);
}
@@ -67,10 +68,14 @@ Zum Anmelden kannst du folgende Zugangsdaten verwenden:
Nutzername: $username
Passwort: $password
Hinweis: Aktuell ist es nicht möglich, das Passwort selbst zu ändern. Sobald die Funktionalität zur
Verfügung steht, gibt es aber einen Hinweis.
";
/*
Wichtig: Bitte ändere das Passwort direkt nach der Anmeldung.
Es wird zwar von unserer Seite nicht gespeichert, doch fremde könnten Zugriff auf diese E-Mail
erhalten und so deinen Account kompromittieren.
";
*/
if (!empty($howToURL)) {
$body .= "
Zu weiteren Hilfestellungen findest du hier eine Auflistung von verschiedenen

View File

@@ -7,7 +7,6 @@ if (!file_exists("../config.php")) {
exit();
}
require_once "../config.php";
require_once "../mail_templates.php";
// enforce admin via https
if (!isset($_SERVER['HTTPS'])) {
@@ -17,8 +16,6 @@ if (!isset($_SERVER['HTTPS'])) {
session_start();
require_once("../database.php");
if ($_SERVER["REQUEST_METHOD"] == "POST") {
try {
if (!isset($_SESSION["token"]) || !isset($_POST["token"]) || $_SESSION["token"] != $_POST["token"]) {
@@ -34,6 +31,10 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
if (ctype_alnum($_POST['username']) != true) {
throw new Exception($language["USERNAME_NOT_ALNUM"]);
}
if (isset($config["getPasswordOnRegistration"]) && $config["getPasswordOnRegistration"] &&
$_POST["password"] != $_POST["password_confirm"]) {
throw new Exception($language["PASSWORD_NOT_MATCH"]);
}
if (isset($_POST["note"]) && strlen($_POST["note"]) > 50) {
throw new Exception($language["NOTE_LENGTH_EXEEDED"]);
}
@@ -47,71 +48,52 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
throw new Exception($language["SIRNAME_INVALID_FORMAT"]);
}
// check valid password
$first_name = filter_var($_POST["first_name"], FILTER_SANITIZE_STRING);
$last_name = filter_var($_POST["last_name"], FILTER_SANITIZE_STRING);
$username = filter_var($_POST["username"], FILTER_SANITIZE_STRING);
if (isset($_POST["password"])) {
$password = filter_var($_POST["password"], FILTER_SANITIZE_STRING);
}
$note = filter_var($_POST["note"], FILTER_SANITIZE_STRING);
$email = filter_var($_POST["email"], FILTER_VALIDATE_EMAIL);
$verify_token = bin2hex(random_bytes(16));
$admin_token = bin2hex(random_bytes(16));
# $first="test"; $last="test2"; $user="test3"; $note="empty"; $email="mail+test1@matthias-kesler.de";
require_once("../database.php");
$res = $mx_db->addRegistration($first_name, $last_name, $username, $note, $email);
$sql = "SELECT COUNT(*) FROM registrations WHERE username = '" . $username . "' AND NOT state = "
. RegisterState::RegistrationDeclined . " LIMIT 1;";
$res = $db->query($sql);
if ($res->fetchColumn() > 0) {
throw new Exception($language["USERNAME_PENDING_REGISTRATION"]);
}
require_once("../MatrixConnection.php");
$mxConn = new MatrixConnection($homeserver, $access_token);
if ($mxConn->hasUser($username)) {
throw new Exception($language["USERNAME_REGISTERED"]);
if (!isset($res["verify_token"])) {
error_log("sth. went wrong. registration did not throw but admin_token not set");
throw Exception ("Unknown Error");
}
$verify_token = $res["verify_token"];
$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.'")');
# $ins_stmt->bindValue(':first_name', $first);
# $ins_stmt->bindValue(':last_lame', $last);
# $ins_stmt->bindValue(':username', $user);
# $ins_stmt->bindValue(':note', $note);
# $ins_stmt->bindValue(':email', $email);
# $ins_stmt->bindValue(':verify_token', $vToken);
# $ins_stmt->bindValue(':admin_token', $adminToken);
# $ins_stmt->bindValue(':now', date('Y-m-d H:i:s'));
#
# $ins_stmt->execute();
$verify_url = $webroot . "/verify.php?t=" . $verify_token;
$verify_url = $config["webroot"] . "/verify.php?t=" . $verify_token;
require_once "../mail_templates.php";
$success = send_mail_pending_verification(
$homeserver,
$config["homeserver"],
$first_name . " " . $last_name,
$email,
$verify_url);
$db->exec("UPDATE registrations SET state = " .
($success ? RegisterState::PendingEmailVerify : RegisterState::PendingEmailSend)
. " WHERE verify_token = \"" . $verify_token. "\";");
$mx_db->setRegistrationStateVerify(
($success ? RegisterState::PendingEmailVerify : RegisterState::PendingEmailSend),
$verify_token);
print("<title>Erfolgreich</title>");
print("</head><body>");
print("<h1>Erfolgreich</h1>");
print("<p>Bitte überprüfe deine E-Mails um deine E-Mail-Adresse zu bestätigen.</p>");
print("<a href=\"" . "/register.php" . "\">Zur Registrierungsseite</a>");
print("<a href=\"" . $config["webroot"] . "/index.php" . "\">Zur Registrierungsseite</a>");
} catch (Exception $e) {
print("<title>" . $language["REGISTRATION_REQUEST_FAILED"] . "</title>");
print("</head><body>");
print("<h1>" . $language["REGISTRATION_REQUEST_FAILED"] . "</h1>");
print("<p>" . $e->getMessage() . "</p>");
print("<a href=\"" . $webroot . "/register.php" . "\">Zur Registrierungsseite</a>");
print("<a href=\"" . $config["webroot"] . "/index.php" . "\">Zur Registrierungsseite</a>");
}
} else {
$_SESSION["token"] = bin2hex(random_bytes(16));
?>
<title>Registriere dich für <?php echo $homeserver; ?></title>
<title>Registriere dich für <?php echo $config["homeserver"]; ?></title>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css" rel="stylesheet">
<style>
body{
@@ -135,21 +117,21 @@ body{
<div class="col-xs-12 col-sm-8 col-md-4 col-sm-offset-2 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Bitte für <?php echo $homeserver; ?> registrieren<small>2-Schritt-Registrierung</small></h3>
<h3 class="panel-title">Bitte für <?php echo $config["homeserver"]; ?> registrieren<small>2-Schritt-Registrierung</small></h3>
</div>
<div class="panel-body">
<form name="regForm" role="form" action="register.php" method="post">
<form name="regForm" role="form" action="index.php" method="post">
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<input type="text" name="first_name" id="first_name" class="form-control input-sm"
placeholder="Vorname" pattern="[A-Z][a-z]+">
placeholder="Vorname" pattern="[A-Z][a-z]+">
</div>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<input type="text" name="last_name" id="last_name" class="form-control input-sm"
placeholder="Nachname" pattern="[A-Z][a-z]+">
placeholder="Nachname" pattern="[A-Z][a-z]+">
</div>
</div>
</div>
@@ -164,33 +146,31 @@ body{
<div class="form-group">
<input type="text" name="username" id="username" class="form-control input-sm"
placeholder="Nutzername (für den Login)"
pattern="[a-z1-9]{3,20}"
required>
placeholder="Nutzername (für den Login)" pattern="[a-z1-9]{3,20}" required>
</div>
<?php /**
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-sm" placeholder="Passwort" required>
</div>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<input type="password" name="password_confirm" id="password_confirm" class="form-control input-sm" placeholder="Passwort bestätigen" required>
</div>
</div>
</div>
*/ ?>
<?php if (isset($config["getPasswordOnRegistration"]) && $config["getPasswordOnRegistration"]) { ?>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<input type="password" name="password" id="password" class="form-control input-sm" placeholder="Passwort" required>
</div>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<input type="password" name="password_confirm" id="password_confirm" class="form-control input-sm" placeholder="Passwort bestätigen" required>
</div>
</div>
</div>
<?php } ?>
<input type="hidden" name="token" id="token" value="<?php echo $_SESSION["token"]; ?>">
<input type="submit" value="Registrieren" class="btn btn-info btn-block">
</form>
<p>Hinweis: <br />
cg-s.tk is ein geschlossenes Chat-Netzwerk in dem jeder Nutzer bestätigt werden muss.<br />
<?php echo $config["homeserver"]; ?> ist ein geschlossenes Chat-Netzwerk in dem jeder Nutzer bestätigt werden muss.<br />
Du bekommst eine E-Mail wenn jemand deine Mitgliedschaft bestätigt hat. An diese wird auch dein initiales Passwort gesendet.
Hinterlasse also bitte einen Hinweis zu dir (der nur den entsprechenden Personen gezeigt wird).<br />
Liebe Grüße vom Team von cg-s.tk
Liebe Grüße vom Team von <?php echo $config["homeserver"]; ?>
</p>
</div>
</div>
@@ -219,7 +199,20 @@ body{
user_name.onkeyup = function (event) {
event.target.setCustomValidity("");
}
</script>
<?php if (isset($config["getPasswordOnRegistration"]) && $config["getPasswordOnRegistration"]) { ?>
var password = document.getElementById("password")
, confirm_password = document.getElementById("password_confirm");
function validatePassword(){
if(password.value != confirm_password.value) {
confirm_password.setCustomValidity("Passwörter stimmen nicht überein");
} else {
confirm_password.setCustomValidity('');
}
}
password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
<?php } ?>
</script>
<?php } ?>
</body>
</html>

View File

@@ -28,30 +28,19 @@ try {
require_once("../database.php");
$sql = "SELECT COUNT(*) FROM registrations WHERE verify_token = '" . $token . "'"
. " AND state = " . RegisterState::PendingEmailVerify . " LIMIT 1;";
$res = $db->query($sql);
$first_name = NULL; $last_name = NULL; $note = NULL; $email = NULL; $admin_token = NULL;
if ($res->fetchColumn() > 0) {
$sql = "SELECT first_name, last_name, note, email, admin_token FROM registrations WHERE verify_token = '" . $token . "'"
. " AND state = " . RegisterState::PendingEmailVerify . " LIMIT 1;";
foreach ($db->query($sql) as $row) {
// will only be executed once
$first_name = $row["first_name"];
$last_name = $row["last_name"];
$note = $row["note"];
$email = $row["email"];
$admin_token = $row["admin_token"];
}
} else {
$user = $mx_db->getUserForVerify($token);
if ($user == NULL) {
throw new Exception($language["UNKNOWN_TOKEN"]);
}
$first_name = $user["first_name"];
$last_name = $user["last_name"];
$note = $user["note"];
$email = $user["email"];
$admin_token = $user["admin_token"];
require_once("../MatrixConnection.php");
$adminUrl = $webroot . "verify_admin.php?t=" . $admin_token;
$mxConn = new MatrixConnection($homeserver, $access_token);
$adminUrl = $config["webroot"] . "/verify_admin.php?t=" . $admin_token;
$mxConn = new MatrixConnection($config["homeserver"], $config["access_token"]);
$mxMsg = new MatrixMessage();
$mxMsg->set_body($first_name . ' ' . $last_name . "möchte sich registrieren und hat folgende Notiz hinterlassen:\r\n"
. $note . "\r\n"
@@ -60,28 +49,28 @@ try {
. $note . "<br />"
. "Zum Bearbeiten <a href=\"". $adminUrl . "\">hier</a> klicken");
$mxMsg->set_type("m.text");
$response = $mxConn->send($register_room, $mxMsg);
$response = $mxConn->send($config["register_room"], $mxMsg);
if ($response) {
$message = $language["SEND_MATRIX_FAIL"];
}
$db->exec("UPDATE registrations SET state = " .
($response ? RegisterState::PendingAdminVerify : RegisterState::PendingAdminSend)
. " WHERE verify_token = \"" . $token. "\";");
$mx_db->setRegistrationStateVerify(
($response ? RegisterState::PendingAdminVerify : RegisterState::PendingAdminSend),
$token);
send_mail_pending_approval($homeserver, $first_name . " " . $last_name, $email);
send_mail_pending_approval($config["homeserver"], $first_name . " " . $last_name, $email);
print("<title>" . $language["VERIFICATION_SUCEEDED"] . "</title>");
print("</head><body>");
print("<h1>" . $language["VERIFICATION_SUCEEDED"] . "</h1>");
print("<p>" . $language["VERIFICATION_SUCCESS_BODY"] . "</p>");
print("<a href=\"" . $webroot . "register.php" . "\">Zur Registrierungsseite</a>");
print("<a href=\"" . $config["webroot"] . "/index.php" . "\">Zur Registrierungsseite</a>");
} catch (Exception $e) {
print("<title>" . $language["VERIFICATION_FAILED"] . "</title>");
print("</head><body>");
print("<h1>" . $language["VERIFICATION_FAILED"] . "</h1>");
print("<p>" . $e->getMessage() . "</p>");
print("<a href=\"" . $webroot . "register.php" . "\">Zur Registrierungsseite</a>");
print("<a href=\"" . $config["webroot"] . "/index.php" . "\">Zur Registrierungsseite</a>");
}
?>
</body>

View File

@@ -32,7 +32,7 @@ try {
if (isset($_GET["allow"])) {
$action = RegisterState::RegistrationAccepted;
}
$decline_reason = "Noch nicht implementiert";
$decline_reason = NULL;
if (isset($_GET["deny"])) {
$action = RegisterState::RegistrationDeclined;
if (isset($_GET["reason"])) {
@@ -40,54 +40,40 @@ try {
}
}
$sql = "SELECT COUNT(*) FROM registrations WHERE admin_token = '" . $token
. "' AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;";
$res = $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 = '" . $token
. "' AND state = " . RegisterState::PendingAdminVerify . " LIMIT 1;";
foreach ($db->query($sql) as $row) {
// will only be executed once
$first_name = $row["first_name"];
$last_name = $row["last_name"];
$username = $row["username"];
$note = $row["note"];
$email = $row["email"];
}
} else {
$user = $mx_db->getUserForApproval($token);
if ($user == NULL) {
throw new Exception($language["UNKNOWN_TOKEN"]);
}
$first_name = $user["first_name"];
$last_name = $user["last_name"];
$username = $user["username"];
$note = $user["note"];
$email = $user["email"];
if ($action == RegisterState::RegistrationAccepted) {
$db->exec("UPDATE registrations SET state = " . RegisterState::PendingRegistration
. " WHERE admin_token = '" . $token. "';");
$mx_db->setRegistrationStateAdmin(RegisterState::PendingRegistration, $token);
// register user
require_once("../MatrixConnection.php");
$mxConn = new MatrixConnection($homeserver, $access_token);
$mxConn = new MatrixConnection($config["homeserver"], $config["access_token"]);
// generate a password with 8 characters
$password = bin2hex(openssl_random_pseudo_bytes(4));
$res = $mxConn->register($username, $password, $registration_shared_secret);
if ($res) {
$password = $mx_db->addUser($first_name, $last_name, $username, $email);
if ($password != NULL) {
// send registration_success
$res = send_mail_registration_success($homeserver, $first_name . " " . $last_name, $email, $username, $password, $howToURL);
$res = send_mail_registration_success($config["homeserver"], $first_name . " " . $last_name, $email, $username, $password, $config["howToURL"]);
if ($res) {
$db->exec("UPDATE registrations SET state = " . RegisterState::AllDone
. " WHERE admin_token = '" . $token. "';");
$mx_db->setRegistrationStateAdmin(RegisterState::AllDone, $token);
} else {
$db->exec("UPDATE registrations SET state = " . RegisterState::PendingSendRegistrationMail
. " WHERE admin_token = '" . $token. "';");
$mx_db->setRegistrationStateAdmin(RegisterState::PendingSendRegistrationMail, $token);
}
} else {
send_mail_registration_allowed_but_failed($homeserver, $first_name . " " . $last_name, $email);
send_mail_registration_allowed_but_failed($config["homeserver"], $first_name . " " . $last_name, $email);
$mxMsg = new MatrixMessage();
$mxMsg->set_type("m.text");
$mxMsg->set_body("Fehler beim Registrieren von " . $first_name . " " . $last_name . ".");
$mxConn->send($register_room, $mxMsg);
$mxConn->send($config["register_room"], $mxMsg);
throw new Exception($language["REGISTRATION_FAILED"]);
}
@@ -96,9 +82,8 @@ try {
print("<h1>" . $language["ADMIN_VERIFY_SITE_TITLE"] . "</h1>");
print("<p>" . $language["ADMIN_REGISTER_ACCEPTED_BODY"] . "</p>");
} elseif ($action == RegisterState::RegistrationDeclined) {
$db->exec("UPDATE registrations SET state = " . RegisterState::RegistrationDeclined
. " WHERE admin_token = '" . $token. "';");
send_mail_registration_decline($homeserver, $first_name . " " . $last_name, $email, $decline_reason);
$mx_db->setRegistrationStateAdmin(RegisterState::RegistrationDeclined, $token);
send_mail_registration_decline($config["homeserver"], $first_name . " " . $last_name, $email, $decline_reason);
print("<title>" . $language["ADMIN_VERIFY_SITE_TITLE"] . "</title>");
print("</head><body>");
print("<h1>" . $language["ADMIN_VERIFY_SITE_TITLE"] . "</h1>");
@@ -176,7 +161,7 @@ background: rgba(255, 255, 255, 0.8);
print("</head><body>");
print("<h1>" . $language["REGISTRATION_FAILED"] . "</h1>");
print("<p>" . $e->getMessage() . "</p>");
print("<a href=\"" . $webroot . "/register.php" . "\">Zur Registrierungsseite</a>");
print("<a href=\"" . $config["webroot"] . "/index.php" . "\">Zur Registrierungsseite</a>");
}
?>
</body>