code refactoring:

- make the functions a class
- add exception handling
- change argon2 parameters to more reasonable values
- allow subdirs for request_uri
- fix ini comments
This commit is contained in:
Christian Staudte 2020-11-03 22:26:27 +01:00
parent e68954467c
commit 925f974c42
Signed by: christian.staudte
GPG key ID: 88ED5070FE0D5F23
3 changed files with 171 additions and 140 deletions

View file

@ -5,23 +5,24 @@ user = abstimmidd
password = changeme
[argon2]
memory = 4096
time = 1000
; memory 2^19 KiB = 512 MiB
memory = 19
iterations = 5
threads = 1
length = 32
length = 16
[export]
# Explanatory text at beginning of file. Placeholders:
# %t - Title
# %d - Date
# %r - Vote Round
; Explanatory text at beginning of file. Placeholders:
; %t - Title
; %d - Date
; %r - Vote Round
header = Ergebnis der Abstimmung %r am %t - %t
pgpkey = ID
# if rawpath is set, the raw JSON will be saved into the directory
; if rawpath is set, the raw JSON will be saved into the directory
rawpath = /path/to/json/dir
# path in which signed files will be stored
; path in which signed files will be stored
path = /path/to/dir
# if synccmd can be empty, if no synchronization command is needed. Placeholders:
# %s - signed file
# %u - unsigned file
; synccmd can be empty, if no synchronization command is needed. Placeholders:
; %s - signed file
; %u - unsigned file
;synccmd = rclone %s Remote:dir

View file

