diff --git a/resources/autoload.php b/resources/autoload.php index 5c0c25c2..18388328 100644 --- a/resources/autoload.php +++ b/resources/autoload.php @@ -24,6 +24,8 @@ require_once __DIR__ . "/lib/UnityWebhook.php"; require_once __DIR__ . "/lib/UnityRedis.php"; require_once __DIR__ . "/lib/UnityGithub.php"; +require_once __DIR__ . "/lib/exceptions/PhpUnitNoDieException.php"; +require_once __DIR__ . "/lib/exceptions/UnitySQLRecordNotFound.php"; // run init script require __DIR__ . "/init.php"; diff --git a/resources/lib/UnitySQL.php b/resources/lib/UnitySQL.php index d9be2ca9..96939dec 100644 --- a/resources/lib/UnitySQL.php +++ b/resources/lib/UnitySQL.php @@ -3,6 +3,7 @@ namespace UnityWebPortal\lib; use PDO; +use PDOException; class UnitySQL { @@ -35,239 +36,193 @@ public function getConn() return $this->conn; } - // - // requests table methods - // - public function addRequest($requestor, $dest = self::REQUEST_BECOME_PI) + private function execute($statement) { - if ($this->requestExists($requestor, $dest)) { - return; + try { + $statement->execute(); + } catch (PDOException $e) { + ob_start(); + $statement->debugDumpParams(); + $sql_debug_dump = ob_get_clean(); + throw new PDOException($sql_debug_dump, 0, $e); } - - $stmt = $this->conn->prepare( - "INSERT INTO " . self::TABLE_REQS . " (uid, request_for) VALUES (:uid, :request_for)" - ); - $stmt->bindParam(":uid", $requestor); - $stmt->bindParam(":request_for", $dest); - - $stmt->execute(); } - public function removeRequest($requestor, $dest = self::REQUEST_BECOME_PI) + private function search($table, $filters) { - if (!$this->requestExists($requestor, $dest)) { - return; + if (count($filters) > 0) { + $stmt = $this->conn->prepare( + "SELECT * FROM $table WHERE " . + implode(" and ", array_map(fn($x) => "$x=:$x", array_keys($filters))) + ); + foreach ($filters as $key => $val) { + $stmt->bindValue(":$key", $val); + } + } else { + $stmt = $this->conn->prepare("SELECT * FROM $table"); } + $this->execute($stmt); + return $stmt->fetchAll(); + } + private function delete($table, $filters) + { $stmt = $this->conn->prepare( - "DELETE FROM " . self::TABLE_REQS . " WHERE uid=:uid and request_for=:request_for" + "DELETE FROM $table WHERE " . + implode(" and ", array_map(fn($x) => "$x=:$x", array_keys($filters))) ); - $stmt->bindParam(":uid", $requestor); - $stmt->bindParam(":request_for", $dest); - - $stmt->execute(); + foreach ($filters as $key => $val) { + $stmt->bindValue(":$key", $val); + } + $this->execute($stmt); } - public function removeRequests($dest = self::REQUEST_BECOME_PI) + private function insert($table, $data) { $stmt = $this->conn->prepare( - "DELETE FROM " . self::TABLE_REQS . " WHERE request_for=:request_for" + "INSERT INTO $table " . + "(" . implode(", ", array_keys($data)) . ") " . + "VALUES " . + "(" . implode(", ", array_map(fn($x) => ":$x", array_keys($data))) . ")" ); - $stmt->bindParam(":request_for", $dest); - - $stmt->execute(); + foreach ($data as $key => $val) { + $stmt->bindValue(":$key", $val); + } + $this->execute($stmt); } - public function requestExists($requestor, $dest = self::REQUEST_BECOME_PI) + private function update($table, $filters, $data) { $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_REQS . " WHERE uid=:uid and request_for=:request_for" + "UPDATE $table SET " . + implode(", ", array_map(fn($x) => "$x=:$x", array_keys($filters))) . " " . + "WHERE " . + implode(" and ", array_map(fn($x) => "$x=:$x", array_keys($filters))) ); - $stmt->bindParam(":uid", $requestor); - $stmt->bindParam(":request_for", $dest); - - $stmt->execute(); - - return count($stmt->fetchAll()) > 0; + foreach ($filters as $key => $val) { + $stmt->bindValue(":$key", $val); + } + foreach ($data as $key => $val) { + $stmt->bindValue(":$key", $val); + } + $this->execute($stmt); } - public function getRequests($dest = self::REQUEST_BECOME_PI) + public function addRequest($requestor, $dest = self::REQUEST_BECOME_PI) { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_REQS . " WHERE request_for=:request_for" - ); - $stmt->bindParam(":request_for", $dest); + if ($this->requestExists($requestor, $dest)) { + return; + } + $this->insert(self::TABLE_REQS, ["uid" => $requestor, "request_for" => $dest]); + } - $stmt->execute(); + public function removeRequest($requestor, $dest = self::REQUEST_BECOME_PI) + { + if (!$this->requestExists($requestor, $dest)) { + return; + } + $this->delete(self::TABLE_REQS, ["uid" => $requestor, "request_for" => $dest]); + } - return $stmt->fetchAll(); + public function removeRequests($dest = self::REQUEST_BECOME_PI) + { + $this->delete(self::TABLE_REQS, ["request_for" => $dest]); } - public function getRequestsByUser($user) + public function requestExists($requestor, $dest = self::REQUEST_BECOME_PI) { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_REQS . " WHERE uid=:uid" - ); - $stmt->bindParam(":uid", $user); + $results = $this->search(self::TABLE_REQS, ["request_for" => $dest]); + return count($results) > 0; + } - $stmt->execute(); + public function getRequests($dest = self::REQUEST_BECOME_PI) + { + return $this->search(self::TABLE_REQS, ["request_for" => $dest]); + } - return $stmt->fetchAll(); + public function getRequestsByUser($uid) + { + return $this->search(self::TABLE_REQS, ["uid" => $uid]); } public function deleteRequestsByUser($user) { - $stmt = $this->conn->prepare( - "DELETE FROM " . self::TABLE_REQS . " WHERE uid=:uid" - ); - $stmt->bindParam(":uid", $user); - - $stmt->execute(); + $this->delete(self::TABLE_REQS, ["uid" => $user]); } public function addNotice($title, $date, $content, $operator) { - $stmt = $this->conn->prepare( - "INSERT INTO " . self::TABLE_NOTICES . " (date, title, message) VALUES (:date, :title, :message)" + $this->insert( + self::TABLE_NOTICES, + ["date" => $date, "title" => $title, "message" => $content] ); - $stmt->bindParam(":date", $date); - $stmt->bindParam(":title", $title); - $stmt->bindParam(":message", $content); - - $stmt->execute(); - $operator = $operator->getUID(); - - $this->addLog( - $operator, - $_SERVER['REMOTE_ADDR'], - "added_cluster_notice", - $operator - ); + $this->addLog($operator, $_SERVER['REMOTE_ADDR'], "added_cluster_notice", $operator); } public function editNotice($id, $title, $date, $content) { - $stmt = $this->conn->prepare( - "UPDATE " . self::TABLE_NOTICES . " SET date=:date, title=:title, message=:message WHERE id=:id" + $this->update( + self::TABLE_PAGES, + ["id" => $id], + ["date" => $date, "title" => $title, "message" => $message] ); - $stmt->bindParam(":date", $date); - $stmt->bindParam(":title", $title); - $stmt->bindParam(":message", $content); - $stmt->bindParam(":id", $id); - - $stmt->execute(); } public function deleteNotice($id) { - $stmt = $this->conn->prepare( - "DELETE FROM " . self::TABLE_NOTICES . " WHERE id=:id" - ); - $stmt->bindParam(":id", $id); - - $stmt->execute(); + $this->delete(self::TABLE_NOTICES, ["id" => $id]); } public function getNotice($id) { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_NOTICES . " WHERE id=:id" - ); - $stmt->bindParam(":id", $id); - - $stmt->execute(); - - return $stmt->fetchAll()[0]; + return $this->search(self::TABLE_NOTICES, ["id" => $id]); } public function getNotices() { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_NOTICES . " ORDER BY date DESC" - ); - $stmt->execute(); - - return $stmt->fetchAll(); + return $this->search(self::TABLE_NOTICES, []); } public function getPages() { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_PAGES - ); - $stmt->execute(); - - return $stmt->fetchAll(); + return $this->search(self::TABLE_PAGES, []); } public function getPage($id) { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_PAGES . " WHERE page=:id" - ); - $stmt->bindParam(":id", $id); - - $stmt->execute(); - - return $stmt->fetchAll()[0]; + return $this->search(self::TABLE_PAGES, ["page" => $id]); } public function editPage($id, $content, $operator) { - $stmt = $this->conn->prepare( - "UPDATE " . self::TABLE_PAGES . " SET content=:content WHERE page=:id" - ); - $stmt->bindParam(":id", $id); - $stmt->bindParam(":content", $content); - - $stmt->execute(); - + $this->update(self::TABLE_PAGES, ["page" => $id], ["content" => $content]); $operator = $operator->getUID(); - - $this->addLog( - $operator, - $_SERVER['REMOTE_ADDR'], - "edited_page", - $operator - ); + $this->addLog($operator, $_SERVER['REMOTE_ADDR'], "edited_page", $operator); } - // audit log table methods public function addLog($operator, $operator_ip, $action_type, $recipient) { - $stmt = $this->conn->prepare( - "INSERT INTO " . self::TABLE_AUDIT_LOG . " (operator, operator_ip, action_type, recipient) - VALUE (:operator, :operator_ip, :action_type, :recipient)" + $this->insert( + self::TABLE_AUDIT_LOG, + [ + "operator" => $operator, + "operator_ip" => $operator_ip, + "action_type" => $action_type, + "recipient" => $recipient + ] ); - $stmt->bindParam(":operator", $operator); - $stmt->bindParam(":operator_ip", $operator_ip); - $stmt->bindParam(":action_type", $action_type); - $stmt->bindParam(":recipient", $recipient); - - $stmt->execute(); } public function addAccountDeletionRequest($uid) { - $stmt = $this->conn->prepare( - "INSERT INTO " . self::TABLE_ACCOUNT_DELETION_REQUESTS . " (uid) VALUE (:uid)" - ); - $stmt->bindParam(":uid", $uid); - - $stmt->execute(); + $this->insert(self::TABLE_ACCOUNT_DELETION_REQUESTS, ["uid" => $uid]); } public function accDeletionRequestExists($uid) { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_ACCOUNT_DELETION_REQUESTS . " WHERE uid=:uid" - ); - $stmt->bindParam(":uid", $uid); - - $stmt->execute(); - - return count($stmt->fetchAll()) > 0; + $results = $this->search(self::TABLE_ACCOUNT_DELETION_REQUESTS, ["uid" => $uid]); + return count($results) > 0; } public function deleteAccountDeletionRequest($uid) @@ -275,34 +230,22 @@ public function deleteAccountDeletionRequest($uid) if (!$this->accDeletionRequestExists($uid)) { return; } - $stmt = $this->conn->prepare( - "DELETE FROM " . self::TABLE_ACCOUNT_DELETION_REQUESTS . " WHERE uid=:uid" - ); - $stmt->bindParam(":uid", $uid); - $stmt->execute(); + $this->delete(self::TABLE_ACCOUNT_DELETION_REQUESTS, ["uid" => $uid]); } - public function getSiteVar($name) + public function getSiteVar($name): string { - $stmt = $this->conn->prepare( - "SELECT * FROM " . self::TABLE_SITEVARS . " WHERE name=:name" - ); - $stmt->bindParam(":name", $name); - - $stmt->execute(); - - return $stmt->fetchAll()[0]['value']; + $results = $this->search(self::TABLE_SITEVARS, ["name" => $name]); + if (count($results) == 0) { + throw new UnitySQLRecordNotFound($name); + } + assert(count($results) == 1); + return $results[0]["value"]; } public function updateSiteVar($name, $value) { - $stmt = $this->conn->prepare( - "UPDATE " . self::TABLE_SITEVARS . " SET value=:value WHERE name=:name" - ); - $stmt->bindParam(":name", $name); - $stmt->bindParam(":value", $value); - - $stmt->execute(); + $this->update(self::TABLE_SITEVARS, ["name" => $name], ["value" => $value]); } public function getRole($uid, $group) @@ -310,8 +253,8 @@ public function getRole($uid, $group) $stmt = $this->conn->prepare( "SELECT * FROM " . self::TABLE_GROUP_ROLE_ASSIGNMENTS . " WHERE user=:uid AND `group`=:group" ); - $stmt->bindParam(":uid", $uid); - $stmt->bindParam(":group", $group); + $stmt->bindValue(":uid", $uid); + $stmt->bindValue(":group", $group); $stmt->execute(); @@ -323,7 +266,7 @@ public function hasPerm($role, $perm) $stmt = $this->conn->prepare( "SELECT * FROM " . self::TABLE_GROUP_ROLES . " WHERE slug=:role" ); - $stmt->bindParam(":role", $role); + $stmt->bindValue(":role", $role); $stmt->execute(); @@ -337,7 +280,7 @@ public function getPriority($role) $stmt = $this->conn->prepare( "SELECT * FROM " . self::TABLE_GROUP_ROLES . " WHERE slug=:role" ); - $stmt->bindParam(":role", $role); + $stmt->bindValue(":role", $role); $stmt->execute(); @@ -350,8 +293,8 @@ public function roleAvailableInGroup($uid, $group, $role) $stmt = $this->conn->prepare( "SELECT * FROM " . self::TABLE_GROUP_ROLE_ASSIGNMENTS . " WHERE user=:uid AND `group`=:group" ); - $stmt->bindParam(":uid", $uid); - $stmt->bindParam(":group", $group); + $stmt->bindValue(":uid", $uid); + $stmt->bindValue(":group", $group); $stmt->execute(); $row = $stmt->fetchAll()[0]; @@ -362,7 +305,7 @@ public function roleAvailableInGroup($uid, $group, $role) "SELECT * FROM " . self::TABLE_GROUP_TYPES . " WHERE slug=:slug" ); - $stmt->bindParam(":slug", $group_slug); + $stmt->bindValue(":slug", $group_slug); $stmt->execute(); $row = $stmt->fetchAll()[0]; diff --git a/resources/lib/exceptions/UnitySQLRecordNotFound.php b/resources/lib/exceptions/UnitySQLRecordNotFound.php new file mode 100644 index 00000000..8f767316 --- /dev/null +++ b/resources/lib/exceptions/UnitySQLRecordNotFound.php @@ -0,0 +1,6 @@ +deleteRequestsByUser($USER->getUID()); $org = $USER->getOrgGroup(); - if ($org->inOrg($USER)) { + if ($org->exists() and $org->inOrg($USER)) { $org->removeUser($USER); assert(!$org->inOrg($USER)); } @@ -65,16 +65,18 @@ private function ensureUserDoesNotExist() $all_users_group->write(); assert(!in_array($USER->getUID(), $all_users_group->getAttribute("memberuid"))); } + $REDIS->removeCacheArray("sorted_users", "", $USER->getUID()); } private function ensureOrgGroupDoesNotExist() { - global $USER; + global $USER, $REDIS; $org_group = $USER->getOrgGroup(); if ($org_group->exists()) { $org_group->getLDAPOrgGroup()->delete(); assert(!$org_group->exists()); } + $REDIS->removeCacheArray("sorted_orgs", "", $USER->getOrgGroup()->getOrgID()); } private function ensureUserNotInPIGroup(UnityGroup $pi_group) @@ -88,11 +90,12 @@ private function ensureUserNotInPIGroup(UnityGroup $pi_group) private function ensurePIGroupDoesNotExist() { - global $USER; + global $USER, $REDIS; if ($USER->getPIGroup()->exists()) { $USER->getPIGroup()->getLDAPPIGroup()->delete(); assert(!$USER->getPIGroup()->exists()); } + $REDIS->removeCacheArray("sorted_groups", "", $USER->getPIGroup()->getPIUID()); } public function testCreateUserByJoinGoup() diff --git a/test/phpunit-bootstrap.php b/test/phpunit-bootstrap.php index 2240c7f6..1be5a278 100644 --- a/test/phpunit-bootstrap.php +++ b/test/phpunit-bootstrap.php @@ -19,6 +19,7 @@ require_once __DIR__ . "/../resources/lib/UnityRedis.php"; require_once __DIR__ . "/../resources/lib/UnityGithub.php"; require_once __DIR__ . "/../resources/lib/exceptions/PhpUnitNoDieException.php"; +require_once __DIR__ . "/../resources/lib/exceptions/UnitySQLRecordNotFound.php"; $GLOBALS["PHPUNIT_NO_DIE_PLEASE"] = true; diff --git a/tools/docker-dev/sql/Dockerfile b/tools/docker-dev/sql/Dockerfile index 706d2383..c2f87e06 100644 --- a/tools/docker-dev/sql/Dockerfile +++ b/tools/docker-dev/sql/Dockerfile @@ -28,6 +28,9 @@ COPY phpmyadmin-apache.conf /etc/apache2/sites-available/phpmyadmin.conf RUN a2dissite 000-default RUN a2ensite phpmyadmin RUN echo "ServerName 127.0.0.1" >> /etc/apache2/apache2.conf +RUN echo "general_log_file = /var/log/mysql/mysql.log" >> /etc/mysql/mariadb.conf.d/50-server.cnf +RUN echo "general_log = 1" >> /etc/mysql/mariadb.conf.d/50-server.cnf +RUN mkdir -p /var/log/mysql/ EXPOSE 80 EXPOSE 3306