Merge pull request #2063 from kitkatt7/web-delete-needs-post

web: only delete channels, notes, keys and certs on POST
This commit is contained in:
Alexey Sokolov authored and GitHub committed 2026-10-28 08:47:15 +00:00
commit ab7db564fc
9 files changed
+125 -11

No files matched your search

+5 -2
View File
@@ -79,7 +79,8 @@ class CCertMod : public CModule {
} else if (sPageName == "update") {
CFile fPemFile(PemFile());
if (fPemFile.Open(O_WRONLY | O_TRUNC | O_CREAT)) {
if (WebSock.IsPost() &&
fPemFile.Open(O_WRONLY | O_TRUNC | O_CREAT)) {
fPemFile.Write(WebSock.GetParam("cert", true, ""));
fPemFile.Close();
}
@@ -87,7 +88,9 @@ class CCertMod : public CModule {
WebSock.Redirect(GetWebPath());
return true;
} else if (sPageName == "delete") {
CFile::Delete(PemFile());
if (WebSock.IsPost()) {
CFile::Delete(PemFile());
}
WebSock.Redirect(GetWebPath());
return true;
}
+3 -1
View File
@@ -375,7 +375,9 @@ class CSSLClientCertMod : public CModule {
WebSock.Redirect(GetWebPath());
return true;
} else if (sPageName == "delete") {
DelKey(*pUser, WebSock.GetParam("key", false));
if (WebSock.IsPost()) {
DelKey(*pUser, WebSock.GetParam("key"));
}
WebSock.Redirect(GetWebPath());
return true;
}
+5 -2
View File
@@ -2,8 +2,11 @@
<? INC Header.tmpl ?>
<? IF Cert ?>
<? SETBLOCK DELLINK ?><a href="<? VAR URIPrefix TOP ?><? VAR ModPath ?>delete"><? FORMAT "here" "TRANSLATORS: this text is inserted into `click here` in the other string"?></a><? ENDSETBLOCK ?>
<p><? FORMAT "You already have a certificate set, use the form below to overwrite the current certificate. Alternatively click {1} to delete your certificate." "DELLINK ESC=" "TRANSLATORS: {1} is `here`, translateable in the other string" ?></p>
<form method="post" action="<? VAR URIPrefix TOP ?><? VAR ModPath ?>delete">
<? INC _csrf_check.tmpl ?>
<? SETBLOCK DELLINK ?><input type="submit" value="<? FORMAT "here" "TRANSLATORS: this text is inserted into `click here` in the other string"?>" /><? ENDSETBLOCK ?>
<p><? FORMAT "You already have a certificate set, use the form below to overwrite the current certificate. Alternatively click {1} to delete your certificate." "DELLINK ESC=" "TRANSLATORS: {1} is `here`, translateable in the other string" ?></p>
</form>
<? ELSE ?>
<p><? FORMAT "You do not have a certificate yet." ?></p>
<? ENDIF ?>
+5 -1
View File
@@ -33,7 +33,11 @@
<tbody>
<? LOOP KeyLoop ?>
<tr class="<? IF __EVEN__ ?>evenrow<? ELSE ?>oddrow<? ENDIF ?>">
<td>[<a href="<? VAR ModPath ?>delete?key=<? VAR Key ESC=URL,HTML ?>"><? FORMAT "del" ?></a>]</td>
<td><form method="post" action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>delete">
<? INC _csrf_check.tmpl ?>
<input type="hidden" name="key" value="<? VAR Key ?>" />
<input type="submit" value="<? FORMAT "del" ?>" />
</form></td>
<td><? VAR Key ?></td>
</tr>
<? ENDLOOP ?>
+5 -1
View File
@@ -38,7 +38,11 @@
<tbody>
<? LOOP NotesLoop ?>
<tr class="<? IF __EVEN__ ?>evenrow<? ELSE ?>oddrow<? ENDIF ?>">
<td><a href="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>delnote?key=<? VAR Key ESC=URL,HTML ?>"><img src="<? VAR URIPrefix TOP ?><? VAR ModFilesPath TOP ?>trash.gif" alt="<? FORMAT "[del]" ?>" /></a></td>
<td><form method="post" action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>delnote">
<? INC _csrf_check.tmpl ?>
<input type="hidden" name="key" value="<? VAR Key ?>" />
<input type="image" src="<? VAR URIPrefix TOP ?><? VAR ModFilesPath TOP ?>trash.gif" alt="<? FORMAT "[del]" ?>" />
</form></td>
<td><? VAR Key ?></td>
<td><? VAR Note ?></td>
</tr>
+27
View File
@@ -0,0 +1,27 @@
<? I18N znc-webadmin ?>
<? INC Header.tmpl ?>
<div class="section">
<h3><? FORMAT "Confirm Channel Deletion" ?></h3>
<div class="sectionbg">
<div class="sectionbody">
<div class="subsection">
<? FORMAT "Are you sure you want to delete channel “{3}” of network “{2}” of user “{1}”?" Username Network Channel ?>
<form action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>delchan" method="post">
<? INC _csrf_check.tmpl ?>
<input type="hidden" name="user" value="<? VAR Username ?>" />
<input type="hidden" name="network" value="<? VAR Network ?>" />
<input type="hidden" name="name" value="<? VAR Channel ?>" />
<div class="yesnobutton"><input type="submit" value="<? FORMAT "Yes" ?>"/></div>
</form>
<form action="<? VAR URIPrefix TOP ?><? VAR ModPath TOP ?>editnetwork" method="get">
<input type="hidden" name="user" value="<? VAR Username ?>" />
<input type="hidden" name="network" value="<? VAR Network ?>" />
<div class="yesnobutton"><input type="submit" value="<? FORMAT "No" ?>"/></div>
</form>
</div>
</div>
</div>
</div>
<? INC Footer.tmpl ?>
+3 -1
View File
@@ -203,7 +203,9 @@ class CNotesMod : public CModule {
return true;
} else if (sPageName == "delnote") {
DelNote(WebSock.GetParam("key", false));
if (WebSock.IsPost()) {
DelNote(WebSock.GetParam("key"));
}
WebSock.Redirect(GetWebPath());
return true;
} else if (sPageName == "addnote") {
+16 -3
View File
@@ -568,7 +568,7 @@ class CWebAdminMod : public CModule {
}
if (pNetwork) {
return DelChan(WebSock, pNetwork);
return DelChan(WebSock, pNetwork, Tmpl);
}
WebSock.PrintErrorPage(t_s("No such user or network"));
@@ -1306,8 +1306,11 @@ class CWebAdminMod : public CModule {
return false;
}
bool DelChan(CWebSock& WebSock, CIRCNetwork* pNetwork) {
CString sChan = WebSock.GetParam("name", false);
bool DelChan(CWebSock& WebSock, CIRCNetwork* pNetwork, CTemplate& Tmpl) {
CString sChan = WebSock.GetParam("name");
if (sChan.empty() && !WebSock.IsPost()) {
sChan = WebSock.GetParam("name", false);
}
if (sChan.empty()) {
WebSock.PrintErrorPage(
@@ -1315,6 +1318,16 @@ class CWebAdminMod : public CModule {
return true;
}
if (!WebSock.IsPost()) {
// Show the "Are you sure?" page:
Tmpl.SetFile("del_chan.tmpl");
Tmpl["Username"] = pNetwork->GetUser()->GetUsername();
Tmpl["Network"] = pNetwork->GetName();
Tmpl["Channel"] = sChan;
return true;
}
pNetwork->DelChan(sChan);
pNetwork->PutIRC("PART " + sChan);
+56
View File
@@ -443,6 +443,62 @@ TEST_F(ZNCTest, ModuleCSRFOverride) {
EXPECT_THAT(reply, HasSubstr("ipsum"));
}
TEST_F(ZNCTest, WebDeleteNeedsPost) {
int port = PickPortNumber();
auto znc = Run();
auto ircd = ConnectIRCd();
auto client = LoginClient();
client.Write(QStringLiteral("znc addport %1 all all").arg(port).toUtf8());
client.ReadUntil(":Port added");
client.Write("znc loadmod notes");
client.ReadUntil("Loaded module");
client.Write("PRIVMSG *notes :Add secretkey lorem");
client.ReadUntil("Added note");
auto url = [&](const QString& path) {
QNetworkRequest request(
QUrl(QStringLiteral("http://127.0.0.1:%1%2").arg(port).arg(path)));
request.setRawHeader("Authorization",
"Basic " + QByteArray("user:hunter2").toBase64());
return request;
};
HttpPost(url("/mods/global/webadmin/addchan"), {
{"user", "user"},
{"network", "test"},
{"submitted", "1"},
{"name", "#znc"},
{"enabled", "1"},
});
const QString sEditNetwork =
"/mods/global/webadmin/editnetwork?user=user&network=test";
const std::string sChanRow = "name=\"channel\" value=\"#znc\"";
EXPECT_THAT(HttpGet(url(sEditNetwork))->readAll().toStdString(),
HasSubstr(sChanRow));
// A plain GET, e.g. from an <img> on another page, must not delete
EXPECT_THAT(HttpGet(url("/mods/global/webadmin/"
"delchan?user=user&network=test&name=%23znc"))
->readAll()
.toStdString(),
HasSubstr("Confirm Channel Deletion"));
EXPECT_THAT(HttpGet(url(sEditNetwork))->readAll().toStdString(),
HasSubstr(sChanRow));
HttpGet(url("/mods/user/notes/delnote?key=secretkey"));
EXPECT_THAT(HttpGet(url("/mods/user/notes/"))->readAll().toStdString(),
HasSubstr("secretkey"));
HttpPost(url("/mods/global/webadmin/delchan"), {
{"user", "user"},
{"network", "test"},
{"name", "#znc"},
});
EXPECT_THAT(HttpGet(url(sEditNetwork))->readAll().toStdString(),
Not(HasSubstr(sChanRow)));
HttpPost(url("/mods/user/notes/delnote"), {{"key", "secretkey"}});
EXPECT_THAT(HttpGet(url("/mods/user/notes/"))->readAll().toStdString(),
Not(HasSubstr("secretkey")));
}
class SaslModuleTest : public ZNCTest,
public testing::WithParamInterface<
std::pair<int, std::vector<std::string>>> {