Json("secToken", self::$secTokenUse); } KV::Get()->set("secToken:".self::$id, self::$secTokenUse); KV::Get()->expire("secToken:".self::$id, 60*60); // secToken ist für 60 min * 60 s/min gültig if (!is_null(self::$secTokenVerify)) { Request::VerifyInputSecToken(self::$secTokenVerify); } } } // Blacklists the issued JWT and sets logged in status to false public static function Logout() { KV::Get()->set("JWT:Blacklist:".self::$id, Request::JWT()); KV::Get()->expireAt("JWT:Blacklist:".self::$id, self::$jwt_expires); self::$loggedIn = false; // DEPRECATED self::$loggedIn = false; } public static function VerifySecToken($secToken) { Response::Get()->Message("sTV: ".self::$secTokenVerify); return (self::$secTokenVerify==null || $secToken==self::$secTokenVerify); } // Returns true if logged in user has the (or, if array, one of the) named right(s) // If null or empty array is passed, no rights shall be required and return will be true (if logged in). // Fetched rights will be buffered, so recurring queries are no performance problem static function HasRight($rightNames) { if (!self::$loggedIn) { return false; } if ((is_array($rightNames) && empty($rightNames)) || is_null($rightNames)) { return true; } if (is_null(self::$rights)) { self::$rights = []; $qry = "SELECT r.Name FROM Personal_Verwalter pv " ."LEFT JOIN Rechte_Verwalter rv ON rv.Verwalter = pv.Verwalter " ."LEFT JOIN Rechte r ON r.ID = rv.Rechte " ."WHERE pv.Personal = ".self::$id; $res = Db::Get()->query($qry); while ($row = $res->fetch_assoc()) { self::$rights[] = $row["Name"]; } } if (is_array($rightNames)) { foreach ($rightNames as $r) { if (in_array($r, self::$rights)) { return true; } } return false; } else { return in_array($rightNames, self::$rights); } } // Returns true if logged in user is in the (or, if array, one of the) named department(s) ("Abteilungen") // Fetched departments will be buffered, so recurring queries are no performance problem public static function InDepartment($departmentIDs) { if (!self::$loggedIn) { return false; } if ((is_array($departmentIDs) && empty($departmentIDs)) || is_null($departmentIDs)) { return true; } if (!isset(self::$departments)) { self::$departments = []; $qry = "SELECT Abteilungen FROM Personal_Abteilungen WHERE Personal = ".self::$id; $res = Db::Get()->query($qry); while ($row = $res->fetch_assoc()) { self::$departments[] = $row["Abteilungen"]; } } if (is_array($departmentIDs)) { foreach ($departmentIDs as $dID) { if (in_array($dID, self::$departments)) { return true; } } return false; } else { return in_array($departmentIDs, self::$departments); } } // Returns true if logged in user is in the (or, if array, one of the) named group(s) ("Gruppen") // Fetched groups will be buffered, so recurring queries are no performance problem public static function InGroup($groupIDs) { if (!self::$loggedIn) { return false; } if ($groupIDs==null) { return true; } if (!isset(self::$groups)) { self::$groups = []; $qry = "SELECT Gruppen FROM Personal_Gruppen WHERE Personal = ".self::$id; $res = Db::Get()->query($qry); while ($row = $res->fetch_assoc()) { self::$groups[] = $row["Gruppen"]; } } if (is_array($groupIDs)) { foreach ($groupIDs as $gID) { if (in_array($gID, self::$groups)) { return true; } } return false; } else { return in_array($groupIDs, self::$groups); } } /***** Private Static functions *****/ // Tries to decode the submitted JWT, checks blacklisting of JWT and sets logged in status accordingly private static function authenticateJWT() { try { $decoded = \Firebase\JWT\JWT::decode(Request::JWT(), JWT_KEY, ['HS256']); // What has been an array before encoding is now an object // Read member variables from decoded data self::$id = $decoded->data->userID==58 && isset($_GET["emulate"]) ? $_GET["emulate"] : $decoded->data->userID; self::$user = $decoded->data->userLogin; self::$vornamen = $decoded->data->userVornamen; self::$nachnamen = $decoded->data->userNachnamen; self::$jwt_expires = $decoded->exp; } catch (Exception $e) { // JWT will throw exceptions if it cannot decode the JWT. In this case - don't login return; } if (KV::Get()->get("JWT:Blacklist:".self::$id)!=Request::JWT()) { // Not blacklisted, can log in self::$loggedIn = true; self::$secTokenVerify = KV::Get()->get("secToken:".self::$id); $now = time(); if (self::$jwt_expires-$now <= JWT_VALID_TIME*0.5 && Request::AllowJwtRenewal()) { Response::Get()->Message("Extending JWT"); self::encodeJWT(); } } else { self::$id = -99; } } private static function encodeJWT() { $time = time(); self::$jwt_expires = $time + JWT_VALID_TIME; $issuer = "http://fw-innenstadt.de/"; $token = [ "iat" => $time, "exp" => self::$jwt_expires, "iss" => "fw-innenstadt.de", "data" => [ "userID" => self::$id, "userLogin" => self::$user, "userVornamen" => self::$vornamen, "userNachnamen" => self::$nachnamen ] ]; Response::Get()->Json("jwt", \Firebase\JWT\JWT::encode($token, JWT_KEY)); } private static function authenticatePWD($user, $password) { // Copied and adjusted after https://de.wikihow.com/Ein-sicheres-Login-Skript-mit-PHP-und-MySQL-erstellen // and https://github.com/nextcloud/user_external/blob/master/lib/webdavauth.php // and https://codeofaninja.com/2018/09/rest-api-authentication-example-php-jwt-tutorial.html // Login zum gewünschten Format zurechtbiegen $user = strtolower($user); $user = str_replace("@feuerwehr-bs.net", "", $user); // Das Benutzen vorbereiteter Statements verhindert SQL-Injektion. if ($stmt = Db::Get()->prepare("SELECT p.ID, p.login, p.Vornamen, p.Nachnamen FROM Personal p WHERE p.login = ? LIMIT 1")) { $stmt->bind_param("s", $user); // Bind "$user" to parameter. $stmt->execute(); // Führe die vorbereitete Anfrage aus. $stmt->store_result(); // hole Variablen von result. $stmt->bind_result(self::$id, self::$user, self::$vornamen, self::$nachnamen); $stmt->fetch(); if ($stmt->num_rows == 1) { $url= 'https://'.urlencode(self::$user).':'.urlencode($password).'@feuerwehr-bs.net/webdav'; $headers = get_headers($url); if($headers === false) { Response::Get()->Message("ERROR: Not possible to connect to WebDAV Url: https://feuerwehr-bs.net/webdav"); // THROW return; } $returnCode= substr($headers[0], 9, 3); if (substr($returnCode, 0, 1) === '2') { // Passwort ist korrekt! // XSS-Schutz, denn eventuell wird der Wert gedruckt self::$id = preg_replace("/[^0-9]+/", "", self::$id); self::$user = preg_replace("/[^a-zA-Z0-9_\-]+/", "", self::$user); // Generiere einen Hash aus dem Passwort mit zufälligem Salt und speichere ihn in der Datenbank // Question: Why? $hash = Login::GenHash($password, Login::GenSalt(), DESIRED_ITERATIONS); Db::Get()->query("REPLACE INTO sys_iservhashes(ID, Hash) VALUES ('".self::$id."', '".$hash."')"); // Login erfolgreich. self::encodeJWT(); self::$loggedIn = true; Response::Get()->Message("Login erfolgreich!"); return; } else if ($returnCode === "401") { // Passwort ist nicht korrekt Response::Get()->Message("Das Passwort ist vermutlich nicht korrekt. Bitte erneut probieren oder an Nils wenden."); } else if ($returnCode === "503") { // Passwort ist nicht korrekt Response::Get()->Message("IServ verweigert im Moment die Anmeldung. Vermutlich gab es temporär zu viele falsche Passworteingaben " ."(auch von anderen Nutzern). Bitte erst in 10 Minuten neu probieren!"); } else { // Unbekannter Fehler Response::Get()->Code(500)->Message("Anmeldung konnte aus unbekannten Gründen nicht durchgeführt werden. Fehlercode (bitte an Nils senden): ".$returnCode); } } else { Response::Get()->Message("Benutzername inkorrekt: ".$user); } } if (Db::Get()->error!="") { Response::Get()->Message("Mysql error: ".Db::Get()->error); } return; } }