Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85823563
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
16 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/client/power-levels-desc.cpp b/src/client/power-levels-desc.cpp
index 19e8182..b117c96 100644
--- a/src/client/power-levels-desc.cpp
+++ b/src/client/power-levels-desc.cpp
@@ -1,147 +1,179 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <event.hpp>
#include "validator.hpp"
#include "power-levels-desc.hpp"
namespace Kazv
{
static std::pair<bool, json> numericValidator(const json &j)
{
if (j.is_number()) {
return {true, json(j.template get<long long>())};
} else if (j.is_string()) {
auto str = j.template get<std::string>();
try {
std::size_t pos = 0;
auto num = std::stoll(str, &pos);
if (pos != str.size()) { // not entirely a number
return {false, json()};
}
return {true, json(num)};
} catch (const std::logic_error &) {
return {false, json()};
}
} else {
return {false, json()};
}
}
static std::pair<bool, json> eventsValidator(const json &j)
{
auto [ret, val] = numericValidator(j[1]);
return {ret, json::array({j[0], std::move(val)})};
}
static std::pair<bool, json> notificationsValidator(const json &j)
{
auto ret = json::object();
auto res = cast(ret, j, "room", numericValidator);
return {res, ret};
}
static std::pair<bool, json> usersValidator(const json &j)
{
auto [ret, val] = numericValidator(j[1]);
return {ret, json::array({j[0], std::move(val)})};
}
// https://spec.matrix.org/v1.8/client-server-api/#mroompower_levels
static const auto numericFields = immer::map<std::string, long long>{
{"ban", 50},
{"events_default", 0},
{"invite", 0},
{"kick", 50},
{"redact", 50},
{"state_default", 50},
{"users_default", 0},
};
struct PowerLevelsDesc::Private
{
Private(const Event &e)
: original(e)
, normalized(normalize(e))
{}
Event normalize(const Event &e)
{
auto content = e.content().get();
auto ret = json{
{"type", "m.power_levels"},
{"state_key", ""},
{"content", json::object()},
};
// cast numeric fields
for (const auto &[key, defaultValue] : numericFields) {
ret["content"][key] = defaultValue;
cast(ret["content"], content, key, numericValidator);
}
// cast events map
ret["content"]["events"] = json::object();
castObject(ret["content"], content, "events", eventsValidator, CastObjectStrategy::IgnoreInvalid);
// cast notifications map
cast(ret["content"], content, "notifications", notificationsValidator);
// cast users map
ret["content"]["users"] = json::object();
castObject(ret["content"], content, "users", usersValidator, CastObjectStrategy::IgnoreInvalid);
return Event(ret);
}
Event original;
Event normalized;
};
PowerLevelsDesc::PowerLevelsDesc(const Event &e)
: m_d(new Private(e))
{}
PowerLevelsDesc::~PowerLevelsDesc() = default;
KAZV_DEFINE_COPYABLE_UNIQUE_PTR(PowerLevelsDesc, m_d);
PowerLevels PowerLevelsDesc::powerLevelsOfUser(std::string userId) const
{
const auto content = m_d->normalized.content().get();
if (content["users"].contains(userId)) {
return content["users"][userId].template get<PowerLevels>();
} else {
return content["users_default"].template get<PowerLevels>();
}
}
bool PowerLevelsDesc::canSendMessage(std::string userId, std::string eventType) const
{
auto userPowerLevels = powerLevelsOfUser(userId);
const auto content = m_d->normalized.content().get();
auto powerLevelsNeeded = content["events"].contains(eventType)
? content["events"][eventType].template get<PowerLevels>()
: content["events_default"].template get<PowerLevels>();
return userPowerLevels >= powerLevelsNeeded;
}
bool PowerLevelsDesc::canSendState(std::string userId, std::string eventType) const
{
auto userPowerLevels = powerLevelsOfUser(userId);
const auto content = m_d->normalized.content().get();
auto powerLevelsNeeded = content["events"].contains(eventType)
? content["events"][eventType].template get<PowerLevels>()
: content["state_default"].template get<PowerLevels>();
return userPowerLevels >= powerLevelsNeeded;
}
+ bool PowerLevelsDesc::canInvite(std::string userId) const
+ {
+ auto userPowerLevels = powerLevelsOfUser(userId);
+ const auto content = m_d->normalized.content().get();
+ auto powerLevelsNeeded = content["invite"].template get<PowerLevels>();
+ return userPowerLevels >= powerLevelsNeeded;
+ }
+
+ bool PowerLevelsDesc::canKick(std::string userId) const
+ {
+ auto userPowerLevels = powerLevelsOfUser(userId);
+ const auto content = m_d->normalized.content().get();
+ auto powerLevelsNeeded = content["kick"].template get<PowerLevels>();
+ return userPowerLevels >= powerLevelsNeeded;
+ }
+
+ bool PowerLevelsDesc::canBan(std::string userId) const
+ {
+ auto userPowerLevels = powerLevelsOfUser(userId);
+ const auto content = m_d->normalized.content().get();
+ auto powerLevelsNeeded = content["ban"].template get<PowerLevels>();
+ return userPowerLevels >= powerLevelsNeeded;
+ }
+
+ bool PowerLevelsDesc::canRedact(std::string userId) const
+ {
+ auto userPowerLevels = powerLevelsOfUser(userId);
+ const auto content = m_d->normalized.content().get();
+ auto powerLevelsNeeded = content["redact"].template get<PowerLevels>();
+ return userPowerLevels >= powerLevelsNeeded;
+ }
+
Event PowerLevelsDesc::normalizedEvent() const
{
return m_d ? m_d->normalized : Event();
}
}
diff --git a/src/client/power-levels-desc.hpp b/src/client/power-levels-desc.hpp
index 0003426..11cc589 100644
--- a/src/client/power-levels-desc.hpp
+++ b/src/client/power-levels-desc.hpp
@@ -1,72 +1,104 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <libkazv-config.hpp>
#include <memory>
#include <cstdint>
#include <copy-helper.hpp>
namespace Kazv
{
class Event;
using PowerLevels = std::int_fast64_t;
/**
* Represent a m.power_levels event.
*/
class PowerLevelsDesc
{
public:
/**
* Construct a PowerLevelsDesc from an event.
*/
PowerLevelsDesc(const Event &e);
~PowerLevelsDesc();
KAZV_DECLARE_COPYABLE(PowerLevelsDesc);
/**
* Get the power levels of a user in the room.
*
* @param userId The id of the user.
* @return The power levels of
*/
PowerLevels powerLevelsOfUser(std::string userId) const;
/**
* Determine whether a user can send a non-state event.
*
* @param userId The id of the user.
* @param eventType The type of the event to send.
* @return Whether the user can send such an event.
*/
bool canSendMessage(std::string userId, std::string eventType) const;
/**
* Determine whether a user can send a state event.
*
* @param userId The id of the user.
* @param eventType The type of the event to send.
* @return Whether the user can send such an event.
*/
bool canSendState(std::string userId, std::string eventType) const;
+ /**
+ * Determine whether a user can invite someone else.
+ *
+ * @param userId The id of the user.
+ * @return Whether the user can send such an event.
+ */
+ bool canInvite(std::string userId) const;
+
+ /**
+ * Determine whether a user can kick someone else.
+ *
+ * @param userId The id of the user.
+ * @return Whether the user can send such an event.
+ */
+ bool canKick(std::string userId) const;
+
+ /**
+ * Determine whether a user can ban someone else.
+ *
+ * @param userId The id of the user.
+ * @return Whether the user can send such an event.
+ */
+ bool canBan(std::string userId) const;
+
+ /**
+ * Determine whether a user can redact someone else's events.
+ *
+ * @param userId The id of the user.
+ * @return Whether the user can send such an event.
+ */
+ bool canRedact(std::string userId) const;
+
/**
* Get the normalized event of this.
*
* @return The normalized event for this power levels desc.
*/
Event normalizedEvent() const;
private:
struct Private;
std::unique_ptr<Private> m_d;
};
}
diff --git a/src/tests/client/power-levels-desc-test.cpp b/src/tests/client/power-levels-desc-test.cpp
index 86ed10c..fa65ca0 100644
--- a/src/tests/client/power-levels-desc-test.cpp
+++ b/src/tests/client/power-levels-desc-test.cpp
@@ -1,168 +1,196 @@
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <libkazv-config.hpp>
#include <catch2/catch_test_macros.hpp>
#include <types.hpp>
#include <factory.hpp>
#include <power-levels-desc.hpp>
using namespace Kazv;
using namespace Kazv::Factory;
static auto examplePowerLevelsJson = R"({
"ban": 10,
"events": {
"m.room.message": 20,
"moe.kazv.mxc.some-type": 50
},
"events_default": 1,
"invite": 1,
"kick": 1,
"notifications": {"room": 99},
"redact": 49,
"state_default": 100,
"users": {
"@mew:example.com": 100
},
"users_default": 1
})"_json;
static auto examplePowerLevels = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(examplePowerLevelsJson)
);
TEST_CASE("Parse Power Levels", "[client][power-levels]")
{
auto powerLevels = PowerLevelsDesc(examplePowerLevels);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
SECTION("additional keys are discarded")
{
auto jsonWithAdditionalKeys = examplePowerLevelsJson;
jsonWithAdditionalKeys["moe.kazv.mxc.something-else"];
auto eventWithAdditionalKeys = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithAdditionalKeys)
);
auto powerLevels = PowerLevelsDesc(eventWithAdditionalKeys);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
}
SECTION("additional keys in notifications are discarded")
{
auto jsonWithAdditionalKeys = examplePowerLevelsJson;
jsonWithAdditionalKeys["notifications"]["moe.kazv.mxc.something-else"];
auto eventWithAdditionalKeys = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithAdditionalKeys)
);
auto powerLevels = PowerLevelsDesc(eventWithAdditionalKeys);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
}
SECTION("string values are converted to numbers")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["ban"] = "10";
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== examplePowerLevels.content().get());
}
SECTION("string values that cannot be converted are replaced by default values")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["ban"] = "10s";
auto expected = examplePowerLevelsJson;
expected["ban"] = 50;
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== expected);
}
SECTION("string values in events that cannot be converted are replaced by default values")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["events"]["ttt"] = "10s";
auto expected = examplePowerLevelsJson;
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== expected);
}
SECTION("string values in users that cannot be converted are replaced by default values")
{
auto jsonWithStringValues = examplePowerLevelsJson;
jsonWithStringValues["users"]["@ttt:example.com"] = "10s";
auto expected = examplePowerLevelsJson;
auto eventWithStringValues = makeEvent(
withEventType("m.power_levels")
| withStateKey("")
| withEventContent(jsonWithStringValues)
);
auto powerLevels = PowerLevelsDesc(eventWithStringValues);
REQUIRE(powerLevels.normalizedEvent().content().get()
== expected);
}
}
TEST_CASE("powerLevelsOfUser()", "[client][power-levels]")
{
auto powerLevels = PowerLevelsDesc(examplePowerLevels);
REQUIRE(powerLevels.powerLevelsOfUser("@mew:example.com") == 100);
REQUIRE(powerLevels.powerLevelsOfUser("@mewmew:example.com") == 1);
SECTION("default power levels object")
{
powerLevels = PowerLevelsDesc(json::object({}));
REQUIRE(powerLevels.powerLevelsOfUser("@mew:example.com") == 0);
REQUIRE(powerLevels.powerLevelsOfUser("@mewmew:example.com") == 0);
}
}
TEST_CASE("canSendMessage()", "[client][power-levels]")
{
auto powerLevels = PowerLevelsDesc(examplePowerLevels);
REQUIRE(powerLevels.canSendMessage("@mew:example.com", "m.room.message"));
REQUIRE(!powerLevels.canSendMessage("@mewmew:example.com", "m.room.message"));
REQUIRE(powerLevels.canSendMessage("@mew:example.com", "m.room.encrypted"));
REQUIRE(powerLevels.canSendMessage("@mewmew:example.com", "m.room.encrypted"));
}
TEST_CASE("canSendState()", "[client][power-levels]")
{
auto powerLevels = PowerLevelsDesc(examplePowerLevels);
REQUIRE(powerLevels.canSendState("@mew:example.com", "moe.kazv.mxc.some-type"));
REQUIRE(!powerLevels.canSendState("@mewmew:example.com", "moe.kazv.mxc.some-type"));
REQUIRE(powerLevels.canSendState("@mew:example.com", "moe.kazv.mxc.some-other-type"));
REQUIRE(!powerLevels.canSendState("@mewmew:example.com", "moe.kazv.mxc.some-other-type"));
}
+
+TEST_CASE("canInvite()", "[client][power-levels]")
+{
+ auto powerLevels = PowerLevelsDesc(examplePowerLevels);
+ REQUIRE(powerLevels.canInvite("@mew:example.com"));
+ REQUIRE(powerLevels.canInvite("@mewmew:example.com"));
+}
+
+TEST_CASE("canKick()", "[client][power-levels]")
+{
+ auto powerLevels = PowerLevelsDesc(examplePowerLevels);
+ REQUIRE(powerLevels.canKick("@mew:example.com"));
+ REQUIRE(powerLevels.canKick("@mewmew:example.com"));
+}
+
+TEST_CASE("canBan()", "[client][power-levels]")
+{
+ auto powerLevels = PowerLevelsDesc(examplePowerLevels);
+ REQUIRE(powerLevels.canBan("@mew:example.com"));
+ REQUIRE(!powerLevels.canBan("@mewmew:example.com"));
+}
+
+TEST_CASE("canRedact()", "[client][power-levels]")
+{
+ auto powerLevels = PowerLevelsDesc(examplePowerLevels);
+ REQUIRE(powerLevels.canRedact("@mew:example.com"));
+ REQUIRE(!powerLevels.canRedact("@mewmew:example.com"));
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Oct 29, 6:59 AM (12 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1797694
Default Alt Text
(16 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline