-
Notifications
You must be signed in to change notification settings - Fork 4
/
piratenid-import.php
159 lines (118 loc) · 7.15 KB
/
piratenid-import.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<?php
// TODO: review
// This file needs to be placed on the PiratenID server and requires piratenid-verify.php
// It should be reachable only from the server doing the export
// Suggestion: Deploy as a separate site, listening on a separate port
/// CONFIG ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Shared secret for encrypting and authenticating token imports - needs to match secret in piratenid-export.php
// If compromised, replace with new random value in export and import file and re-import token table
$SECRET = "7EbkyTL7N0npJhc4Gv2oXvm4mhDyYXk8cTMg2fa1bcOiiun3Xh7l5YsNNqw0";
$ALLOWED_IP = '127.0.0.1'; // IP from which tokens will be imported. Only this IP will be allowed to request token imports
function getDatabaseImportPDO() { // Database login data for token import
return new PDO('mysql:dbname=piratenid;host=127.0.0.1', "root", "");
}
$TESTING = false; // set to true to allow imports with less than 1000 entries
/// END OF CONFIG ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
error_reporting(E_ALL & E_STRICT);
function fatalErrors($errno, $errstr) { die("Fehler $errno:\n$errstr\n"); }
set_error_handler("fatalErrors");
require_once('piratenid-verify.php');
/*
PiratenIDImport_import($db, $dataarray, $ignorelength = false): Importiert Token-Daten in PiratenID
$db: Datenbank-PDO mit ausreichenden Zugriffsrechten
$dataarray: Die zu importierenden Daten als array von arrays. Jedes sub-array hat folgendes Format:
[0] => SHA256-Hash des Tokens (Hex-String, Kleinbuchstaben)
[1] => mitgliedschaft-bund - String, der die Piratenmitgliedschaft (Bundesebene) anzeigt. Entweder "ja" oder "nein".
[2] => mitgliedschaft-land
[3] => mitgliedschaft-bezirk
[4] => mitgliedschaft-kreis
[5] => mitgliedschaft-ort
[6] => stimmberechtigt - String, der die Stimmberechtigung anzeigt. Entweder "ja" oder "nein" oder leer (unbekannt).
Die Strings dürfen eine Länge von 100 Byte nicht überschreiten und müssen UTF8-kodiert sein.
Ist eine Angabe nicht bekannt, so muss in einem Feld ein leerer String übergeben werden.
Ist bekannt, dass ein Pirat auf der jeweiligen Ebene in keinem Verband Mitglied ist,
so muss der entsprechende String "---" (drei normale Bindestriche) lauten.
Es ist sicherzustellen, dass alle Mitglieder eines Verbands den gleichen Bezeichner eingetragen bekommen,
d.h. es darf nicht vorkommen, dass bei einem Mitglied des Kreisverbands Frankfurt am Main z. B. der Wert
"KV Frankfurt" und bei einem anderen "KV Frankfurt am Main" eingetragen ist.
Die Verbandsbezeichnungen dürfen nicht geändert werden. Würde beispielsweise der "KV Frankfurt" in
"KV Frankfurt am Main" umbennant, könnte dies dazu führen, dass eine Anwednung Mitgliedern des
"KV Frankfurt am Main" Zugriff auf einen Bereich verweigert, weil dieser nur für Mitglieder des "KV Frankfurt"
zugänglich ist.
$ignorelength: Zum Testen mit kleinen Datensätzen auf "true" setzen.
Ist dieser Wert false oder nicht gesetzt, wird ein Import abgelehnt, wenn weniger als 1000 Datensätze geliefert werden.
Dies soll verhindern, dass durch einen defekten Import die Token-Datenbank gelöscht wird.
*/
function PiratenIDImport_import($db, $dataarray, $ignorelength = false) {
if (!$db) die("No database connection");
if (!is_array($dataarray)) die("Invalid data: data not an array");
if (empty($dataarray)) die("Invalid data: data is empty");
if (count($dataarray) < 2) die("Invalid data: less than 2 entries");
if (!$ignorelength && count($dataarray) < 1000) die("Invalid data: less than 1000 entries, data probably incomplete");
$seenTokens = array();
foreach ($dataarray as $entry) {
PiratenIDImport_verifyEntry($entry);
if (array_key_exists($entry[0], $seenTokens)) die("Invalid data: DUPLICATE TOKEN - SOMETHING IS *SERIOUSLY* WRONG");
$seenTokens[$entry[0]] = 1;
}
try {
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->exec("SET sql_mode = TRADITIONAL");
$db->exec("SET NAMES 'utf8'");
$db->beginTransaction(); // "die()" will now cause an automatic rollback
$numDeleted = $db->exec("DELETE FROM tokens"); // Empty table. "TRUNCATE" not used due to transaction incompatibility.
$statement = $db->prepare(
"INSERT INTO tokens (`token`, `mitgliedschaft-bund`, `mitgliedschaft-land`, `mitgliedschaft-bezirk`, `mitgliedschaft-kreis`, `mitgliedschaft-ort`, `stimmberechtigt`) VALUES (?,?,?,?,?,?,?)"
);
foreach ($dataarray as $entry) { // entry has exactly the order required by the SQL statement
$statement->execute($entry);
}
$db->commit();
} catch (PDOException $e) {
die('Database error: ' . htmlspecialchars($e->getMessage())); // automatic rollback
}
}
function PiratenIDImport_importFromPost($db, $ignorelength = false) {
global $SECRET;
global $ALLOWED_IP;
if (empty($SECRET)) die("Server misconfiguration: Token import secret not set");
if (empty($ALLOWED_IP)) die("Server misconfiguration: Token import IP not set");
if (empty($_SERVER['REQUEST_METHOD'])) die("Cannot be used from command line");
if ($_SERVER['REQUEST_METHOD'] !== "POST") die("Must use POST");
if (empty($_SERVER['REMOTE_ADDR'])) die("Server did not provide remote IP");
if ($_SERVER['REMOTE_ADDR'] !== $ALLOWED_IP) die("IP not authorized for import");
$postdata = file_get_contents('php://input');
if (strlen($postdata) < 50) die("Invalid data (too short)");
$auth = substr($postdata, 0,32);
$iv = substr($postdata, 32,16);
$encrypted = substr($postdata, 48);
unset($postdata);
// Derive keys
$key_crypto_raw = hash('sha256', 'crypto|'.$SECRET, true); // encryption key
$key_hmac_raw = hash('sha256', 'hmac|'.$SECRET, true); // HMAC integrity key
$key_auth_raw = hash('sha256', 'auth|'.$SECRET, true); // Authentication token
if (strlen($key_crypto_raw) != 32 || strlen($key_hmac_raw) != 32 || strlen($key_auth_raw) != 32 ) die("Key derivation failed");
if ($key_auth_raw !== $auth) die("Invalid authorization");
$decrypted = openssl_decrypt($encrypted, 'aes-256-cbc' , $key_crypto_raw, true, $iv);
if ($decrypted === false) die("Decryption failed");
unset($encrypted); // conserve memory
$hmac = substr($decrypted, 0, 64);
$json = substr($decrypted, 64);
if (!$json) die("Parsing failed");
unset($decrypted); // conserve memory
if ($hmac !== hash_hmac('sha256', $json, $key_hmac_raw)) die("Wrong HMAC authentication value");
$data = json_decode($json);
if (empty($data)) die("JSON decode failed");
PiratenIDImport_import($db, $data, $ignorelength);
echo "Import successful";
}
PiratenIDImport_importFromPost(getDatabaseImportPDO(), $TESTING);
/* Example:
$db = new PDO('mysql:dbname=piratenid;host=127.0.0.1', "root", "");
$data = array(
array(hash('sha256','a'), 'ja', 'Hessen', '---', 'Frankfurt am Main', '', 'ja'),
array(hash('sha256','b'), 'nein', 'land', 'bezirk', 'kreis', 'ort', 'nein'),
);
// Remove "true" for production use!
PiratenIDImport_import($db, $data, true);
*/