@ -1,127 +1,143 @@
<?php
function init() {
$cfg = parse_ini_file ( 'config.ini', $process_sections = true);
$cfg['mysqli'] = new mysqli($cfg['database']['host'], $cfg['database']['user'], $cfg['database']['password'], $cfg['database']['database']);
$cfg['database']['password'] = '';
return $cfg;
}
function get_voting_ids($cfg) {
$body = json_decode(file_get_contents('php://input'));
$event_id = get_event_id($cfg, $body->event_token);
$vote_round = $body->round;
$result = [];
if (!$event_id) {
return ["error" => "event not found"];
}
foreach($body->user_names as $name) {
$hash = get_hash($cfg, $event_id, $vote_round, $name);
$result[] = ["round" => $vote_round, "user_name" => $name, "hash" => $hash];
}
return $result;
}
class AbstimmIDd {
private $cfg;
private $mysqli;
function get_event_id($cfg, $token) {
$query = "SELECT id FROM events WHERE token=? LIMIT 1";
$stmt = $cfg['mysqli']->prepare($query);
$stmt->bind_param('s', $token);
$stmt->bind_result($event_id);
$stmt->execute();
$stmt->fetch();
$stmt->close();
return $event_id;
}
function __construct() {
$this->cfg = parse_ini_file('config.ini', $process_sections = true);
$this->mysqli = new mysqli(
$this->cfg['database']['host'],
$this->cfg['database']['user'],
$this->cfg['database']['password'],
$this->cfg['database']['database']);
$this->cfg['database']['password'] = '';
}
function register_event($cfg) {
$body = json_decode(file_get_contents('php://input'));
if (get_event_id($cfg, $body->event_token)) {
return ["success" => false];
}
$query = "INSERT INTO events (token) VALUES (?)";
$stmt = $cfg['mysqli']->prepare($query);
$stmt->bind_param("s", $body->event_token);
$stmt->execute();
$stmt->close();
return ["success" => true];
}
function get_voting_ids($body) {
$event_id = $this->get_event_id($body->event_token);
$vote_round = $body->round;
$result = [];
if (!$event_id) {
return ["error" => "event not found"];
}
foreach($body->user_names as $name) {
$hash = $this->get_hash($event_id, $vote_round, $name);
$result[] = ["round" => $vote_round, "user_name" => $name, "hash" => $hash];
}
return $result;
}
function save_hash($cfg, $event_id, $vote_round, $name, $hash) {
$query = "INSERT INTO hashes (event, vote_round, name, hash) VALUES (?, ?, ?, ?)";
$stmt = $cfg['mysqli']->prepare($query);
$stmt->bind_param("iiss", $event_id, $vote_round, $name, $hash);
$stmt->execute();
$stmt->close();
}
function get_event_id($token) {
$query = "SELECT id FROM events WHERE token=? LIMIT 1";
$stmt = $this->mysqli->prepare($query);
$stmt->bind_param('s', $token);
$stmt->bind_result($event_id);
$stmt->execute();
$stmt->fetch();
$stmt->close();
return $event_id;
}
function create_hash($cfg, $vote_round, $name) {
// The PHP password_hash function does not provide the required options
$vote_round = (int)$vote_round;
$name = addslashes($name);
$hash = shell_exec("echo -n '$name' | argon2 'Abstimmung $vote_round' -p ".$cfg["argon2"]["threads"]." -k ".$cfg["argon2"]["memory"]." -t ".$cfg["argon2"]["time"]." -l ".$cfg["argon2"]["length"]." -id -r");
return str_replace(array("\n", "\r"), '', $hash);
}
function register_event($body) {
if ($this->get_event_id($body->event_token)) {
return ["success" => false];
}
$query = "INSERT INTO events (token) VALUES (?)";
$stmt = $this->mysqli->prepare($query);
$stmt->bind_param("s", $body->event_token);
$stmt->execute();
$stmt->close();
return ["success" => true];
}
function get_hash($cfg, $event_id, $vote_round, $name) {
$hash = get_hash_db($cfg, $event_id, $vote_round, $name);
if (strlen($hash) == 0) {
$hash = create_hash($cfg, $vote_round, $name);
save_hash($cfg, $event_id, $vote_round, $name, $hash);
}
return $hash;
}
function create_hash(int $event_id, int $vote_round, string $name) : string {
// sanitize the inputs which will go to the shell
$vote_round = (int)$vote_round;
$name = addslashes($name);
function get_hash_db($cfg, $event_id, $vote_round, $name) {
$query = "SELECT hash FROM hashes WHERE event=? AND vote_round=? AND name=? LIMIT 1";
$stmt = $cfg['mysqli']->prepare($query);
$stmt->bind_param('iis', $event_id, $vote_round, $name);
$stmt->bind_result($hash);
$stmt->execute();
$stmt->fetch();
$stmt->close();
return $hash;
}
// generate the hash (the PHP password_hash function does not provide the required options)
$hash = shell_exec("echo -n '$name' | argon2 'Abstimmung $vote_round'
-p {$this->cfg["argon2"]["threads"]}
-m {$this->cfg["argon2"]["memory"]}
-t {$this->cfg["argon2"]["iterations"]}
-l {$this->cfg["argon2"]["length"]} -id -r");
$hash = str_replace(array("\n", "\r"), '', $hash);
function export_result($cfg) {
$body = json_decode(file_get_contents('php://input'));
if (!get_event_id($cfg, $body->event_token)) {
return ["success" => false];
}
$sha256 = create_text_file($cfg, $body);
if ( strlen($cfg["export"]["rawpath"])>0) {
file_put_contents(
$cfg["export"]["rawpath"] . "/" . $body->event_token . "-" . $body->vote_round .".json",
json_encode($body));
}
return ["success" => true, "sha256" => $sha256, "signing_key" => $cfg["export"]["pgpkey"]];
}
// insert the new hash into the database
$query = "INSERT INTO hashes (event, vote_round, name, hash) VALUES (?, ?, ?, ?)";
$stmt = $this->mysqli->prepare($query);
$stmt->bind_param("iiss", $event_id, $vote_round, $name, $hash);
$stmt->execute();
$stmt->close();
function create_header($cfg, $body) {
$header = str_replace("%t", $body->event_title, $cfg["export"]["header"]);
$header = str_replace("%d", date("Y-m-d"), $header);
$header = str_replace("%r", $body->vote_round, $header);
$header = $header . "\n\nAbstimm-ID | Stimme(n)" .
"\n###############################################################################\n";
return $header;
}
return $hash;
}
function create_text_file($cfg, $body) {
$file_path = $cfg["export"]["path"] . "/" . date('Y-m-d') . "__" . str_replace(" ", "_", $body->event_title) . "__Abstimmung-" . $body->vote_round . ".txt";
file_put_contents($file_path, create_header($cfg, $body));
foreach ($body->votes as $vote) {
$line = $vote->hash . " | " . implode(", ", $vote->vote) . "\n";
file_put_contents($file_path, $line, FILE_APPEND);
}
shell_exec("gpg --sign " . $file_path);
sync_command($cfg, $file_path);
return trim(shell_exec("/usr/bin/sha256sum " . $file_path. " | awk '{ print $1 }'"));
}
function get_hash(int $event_id, int $vote_round, string $name) : string {
$query = "SELECT hash FROM hashes WHERE event=? AND vote_round=? AND name=? LIMIT 1";
$stmt = $this->mysqli->prepare($query);
$stmt->bind_param('iis', $event_id, $vote_round, $name);
$stmt->bind_result($hash);
$stmt->execute();
$stmt->fetch();
$stmt->close();
if (strlen($hash) == 0)
return $this->create_hash($event_id, $vote_round, $name);
else
return $hash;
}
function export_result($body) {
if (!$this->get_event_id($body->event_token)) {
return ["success" => false];
}
$sha256 = $this->create_text_file($body);
if (strlen($this->cfg["export"]["rawpath"]) > 0) {
$filepath = "{$this->cfg["export"]["rawpath"]}/{$body->event_token}-{$body->vote_round}.json";
file_put_contents($filepath, json_encode($body));
}
return ["success" => true, "sha256" => $sha256, "signing_key" => $this->cfg["export"]["pgpkey"]];
}
function create_header($body) {
$header = str_replace("%t", $body->event_title, $this->cfg["export"]["header"]);
$header = str_replace("%d", date("d.m.Y"), $header);
$header = str_replace("%r", $body->vote_round, $header);
return <<<EOT
$header\n
Abstimm-ID | Stimme(n)
#################################|#############\n
EOT;
}
function create_text_file($body) {
$file_path = $this->cfg["export"]["path"] . "/" . date('Y-m-d') . "__" .
str_replace(" ", "_", $body->event_title) . "__Abstimmung-" . $body->vote_round . ".txt";
$file_data = $this->create_header($body);
foreach ($body->votes as $vote) {
$file_data .= $vote->hash . " | " . implode(", ", $vote->vote) . "\n";
}
$file_data .= "\n";
file_put_contents($file_path, $file_data);
shell_exec("gpg --sign $file_path");
$this->sync_command($file_path);
return trim(shell_exec("/usr/bin/sha256sum $file_path | awk '{ print $1 }'"));
}
function sync_command($file_path) {
if (isset($this->cfg["export"]["synccmd"])) {
$cmd = $this->cfg["export"]["synccmd"];
if (strlen($cmd) > 0) {
$cmd = str_replace("%s", $file_path . ".gpg", $cmd);
$cmd = str_replace("%u", $file_path, $cmd);
shell_exec($cmd);
}
}
}
function sync_command($cfg, $file_path) {
$cmd = $cfg["export"]["synccmd"];
if (strlen($cmd) > 0) {
$cmd = str_replace("%s", $file_path . ".gpg", $cmd);
$cmd = str_replace("%u", $file_path, $cmd);
shell_exec($cmd);
}
}
?>

View file

@ -1,21 +1,35 @@
<?php
require_once("../functions.php");
$cfg = init();
require_once dirname(__DIR__) . '/functions.php';
header('Content-Type: application/json');
if($_SERVER["REQUEST_URI"] == '/get_ids') {
$data = get_voting_ids($cfg);
echo json_encode($data);
} else if($_SERVER["REQUEST_URI"] == '/register_event') {
$data = register_event($cfg);
echo json_encode($data);
} else if($_SERVER["REQUEST_URI"] == '/export_result') {
$data = export_result($cfg);
echo json_encode($data);
try {
$aidd = new AbstimmIDd();
$body = json_decode(file_get_contents('php://input'));
if(ends_with($_SERVER["REQUEST_URI"], '/get_ids')) {
$data = $aidd->get_voting_ids($body);
} else if(ends_with($_SERVER["REQUEST_URI"], '/register_event')) {
$data = $aidd->register_event($body);
} else if(ends_with($_SERVER["REQUEST_URI"], '/export_result')) {
$data = $aidd->export_result($body);
} else {
$data = ["error" => "no route"];
}
echo json_encode($data);
}
else {
echo json_encode(array("error" => "no route"));
catch (Exception $e) {
http_response_code(500);
echo json_encode(["error" => $e->getMessage()]);
exit;
}
?>
function ends_with($string, $endString) {
$len = strlen($endString);
if ($len == 0)
return true;
return (substr($string, -$len) === $endString);
}