[update] remove extension - unzipping new code over the top of old code is fundamentally unsafe

This commit is contained in:
Shish 2024-02-10 23:15:08 +00:00
parent cc9de6b4b2
commit 79087c51a5
5 changed files with 88 additions and 285 deletions

View file

@ -294,162 +294,135 @@ class Pools extends Extension
if ($event->page_matches("pool/edit")) { if ($event->page_matches("pool/edit")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) { $result = $database->execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $pool_id]);
$result = $database->execute("SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $pool_id]); $images = [];
$images = []; while ($row = $result->fetch()) {
while ($row = $result->fetch()) { $images[] = Image::by_id((int) $row["image_id"]);
$images[] = Image::by_id((int) $row["image_id"]);
}
$this->theme->edit_pool($page, $pool, $images);
} else {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} }
$this->theme->edit_pool($page, $pool, $images);
} }
if ($event->page_matches("pool/order")) { if ($event->page_matches("pool/order")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($event->get_POST("order_view")) { if ($event->get_POST("order_view")) {
if ($this->have_permission($user, $pool)) { $result = $database->execute(
$result = $database->execute( "SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC",
"SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order ASC", ["pid" => $pool_id]
["pid" => $pool_id] );
); $images = [];
$images = [];
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$image = $database->get_row( $image = $database->get_row(
" "
SELECT * FROM images AS i SELECT * FROM images AS i
INNER JOIN pool_images AS p ON i.id = p.image_id INNER JOIN pool_images AS p ON i.id = p.image_id
WHERE pool_id=:pid AND i.id=:iid", WHERE pool_id=:pid AND i.id=:iid",
["pid" => $pool_id, "iid" => (int) $row['image_id']] ["pid" => $pool_id, "iid" => (int) $row['image_id']]
); );
$images[] = ($image ? new Image($image) : null); $images[] = ($image ? new Image($image) : null);
}
$this->theme->edit_order($page, $pool, $images);
} else {
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} }
$this->theme->edit_order($page, $pool, $images);
} else { } else {
if ($this->have_permission($user, $pool)) { foreach ($event->POST as $key => $value) {
foreach ($event->POST as $key => $value) { if (str_starts_with($key, "order_")) {
if (str_starts_with($key, "order_")) { $imageID = (int) substr($key, 6);
$imageID = (int) substr($key, 6); $database->execute(
$database->execute( "
"
UPDATE pool_images UPDATE pool_images
SET image_order = :ord SET image_order = :ord
WHERE pool_id = :pid AND image_id = :iid", WHERE pool_id = :pid AND image_id = :iid",
["ord" => $value, "pid" => int_escape($event->req_POST('pool_id')), "iid" => $imageID] ["ord" => $value, "pid" => int_escape($event->req_POST('pool_id')), "iid" => $imageID]
); );
}
} }
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
} }
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} }
} }
if ($event->page_matches("pool/reverse")) { if ($event->page_matches("pool/reverse")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) { $database->with_savepoint(function () use ($pool_id) {
$database->with_savepoint(function () use ($pool_id) { global $database;
global $database; $result = $database->execute(
$result = $database->execute( "SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order DESC",
"SELECT image_id FROM pool_images WHERE pool_id=:pid ORDER BY image_order DESC", ["pid" => $pool_id]
["pid" => $pool_id] );
); $image_order = 1;
$image_order = 1; while ($row = $result->fetch()) {
while ($row = $result->fetch()) { $database->execute(
$database->execute( "
"
UPDATE pool_images UPDATE pool_images
SET image_order=:ord SET image_order=:ord
WHERE pool_id = :pid AND image_id = :iid", WHERE pool_id = :pid AND image_id = :iid",
["ord" => $image_order, "pid" => $pool_id, "iid" => (int) $row['image_id']] ["ord" => $image_order, "pid" => $pool_id, "iid" => (int) $row['image_id']]
); );
$image_order = $image_order + 1; $image_order = $image_order + 1;
} }
}); });
$page->set_mode(PageMode::REDIRECT); $page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id)); $page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
} }
if ($event->page_matches("pool/import")) { if ($event->page_matches("pool/import")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) { $images = Search::find_images(
$images = Search::find_images( limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000),
limit: $config->get_int(PoolsConfig::MAX_IMPORT_RESULTS, 1000), tags: Tag::explode($event->req_POST("pool_tag"))
tags: Tag::explode($event->req_POST("pool_tag")) );
); $this->theme->pool_result($page, $images, $pool);
$this->theme->pool_result($page, $images, $pool);
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
} }
if ($event->page_matches("pool/add_posts")) { if ($event->page_matches("pool/add_posts")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) { $image_ids = array_map('intval', $event->req_POST_array('check'));
$image_ids = array_map('intval', $event->req_POST_array('check')); send_event(new PoolAddPostsEvent($pool_id, $image_ids));
send_event(new PoolAddPostsEvent($pool_id, $image_ids)); $page->set_mode(PageMode::REDIRECT);
$page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("pool/view/" . $pool_id));
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
} }
if ($event->page_matches("pool/remove_posts")) { if ($event->page_matches("pool/remove_posts")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) { $images = "";
$images = ""; foreach ($event->req_POST_array('check') as $imageID) {
foreach ($event->req_POST_array('check') as $imageID) { $database->execute(
$database->execute( "DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid",
"DELETE FROM pool_images WHERE pool_id = :pid AND image_id = :iid", ["pid" => $pool_id, "iid" => $imageID]
["pid" => $pool_id, "iid" => $imageID]
);
$images .= " " . $imageID;
}
$count = (int) $database->get_one(
"SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid",
["pid" => $pool_id]
); );
$this->add_history($pool_id, 0, $images, $count); $images .= " " . $imageID;
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
} }
$count = (int) $database->get_one(
"SELECT COUNT(*) FROM pool_images WHERE pool_id=:pid",
["pid" => $pool_id]
);
$this->add_history($pool_id, 0, $images, $count);
$page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/view/" . $pool_id));
} }
if ($event->page_matches("pool/edit_description")) { if ($event->page_matches("pool/edit_description")) {
$pool_id = int_escape($event->req_POST("pool_id")); $pool_id = int_escape($event->req_POST("pool_id"));
$pool = $this->get_single_pool($pool_id); $pool = $this->get_single_pool($pool_id);
$this->assert_permission($user, $pool);
if ($this->have_permission($user, $pool)) { $database->execute(
$database->execute( "UPDATE pools SET description=:dsc,lastupdated=CURRENT_TIMESTAMP WHERE id=:pid",
"UPDATE pools SET description=:dsc,lastupdated=CURRENT_TIMESTAMP WHERE id=:pid", ["dsc" => $event->req_POST('description'), "pid" => $pool_id]
["dsc" => $event->req_POST('description'), "pid" => $pool_id] );
); $page->set_mode(PageMode::REDIRECT);
$page->set_mode(PageMode::REDIRECT); $page->set_redirect(make_link("pool/view/" . $pool_id));
$page->set_redirect(make_link("pool/view/" . $pool_id));
} else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page");
}
} }
if ($event->page_matches("pool/nuke")) { if ($event->page_matches("pool/nuke")) {
// Completely remove the given pool. // Completely remove the given pool.
@ -462,7 +435,7 @@ class Pools extends Extension
$page->set_mode(PageMode::REDIRECT); $page->set_mode(PageMode::REDIRECT);
$page->set_redirect(make_link("pool/list")); $page->set_redirect(make_link("pool/list"));
} else { } else {
$this->theme->display_error(403, "Permission Denied", "You do not have permission to access this page"); throw new PermissionDeniedException("You do not have permission to access this page");
} }
} }
} }
@ -646,6 +619,13 @@ class Pools extends Extension
); );
} }
private function assert_permission(User $user, Pool $pool): void
{
if (!$this->have_permission($user, $pool)) {
throw new PermissionDeniedException("You do not have permission to access this pool");
}
}
private function list_pools(Page $page, int $pageNumber, ?string $search): void private function list_pools(Page $page, int $pageNumber, ?string $search): void
{ {
global $config, $database; global $config, $database;
@ -758,9 +738,7 @@ class Pools extends Extension
global $database, $user; global $database, $user;
$pool = $this->get_single_pool($event->pool_id); $pool = $this->get_single_pool($event->pool_id);
if (!$this->have_permission($user, $pool)) { $this->assert_permission($user, $pool);
return;
}
$images = []; $images = [];
foreach ($event->posts as $post_id) { foreach ($event->posts as $post_id) {

View file

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class UpdateInfo extends ExtensionInfo
{
public const KEY = "update";
public string $key = self::KEY;
public string $name = "Update";
public string $url = "http://www.codeanimu.net";
public array $authors = ["DakuTree" => "dakutree@codeanimu.net"];
public string $license = self::LICENSE_GPLV2;
public string $description = "Shimmie updater!";
public array $dependencies = [AdminPageInfo::KEY];
public ExtensionCategory $category = ExtensionCategory::ADMIN;
}

View file

@ -1,120 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class Update extends Extension
{
/** @var UpdateTheme */
protected Themelet $theme;
public function onInitExt(InitExtEvent $event): void
{
global $config;
$config->set_default_string("update_guserrepo", "shish/shimmie2");
$config->set_default_string("commit_hash", "unknown");
$config->set_default_string("update_time", "01/01/1970");
}
public function onSetupBuilding(SetupBuildingEvent $event): void
{
$sb = $event->panel->create_new_block("Update");
$sb->add_text_option("update_guserrepo", "User/Repo: ");
}
public function onAdminBuilding(AdminBuildingEvent $event): void
{
global $config;
if ($config->get_string(UploadConfig::TRANSLOAD_ENGINE) !== "none") {
$this->theme->display_admin_block();
}
}
public function onPageRequest(PageRequestEvent $event): void
{
global $user, $page;
$sha = $event->get_GET('sha');
// FIXME: use POST
if ($event->page_matches("update/download", permission: Permissions::EDIT_FILES)) {
assert(!is_null($sha));
$ok = $this->download_shimmie($sha);
$page->set_mode(PageMode::REDIRECT);
if ($ok) {
$page->set_redirect(make_link("update/update", "sha=".$sha));
} else {
$page->set_redirect(make_link("admin"));
} //TODO: Show error?
}
if ($event->page_matches("update/update", permission: Permissions::EDIT_FILES)) {
assert(!is_null($sha));
$ok = $this->update_shimmie($sha);
$page->set_mode(PageMode::REDIRECT);
if ($ok) {
$page->set_redirect(make_link("admin"));
} //TODO: Show success?
else {
$page->set_redirect(make_link("admin"));
} //TODO: Show error?
}
}
private function download_shimmie(string $commitSHA): bool
{
global $config;
$g_userrepo = $config->get_string('update_guserrepo');
$url = "https://codeload.github.com/".$g_userrepo."/zip/".$commitSHA;
$filename = "./data/update_{$commitSHA}.zip";
log_info("update", "Attempting to download Shimmie commit: ".$commitSHA);
$headers = fetch_url($url, $filename);
if (($headers['Content-Type'] !== MimeType::ZIP) || ((int) $headers['Content-Length'] !== filesize($filename))) {
unlink("./data/update_{$commitSHA}.zip");
log_warning("update", "Download failed: not zip / not same size as remote file.");
return false;
}
return true;
}
private function update_shimmie(string $commitSHA): bool
{
global $config;
log_info("update", "Download succeeded. Attempting to update Shimmie.");
$ok = false;
/** TODO: Backup all folders (except /data, /images, /thumbs) before attempting this?
Either that or point to https://github.com/shish/shimmie2/blob/master/README.txt -> Upgrade from 2.3.X **/
$zip = new \ZipArchive();
if ($zip->open("./data/update_$commitSHA.zip") === true) {
for ($i = 1; $i < $zip->numFiles; $i++) {
$filename = false_throws($zip->getNameIndex($i));
if (substr($filename, -1) !== "/") {
copy("zip://".dirname(dirname(__DIR__)).'/'."./data/update_$commitSHA.zip"."#".$filename, substr($filename, 50));
}
}
$ok = true; //TODO: Do proper checking to see if everything copied properly
} else {
log_warning("update", "Update failed to open ZIP.");
}
$zip->close();
unlink("./data/update_$commitSHA.zip");
if ($ok) {
$config->set_string("commit_hash", $commitSHA);
$config->set_string("update_time", date('d-m-Y'));
log_info("update", "Update succeeded?");
}
return $ok;
}
}

View file

@ -1,16 +0,0 @@
/*jshint bitwise:true, curly:true, forin:false, noarg:true, noempty:true, nonew:true, undef:true, strict:false, browser:true, jquery:true */
document.addEventListener('DOMContentLoaded', () => {
if($('#updatecheck').length !== 0){
$.getJSON('https://api.github.com/repos/shish/shimmie2/commits', function(data){
var c = data[0];
$('#updatecheck').html('<a href="'+ c.html_url+'">'+ c.sha+'</a>' + " ("+ c.commit.message+")");
var params = $.param({sha: c.sha, date: c.commit.committer.date});
$('#updatelink').attr('href', function(i, val){ return val + "?" + params; });
$('#updatelink').text("Update");
}).fail(function(){
$('#updatecheck').text("Loading failed. (Github down?)");
});
}
});

View file

@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace Shimmie2;
class UpdateTheme extends Themelet
{
public function display_admin_block(): void
{
global $page, $config;
$html = "".
"<b>Current Commit</b>: ".$config->get_string('commit_hash')." | (".$config->get_string('update_time').")".
"<br><b>Latest Commit</b>: <span id='updatecheck'>Loading...</span>".
"<br><a href='" . make_link('update/download') . "' id='updatelink'></a>";
//TODO: Show warning before use.
$page->add_block(new Block("Software Update", $html, "main", 75));
}
}