diff --git a/class/UserMgr.php b/class/UserMgr.php new file mode 100644 index 0000000..e286b6d --- /dev/null +++ b/class/UserMgr.php @@ -0,0 +1,221 @@ +userMgr->updateUser($this); + } + + // ------------------------------------------- + + function __construct(UserMgr &$usrmgr, int|array $id_serialized, string $nickname = null, string $password = null, string $realname = null, array $groups = null, string $privilege = null) + { + if (is_array($id_serialized)) { + $init = $id_serialized; + $this->id = $id_serialized["_id"] ?? "-1"; + $this->nickname = $init["nickname"]; + $this->password = $init["password"]; + $this->realname = $init["realname"]; + $this->groups = $init["groups"]; + $this->privilege = $init["privilege"]; + } else { + $this->id = $id_serialized; + $this->nickname = $nickname; + $this->password = $password; + $this->realname = $realname; + $this->groups = $groups; + $this->privilege = $privilege; + } + + // save reference to user manager + $this->userMgr = &$usrmgr; + } + + // Create user from an array + static function fromArray(UserMgr &$usrmgr, array $a): User + { + return new User($usrmgr, $a); + } + + // Convert user to array + function toArray(array $omit = []): array + { + $a = [ + "_id" => $this->id, + "nickname" => $this->nickname, + "password" => $this->password, + "realname" => $this->realname, + "groups" => $this->groups, + "privilege" => $this->privilege + ]; + + // omit specific fields + foreach ($omit as $field) { + unset($a[$field]); + } + + return $a; + } + + // Change user password. If $safe, then $old is checked. + function changePassword(string $new, string $old, bool $safe = true): bool + { + if (!$safe || password_verify($old, $this->password)) { + $this->password = password_hash($new, PASSWORD_DEFAULT); + $this->storeMods(); // store modifications + return true; + } else { + return false; + } + } + + // Change user groups + function changeGroups(array $add, array $remove): void + { + alter_array_contents($this->groups, $add, $remove); + $this->storeMods(); // store modifications + } + + // Get user's groups + function getGroups(): array + { + return $this->groups; + } + + // Set user privilege level + function setPrivilege(string $privilege): void + { + $this->privilege = ($this->nickname === QUIZMASTER_NICKNAME) ? PRIVILEGE_QUIZMASTER : $privilege; // quizmaster's privilege mustn't be tampered with + $this->storeMods(); // store modifications + } + + // Get user privilege level + function getPrivilege(): string + { + return $this->privilege; + } + + // Get user's nickname. + function getNickname(): string + { + return $this->nickname; + } + + // Set user's real name. + function setRealname(string $realname): void { + $this->realname = $realname; + } + + // Get user's real name. + function getRealname(): string { + return $this->realname; + } + + // Check against user credentials. + function checkPassword(string $password): bool + { + return password_verify($password, $this->password); + } +} + +class UserMgr +{ + public \SleekDB\Store $db; + + function __construct() + { + // create database + $this->db = new \SleekDB\Store(USERDB, DATADIR, ["timeout" => false]); + } + + // Get user by nickname. Returns with a User object if found, null else. + function getUser(string $nickname): User|null + { + $user_data_array = $this->db->findBy(["nickname", "=", $nickname]); + return count($user_data_array) != 0 ? new User($this, $user_data_array[0]) : null; + } + + // Update user. + function updateUser(User $user): void + { + $a = $user->toArray(); // convert to array + $this->db->update($a); // update using the extracted array + } + + // checks if a nickname is taken + function isNicknameTaken(string $nickname): bool { + return !($this->db->findOneBy(["nickname", "=", $nickname]) == null); + } + + // Add new user. + function addUser(string $nickname, string $password, string $realname, array $groupids = [], string $privilege = PRIVILEGE_PLAYER): bool + { + if ($this->isNicknameTaken($nickname)) { // user exists + return false; + } + + $a = [ + "nickname" => $nickname, + "password" => password_hash($password, PASSWORD_DEFAULT), + "realname" => $realname, + "groups" => $groupids, + "privilege" => $privilege + ]; + + // create user object + $user = new User($this, $a); + + // add user to specific groups FIXME!!!! + foreach ($groupids as $groupid) { + change_group_user_assignments($groupid, $nickname, null); + } + + $this->db->insert($user->toArray(["_id"])); + + return true; // user registration successful + } + + // Delete user from the storage + function deleteUser(string $nickname): void + { + // cannot delete quizmaster + if ($nickname == QUIZMASTER_NICKNAME) { + return; + } + + $user = $this->getUser($nickname); + if ($user !== null) { + foreach ($user->getGroups() as $groupid) { + change_group_user_assignments($groupid, null, $nickname); + } + $this->db->deleteBy(["nickname", "=", $nickname]); + } + } + + // Dump all users. Users come wrapped in User objects. + function getAllUsers(): array + { + return array_map(fn($a): User => new User($this, $a), $this->db->findAll()); + } + + // Get all nicknames. + function getAllNicknames(): array + { + $qb = $this->db->createQueryBuilder(); + return array_map(fn($c): string => $c["nickname"], $qb->select(["nickname"])->getQuery()->fetch()); + } +} \ No newline at end of file diff --git a/install.php b/install.php index 80b436d..20c089e 100644 --- a/install.php +++ b/install.php @@ -1,5 +1,7 @@ addUser(QUIZMASTER_NICKNAME, $pw, ""); +$qm = $usrmgr->getUser(QUIZMASTER_NICKNAME); +$qm->setPrivilege(PRIVILEGE_QUIZMASTER); echo "Quizmaster account: quizmaster, $pw\n"; // deploy install indicator diff --git a/interface.php b/interface.php index e691a5f..9341189 100644 --- a/interface.php +++ b/interface.php @@ -24,39 +24,47 @@ require_once "controller.php"; require_once "class/ReqHandler.php"; -// ------------------------ -function patch_through_image(string $gameid, string $img_url) -{ - $game_dir = get_game_dir_by_gameid($gameid); - $image_fetch_url = $game_dir . DIRECTORY_SEPARATOR . $img_url; - - $img_fp = fopen($image_fetch_url, "r"); - if ($img_fp === false) { - $img_fp = fopen(MISSING_IMAGE_PLACEHOLDER, "r"); - } - fpassthru($img_fp); - fclose($img_fp); -} +require_once "class/UserMgr.php"; // ------------------------ -$action = $_REQUEST["action"]; +$userMgr = new UserMgr(); + +// ------------------------ $result = ""; $success = false; + +// user-related variables +$user = null; +$nickname = ""; $privilege = PRIVILEGE_NONE; +$is_quizmaster = false; +// -------- // create request handler $rh = new ReqHandler(); +// action dump callback +function dump_actions(ReqHandler &$rh, array $params): string { + return $rh->dump_actions(); +} + +$rh->add("dump_actions", [], PRIVILEGE_QUIZMASTER, "dump_actions", RESP_PLAIN, "Dump all registered actions."); + /* ------------ ACTIONS AVAILABLE WITHOUT LOGGING IN ---------- */ // login the user function login(ReqHandler &$rh, array $params): string { + global $userMgr; + global $user; + $nickname = $params["nickname"]; $password = $params["password"]; - if (check_user_credentials($nickname, $password)) { + + $user = $userMgr->getUser($nickname); + if (($user !== null) && $user->checkPassword($password)) { session_start(); $_SESSION["nickname"] = $nickname; $result = "OK"; @@ -74,9 +82,9 @@ if ((session_status() != PHP_SESSION_ACTIVE) || (!isset($_SESSION["nickname"])) goto process_and_print; } -$user_data = get_user($_SESSION["nickname"]); -$nickname = $user_data["nickname"]; -$privilege = $user_data["privilege"]; +$user = $userMgr->getUser($_SESSION["nickname"]); +$nickname = $user->getNickname(); +$privilege = $user->getPrivilege(); $is_quizmaster = $privilege === PRIVILEGE_QUIZMASTER; /* ---------- ACTIONS REQUIRING BEING LOGGED IN ------------ */ @@ -90,17 +98,17 @@ function logout(ReqHandler &$rh, array $params): string function get_user_info(ReqHandler &$rh, array $params): array { - global $user_data; - $user_data_filtered = $user_data; + global $user; + $user_data_filtered = $user->toArray(); unset($user_data_filtered["password"]); return $user_data_filtered; } function get_available_games(ReqHandler &$rh, array $params): array { - global $user_data; + global $user; $games_by_groups = []; - $groupids = $user_data["groups"]; + $groupids = $user->getGroups(); foreach ($groupids as $groupid) { $group_data = get_group($groupid); $game_collection = [ @@ -221,6 +229,19 @@ function submit_test(ReqHandler &$rh, array $params): string } } +function patch_through_image(string $gameid, string $img_url) +{ + $game_dir = get_game_dir_by_gameid($gameid); + $image_fetch_url = $game_dir . DIRECTORY_SEPARATOR . $img_url; + + $img_fp = fopen($image_fetch_url, "r"); + if ($img_fp === false) { + $img_fp = fopen(MISSING_IMAGE_PLACEHOLDER, "r"); + } + fpassthru($img_fp); + fclose($img_fp); +} + function get_image(ReqHandler &$rh, array $params): string { $img_url = trim($params["img_url"] ?? ""); @@ -248,6 +269,7 @@ $requester_nickname = $is_quizmaster ? "*" : $nickname; // "*" means every game function create_update_game(ReqHandler &$rh, array $params): array { + global $userMgr; global $nickname; global $is_quizmaster; @@ -308,7 +330,8 @@ function create_update_game(ReqHandler &$rh, array $params): array if (($game_data["owner"] === $nickname) || $is_quizmaster) { $game_data["owner"] = $owner; } - $game_data["contributors"] = array_intersect($contributors, get_all_nicknames()); + + $game_data["contributors"] = array_intersect($contributors, $usrMgr->getAllNicknames()); $game_data["properties"]["time_limit"] = $properties["time_limit"]; $game_data["properties"]["repeatable"] = $properties["repeatable"]; @@ -457,18 +480,15 @@ if ($privilege === PRIVILEGE_CREATOR) { function create_update_group(ReqHandler &$rh, array $params): string { - global $user_data; + global $user; $update = $params[ReqHandler::ACTION_KEY] === "update_group"; $groupname = trim($params["groupname"]); $description = trim($params["description"]); $editors = (!$update) ? [] : explode_list(trim($params["editors"])); - $owner = (!$update) ? $user_data["nickname"] : trim($params["owner"]); + $owner = (!$update) ? $user->getNickname() : (trim($params["owner"]) ?: $user->getNickname()); $result = "FAIL"; - if ($owner === "") { - $owner = $user_data["nickname"]; - } if ($groupname != "") { if (!$update) { create_group($groupname, $owner, $description); @@ -511,6 +531,8 @@ function search_player_groups(ReqHandler &$rh, array $params): array function create_update_user(ReqHandler &$rh, array $params): string { + global $userMgr; + $update = $params[ReqHandler::ACTION_KEY] === "update_user"; $target_nickname = trim($params["nickname"]); $password = trim($params["password"]); @@ -523,13 +545,13 @@ function create_update_user(ReqHandler &$rh, array $params): string $success = false; if (($target_nickname !== "")) { if ((!$update) && ($password !== "")) { // CREATE - $success = add_user($target_nickname, $password, $realname, $groupids, $privilege); + $success = $userMgr->addUser($target_nickname, $password, $realname, $groupids, $privilege); } else if ($update) { // UPDATE - $user_data = get_user($target_nickname); // load user data - if (count ($user_data) > 0) { + $tuser = $userMgr->getUser($target_nickname); // load user data + if ($tuser !== null) { // group management - $old_groupids = $user_data["groups"]; // retain old groupids + $old_groupids = $tuser->getGroups(); // retain old groupids $new_groupids = $groupids; // get new groupids $groupids_add = array_diff($new_groupids, $old_groupids); // groups this user needs to be added to $groupids_remove = array_diff($old_groupids, $new_groupids); // groups this user need to be removed from @@ -541,18 +563,18 @@ function create_update_user(ReqHandler &$rh, array $params): string } // re-fetch user - $user_data = get_user($target_nickname); // load user data + //$tuser = get_user($target_nickname); // load user data // further field update - $user_data["realname"] = $realname; - $user_data["privilege"] = $privilege; + $tuser->setRealname($realname); + $tuser->setPrivilege($privilege); // password replacement, if requested if ($password !== "") { - $user_data["password"] = password_hash($password, PASSWORD_DEFAULT); + $tuser->changePassword(password_hash($password, PASSWORD_DEFAULT), "", false); } - $success = update_user($user_data); + $success = true; } } } @@ -561,18 +583,23 @@ function create_update_user(ReqHandler &$rh, array $params): string } function delete_users(ReqHandler &$rh, array $params): string { - $users = explode_list($params["users"]); - foreach ($users as $g) { - delete_user($g); + global $userMgr; + $nicknames = explode_list($params["users"]); + foreach ($nicknames as $nick) { + $userMgr->deleteUser($nick); } return "OK"; } function get_all_game_users(ReqHandler &$rh, array $params): array { - $user_data_filtered = get_all_users(); - for ($i = 0; $i < count($user_data_filtered); $i++) { - unset($user_data_filtered[$i]["password"]); // remove password from records - resolve_groupids($user_data_filtered[$i]["groups"]); // resolve group IDs + global $userMgr; + $user_data_filtered = []; + $all_users = $userMgr->getAllUsers(); + for ($i = 0; $i < count($all_users); $i++) { + $a = $all_users[$i]->toArray(); // convert user to array + unset($a["password"]); // remove password from records + resolve_groupids($a["groups"]); // resolve group IDs + $user_data_filtered[] = $a; } return $user_data_filtered; } @@ -595,11 +622,13 @@ $rh->add("delete_users", ["users"], PRIVILEGE_QUIZMASTER, "delete_users", RESP_P $rh->add("get_all_users", [], PRIVILEGE_QUIZMASTER, "get_all_game_users", RESP_JSON, "Get all users."); $rh->add("import_users_from_csv", [], PRIVILEGE_QUIZMASTER, "import_users_from_csv", RESP_JSON, "Get all users."); -function dump_actions(ReqHandler &$rh, array $params): string { - return $rh->dump_actions(); +function test(ReqHandler &$rh, array $params): string { + $usrmgr = new UserMgr(); + $nicknames = $usrmgr->getAllNicknames(); + return join(", ", $nicknames); } -$rh->add("dump_actions", [], PRIVILEGE_QUIZMASTER, "dump_actions", RESP_PLAIN, "Dump all registered actions."); +$rh->add("test", [], PRIVILEGE_QUIZMASTER, "test", RESP_PLAIN, "Test."); // ---------- diff --git a/usermgr.php b/usermgr.php index d0cd936..9d3fe54 100644 --- a/usermgr.php +++ b/usermgr.php @@ -12,42 +12,42 @@ const PRIVILEGE_PLAYER = "player"; const PRIVILEGE_CREATOR = "creator"; const PRIVILEGE_QUIZMASTER = "admin"; // TODO: refactor! -function add_user(string $nickname, string $password, string $realname, array $groupids = [], string $privilege = PRIVILEGE_PLAYER): bool -{ - global $userdb; - if (count(get_user($nickname)) != 0) { // user exists - return false; - } - - $user_data = [ - "nickname" => $nickname, - "password" => password_hash($password, PASSWORD_DEFAULT), - "realname" => $realname, - "groups" => $groupids, - "privilege" => $privilege - ]; - foreach ($groupids as $groupid) { - change_group_user_assignments($groupid, $nickname, null); - } - $userdb->insert($user_data); - return true; // user registration successful -} - -function delete_user(string $nickname) -{ - global $userdb; - if ($nickname == QUIZMASTER_NICKNAME) { - return; - } - - $user_data = get_user($nickname); - if (count($user_data) !== 0) { - foreach ($user_data["groups"] as $groupid) { - change_group_user_assignments($groupid, null, $nickname); - } - $userdb->deleteBy(["nickname", "=", $nickname]); - } -} +//function add_user(string $nickname, string $password, string $realname, array $groupids = [], string $privilege = PRIVILEGE_PLAYER): bool +//{ +// global $userdb; +// if (count(get_user($nickname)) != 0) { // user exists +// return false; +// } +// +// $user_data = [ +// "nickname" => $nickname, +// "password" => password_hash($password, PASSWORD_DEFAULT), +// "realname" => $realname, +// "groups" => $groupids, +// "privilege" => $privilege +// ]; +// foreach ($groupids as $groupid) { +// change_group_user_assignments($groupid, $nickname, null); +// } +// $userdb->insert($user_data); +// return true; // user registration successful +//} +// +//function delete_user(string $nickname) +//{ +// global $userdb; +// if ($nickname == QUIZMASTER_NICKNAME) { +// return; +// } +// +// $user_data = get_user($nickname); +// if (count($user_data) !== 0) { +// foreach ($user_data["groups"] as $groupid) { +// change_group_user_assignments($groupid, null, $nickname); +// } +// $userdb->deleteBy(["nickname", "=", $nickname]); +// } +//} function get_user(string $nickname): array { @@ -62,18 +62,18 @@ function update_user(array $user_data) return $userdb->update($user_data); } -function change_password(string $nickname, string $old, string $new): bool -{ - $user_data = get_user($nickname); - if (count($user_data) != 0) { - if (password_verify($old, $user_data["password"])) { - $user_data["password"] = password_hash($new, PASSWORD_DEFAULT); - update_user($user_data); - return true; - } - } - return false; -} +//function change_password(string $nickname, string $old, string $new): bool +//{ +// $user_data = get_user($nickname); +// if (count($user_data) != 0) { +// if (password_verify($old, $user_data["password"])) { +// $user_data["password"] = password_hash($new, PASSWORD_DEFAULT); +// update_user($user_data); +// return true; +// } +// } +// return false; +//} function change_user_group_assignments(string $nickname, $groupname_add, $groupname_remove) { @@ -84,36 +84,36 @@ function change_user_group_assignments(string $nickname, $groupname_add, $groupn } } -function change_privilege_level(string $nickname, string $privilege) -{ - $user_data = get_user($nickname); - if (count($user_data) != 0) { - $user_data["privilege"] = $privilege; - update_user($user_data); - } -} - -function check_user_credentials(string $nickname, string $password): bool -{ - $user_data = get_user($nickname); - if (count($user_data) != 0) { - return password_verify($password, $user_data["password"]); - } else { - return false; - } -} - -function get_all_users(): array -{ - global $userdb; - return $userdb->findAll(); -} - -function get_all_nicknames() : array { - $nicknames = []; - $user_data_array = get_all_users(); - foreach ($user_data_array as $user_data) { - $nicknames[] = $user_data["nickname"]; - } - return $nicknames; -} \ No newline at end of file +//function change_privilege_level(string $nickname, string $privilege) +//{ +// $user_data = get_user($nickname); +// if (count($user_data) != 0) { +// $user_data["privilege"] = $privilege; +// update_user($user_data); +// } +//} +// +//function check_user_credentials(string $nickname, string $password): bool +//{ +// $user_data = get_user($nickname); +// if (count($user_data) != 0) { +// return password_verify($password, $user_data["password"]); +// } else { +// return false; +// } +//} +// +//function get_all_users(): array +//{ +// global $userdb; +// return $userdb->findAll(); +//} +// +//function get_all_nicknames() : array { +// $nicknames = []; +// $user_data_array = get_all_users(); +// foreach ($user_data_array as $user_data) { +// $nicknames[] = $user_data["nickname"]; +// } +// return $nicknames; +//} \ No newline at end of file