Page MenuHomePhorge

No OneTemporary

Size
11 KB
Referenced Files
None
Subscribers
None
diff --git a/src/client/actions/send.cpp b/src/client/actions/send.cpp
index 861b87f..2ed9d6b 100644
--- a/src/client/actions/send.cpp
+++ b/src/client/actions/send.cpp
@@ -1,164 +1,177 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <debug.hpp>
#include <types.hpp>
#include "send.hpp"
#include "status-utils.hpp"
namespace Kazv
{
ClientResult updateClient(ClientModel m, SendMessageAction a)
{
auto event = std::move(a.event);
auto roomId = a.roomId;
auto origJson = event.originalJson().get();
if (!origJson.contains("type") || !origJson.contains("content")) {
m.addTrigger(InvalidMessageFormat{});
return { std::move(m), lager::noop };
}
if (m.roomList.rooms[a.roomId].encrypted && !event.encrypted()) {
if (!event.isState()) {
return { std::move(m), [](auto &&) {
return EffectStatus{
/* succ = */ false,
json{
{"errorCode", "MOE_KAZV_MXC_SENDING_UNENCRYPTED_EVENT_TO_ENCRYPTED_ROOM"},
{"error", "Cannot send unencrypted event to encrypted room"},
}
};
}};
}
}
// We do not use event.type() etc. because we want
// encrypted events stay encrypted.
auto type = origJson["type"];
auto content = origJson["content"];
kzo.client.dbg() << "Sending message of type " << type
<< " with content " << content.dump()
<< " to " << a.roomId
<< " as #" << m.nextTxnId << std::endl;
// We combine the hash of json, the timestamp,
// and a numeric count in the client to avoid collision.
auto txnId = a.txnId.has_value() ? a.txnId.value() : getTxnId(event, m);
m.roomList = RoomListModel::update(std::move(m.roomList),
UpdateRoomAction{
roomId,
AddLocalEchoAction{{txnId, event}},
}
);
auto job = m.job<SendMessageJob>()
.make(a.roomId, type, txnId, content)
.withData(json{
{"roomId", a.roomId},
{"txnId", txnId},
});
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, SendMessageResponse r)
{
auto roomId = r.dataStr("roomId");
if (! r.success()) {
auto txnId = r.dataStr("txnId");
kzo.client.dbg() << "Send message failed" << std::endl;
m.roomList.rooms = std::move(m.roomList.rooms).update(roomId, [txnId](auto room) {
auto maybeLocalEcho = room.getLocalEchoByTxnId(txnId);
if (!maybeLocalEcho.has_value()) {
kzo.client.warn() << "We do not have local echo with txnId " << txnId << " . Have we time-travelled?" << std::endl;
return room;
}
auto localEcho = std::move(maybeLocalEcho).value();
localEcho.status = LocalEchoDesc::Failed;
return RoomModel::update(std::move(room), AddLocalEchoAction{localEcho});
});
m.addTrigger(SendMessageFailed{roomId, r.errorCode(), r.errorMessage()});
return { std::move(m), failWithResponse(r) };
}
m.addTrigger(SendMessageSuccessful{roomId, r.eventId()});
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, SendToDeviceMessageAction a)
{
auto origJson = a.event.originalJson().get();
if (!origJson.contains("type") || !origJson.contains("content")) {
return { std::move(m), simpleFail };
}
// We do not use event.type() etc. because we want
// encrypted events stay encrypted.
auto type = origJson["type"];
auto content = origJson["content"];
+ if (type == "m.room_key" && !a.event.encrypted()) {
+ kzo.client.err() << "Trying to send room key event unencrypted! Rejecting." << std::endl;
+ return { std::move(m), [](auto &&) {
+ return EffectStatus{
+ /* succ = */ false,
+ json{
+ {"errorCode", "MOE_KAZV_MXC_SENDING_ROOM_KEY_EVENT_UNENCRYPTED"},
+ {"error", "Cannot send room key event unencrypted"},
+ }
+ };
+ }};
+ }
+
auto txnId = a.txnId.has_value() ? a.txnId.value() : getTxnId(a.event, m);
kzo.client.info() << "sending to-device message with txnId " << txnId;
auto messages =
immer::map<std::string, immer::map<std::string, JsonWrap>>{};
for (auto [userId, devices] : a.devicesToSend) {
auto deviceIdToContentMap = immer::map<std::string, JsonWrap>{};
for (auto deviceId : devices) {
deviceIdToContentMap = std::move(deviceIdToContentMap).set(deviceId, content);
}
messages = std::move(messages).set(userId, deviceIdToContentMap);
}
auto job = m.job<SendToDeviceJob>()
.make(type, txnId, messages)
.withData(json{{"devicesToSend", a.devicesToSend},
{"txnId", txnId}});
m.addJob(std::move(job));
return { std::move(m), lager::noop };
}
ClientResult processResponse(ClientModel m, SendToDeviceResponse r)
{
auto devicesToSend = r.dataJson("devicesToSend");
auto txnId = r.dataStr("txnId");
if (! r.success()) {
m.addTrigger(SendToDeviceMessageFailed{devicesToSend, txnId, r.errorCode(), r.errorMessage()});
return { std::move(m), failWithResponse(r) };
}
m.addTrigger(SendToDeviceMessageSuccessful{devicesToSend, txnId});
return { std::move(m), lager::noop };
}
ClientResult updateClient(ClientModel m, SaveLocalEchoAction a)
{
auto txnId = a.txnId.has_value() ? a.txnId.value() : getTxnId(a.event, m);
m.roomList = RoomListModel::update(m.roomList, UpdateRoomAction{
a.roomId,
AddLocalEchoAction{{txnId, a.event}},
});
return { std::move(m), [txnId](auto) {
return EffectStatus(true, json::object({{"txnId", txnId}}));
}};
}
}
diff --git a/src/tests/client/send-test.cpp b/src/tests/client/send-test.cpp
index 310ac7f..de39344 100644
--- a/src/tests/client/send-test.cpp
+++ b/src/tests/client/send-test.cpp
@@ -1,107 +1,133 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2021-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <lager/event_loop/boost_asio.hpp>
#include <catch2/catch_all.hpp>
#include <boost/asio.hpp>
#include <asio-promise-handler.hpp>
#include <cursorutil.hpp>
#include <sdk-model.hpp>
#include <client/client.hpp>
#include "client-test-util.hpp"
TEST_CASE("Send a message", "[client][send]")
{
ClientModel loggedInModel = createTestClientModel();
auto [resModel, dontCareEffect] = ClientModel::update(
loggedInModel, SendMessageAction{"!foo:tusooa.xyz", json{
{"type", "m.room.message"},
{"content", {{"foo", "bar"}}},
}});
assert1Job(resModel);
for1stJob(resModel, [] (const auto &job) {
REQUIRE(job.jobId() == "SendMessage");
REQUIRE(job.dataStr("roomId") == "!foo:tusooa.xyz");
REQUIRE(job.dataStr("txnId") != "");
});
}
TEST_CASE("Send a message with specified txnId", "[client][send]")
{
ClientModel loggedInModel = createTestClientModel();
auto [resModel, dontCareEffect] = ClientModel::update(
loggedInModel, SendMessageAction{"!foo:tusooa.xyz", json{
{"type", "m.room.message"},
{"content", {{"foo", "bar"}}},
}, "some-txn-id"});
assert1Job(resModel);
for1stJob(resModel, [] (const auto &job) {
REQUIRE(job.jobId() == "SendMessage");
REQUIRE(job.dataStr("roomId") == "!foo:tusooa.xyz");
REQUIRE(job.dataStr("txnId") == "some-txn-id");
});
}
TEST_CASE("Refuse to send unencrypted event to encrypted room", "[client][send]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> ph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = true;
room.roomId = "!exampleroomid:example.com";
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto store = createTestClientStoreFrom(m, ph);
store.dispatch(SendMessageAction{
"!exampleroomid:example.com",
Event{json{{"content", {{"foo", "bar"}}}, {"type", "m.room.message"}}},
})
.then([](auto status) {
REQUIRE(!status.success());
REQUIRE(status.dataStr("errorCode") == "MOE_KAZV_MXC_SENDING_UNENCRYPTED_EVENT_TO_ENCRYPTED_ROOM");
});
io.run();
}
TEST_CASE("Ok to send unencrypted state event to encrypted room", "[client][send]")
{
boost::asio::io_context io;
SingleTypePromiseInterface<EffectStatus> ph{AsioPromiseHandler{io.get_executor()}};
ClientModel m;
m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
RoomModel room;
room.encrypted = true;
room.roomId = "!exampleroomid:example.com";
m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
auto store = createTestClientStoreFrom(m, ph);
store.dispatch(SendMessageAction{
"!exampleroomid:example.com",
Event{json{
{"content", {{"foo", "bar"}}},
{"type", "m.room.xxx"},
{"state_key", ""},
}},
})
.then([](auto status) {
REQUIRE(status.success());
});
io.run();
}
+
+TEST_CASE("Refuse to send unencrypted m.room_key event", "[client][send]")
+{
+ boost::asio::io_context io;
+ SingleTypePromiseInterface<EffectStatus> ph{AsioPromiseHandler{io.get_executor()}};
+
+ ClientModel m;
+ m.crypto = Crypto(RandomTag{}, genRandomData(Crypto::constructRandomSize()));
+ RoomModel room;
+ room.encrypted = true;
+ room.roomId = "!exampleroomid:example.com";
+ m.roomList.rooms = m.roomList.rooms.set("!exampleroomid:example.com", room);
+
+ auto store = createTestClientStoreFrom(m, ph);
+
+ store.dispatch(SendToDeviceMessageAction{
+ Event{json{{"content", {{"foo", "bar"}}}, {"type", "m.room_key"}}},
+ {},
+ })
+ .then([](auto status) {
+ REQUIRE(!status.success());
+ REQUIRE(status.dataStr("errorCode") == "MOE_KAZV_MXC_SENDING_ROOM_KEY_EVENT_UNENCRYPTED");
+ });
+
+ io.run();
+}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 9:13 AM (2 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55059
Default Alt Text
(11 KB)

Event Timeline