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:
parent
e68954467c
commit
925f974c42
25
config.ini
25
config.ini
|
@ -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
|
||||
|
|
244
functions.php
244
functions.php
|
@ -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);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue