Skip to content

Commit

Permalink
Merge #799(kitsune): Better power level checking, part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
KitsuneRal authored Sep 18, 2024
2 parents 5cc5bd9 + d92f83f commit 798d632
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 116 deletions.
51 changes: 21 additions & 30 deletions Quotient/events/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,44 @@
#include "event.h"

#include "../logging_categories_p.h"
#include "stateevent.h"
#include "../ranges_extras.h"

#include <QtCore/QJsonDocument>

#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
#include "stateevent.h" // For deprecated isStateEvent(); remove, once Event::isStateEvent() is gone
#endif

using namespace Quotient;

void AbstractEventMetaType::addDerived(const AbstractEventMetaType* newType)
{
if (const auto existing =
std::find_if(derivedTypes.cbegin(), derivedTypes.cend(),
[&newType](const AbstractEventMetaType* t) {
return t->matrixId == newType->matrixId;
});
existing != derivedTypes.cend())
{
findIndirect(_derivedTypes, newType->matrixId, &AbstractEventMetaType::matrixId);
existing != _derivedTypes.cend()) {
if (*existing == newType)
return;
// Two different metatype objects claim the same Matrix type id; this
// is not normal, so give as much information as possible to diagnose
if ((*existing)->className == newType->className) {
qCritical(EVENTS)
<< newType->className << "claims" << newType->matrixId
<< "repeatedly; check that it's exported across translation "
"units or shared objects";
qCritical(EVENTS) << newType->className << "claims" << newType->matrixId
<< "repeatedly; check that it's exported across translation "
"units or shared objects";
Q_ASSERT(false); // That situation is plain wrong
return; // So maybe std::terminate() even?
}
qWarning(EVENTS).nospace()
<< newType->matrixId << " is already mapped to "
<< (*existing)->className << " before " << newType->className
<< "; unless the two have different isValid() conditions, the "
"latter class will never be used";
qWarning(EVENTS).nospace() << newType->matrixId << " is already mapped to "
<< (*existing)->className << " before " << newType->className
<< "; unless the two have different isValid() conditions, the "
"latter class will never be used";
}
derivedTypes.emplace_back(newType);
qDebug(EVENTS).nospace()
<< newType->matrixId << " -> " << newType->className << "; "
<< derivedTypes.size() << " derived type(s) registered for "
<< className;
_derivedTypes.emplace_back(newType);
qDebug(EVENTS).nospace() << newType->matrixId << " -> " << newType->className << "; "
<< _derivedTypes.size() << " derived type(s) registered for "
<< className;
}

Event::Event(const QJsonObject& json)
: _json(json)
{
if (!json.contains(ContentKey)
&& !json.value(UnsignedKey).toObject().contains(RedactedCauseKey)) {
qCWarning(EVENTS) << "Event without 'content' node";
qCWarning(EVENTS) << formatJson << json;
}
}
Event::Event(const QJsonObject& json) : _json(json) {}

Event::~Event() = default;

Expand All @@ -68,7 +57,9 @@ const QJsonObject Event::unsignedJson() const
return fullJson()[UnsignedKey].toObject();
}

#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
bool Event::isStateEvent() const { return is<StateEvent>(); }
#endif

void Event::dumpTo(QDebug dbg) const
{
Expand Down
18 changes: 11 additions & 7 deletions Quotient/events/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

#pragma once

#include "single_key_value.h"

#include <Quotient/converters.h>
#include <Quotient/function_traits.h>
#include "single_key_value.h"

#include <span>

namespace Quotient {
// === event_ptr_tt<> and basic type casting facilities ===
Expand All @@ -19,6 +22,7 @@ constexpr inline auto TypeKey = "type"_L1;
constexpr inline auto BodyKey = "body"_L1;
constexpr inline auto ContentKey = "content"_L1;
constexpr inline auto SenderKey = "sender"_L1;
constexpr inline auto UnsignedKey = "unsigned"_L1;

using event_type_t = QLatin1String;

Expand Down Expand Up @@ -55,6 +59,7 @@ class QUOTIENT_API AbstractEventMetaType {
}

void addDerived(const AbstractEventMetaType* newType);
auto derivedTypes() const { return std::span(_derivedTypes); }

virtual ~AbstractEventMetaType() = default;

Expand All @@ -69,7 +74,7 @@ class QUOTIENT_API AbstractEventMetaType {
Event*& event) const = 0;

private:
std::vector<const AbstractEventMetaType*> derivedTypes{};
std::vector<const AbstractEventMetaType*> _derivedTypes{};
Q_DISABLE_COPY_MOVE(AbstractEventMetaType)
};

Expand Down Expand Up @@ -146,7 +151,7 @@ class QUOTIENT_API EventMetaType : public AbstractEventMetaType {
if (EventT::TypeId != type)
return false;
} else {
for (const auto& p : derivedTypes) {
for (const auto& p : _derivedTypes) {
p->doLoadFrom(fullJson, type, event);
if (event) {
Q_ASSERT(is<EventT>(*event));
Expand Down Expand Up @@ -328,10 +333,9 @@ class QUOTIENT_API Event {
return dbg;
}

// State events are quite special in Matrix; so isStateEvent() is here,
// as an exception. For other base events, Event::is<>() and
// Quotient::is<>() should be used; don't add is* methods here
bool isStateEvent() const;
#if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR <= 9
[[deprecated("isStateEvent() has moved to RoomEvent")]] bool isStateEvent() const;
#endif

protected:
friend class EventMetaType<Event>; // To access the below constructor
Expand Down
21 changes: 19 additions & 2 deletions Quotient/events/roomevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

#include "roomevent.h"

#include "../logging_categories_p.h"
#include "encryptedevent.h"
#include "redactionevent.h"
#include "stateevent.h"

#include "encryptedevent.h"
#include "../logging_categories_p.h"

using namespace Quotient;

Expand Down Expand Up @@ -48,6 +49,8 @@ QString RoomEvent::transactionId() const
return unsignedPart<QString>("transaction_id"_L1);
}

bool RoomEvent::isStateEvent() const { return is<StateEvent>(); }

QString RoomEvent::stateKey() const
{
return fullJson()[StateKeyKey].toString();
Expand Down Expand Up @@ -98,3 +101,17 @@ const QJsonObject RoomEvent::encryptedJson() const
}
return _originalEvent->fullJson();
}

namespace {
bool containsEventType(const auto& haystack, const auto& needle)
{
return std::ranges::any_of(haystack, [needle](const AbstractEventMetaType* candidate) {
return candidate->matrixId == needle || containsEventType(candidate->derivedTypes(), needle);
});
}
}

bool Quotient::isStateEvent(const QString& eventTypeId)
{
return containsEventType(StateEvent::BaseMetaType.derivedTypes(), eventTypeId);
}
10 changes: 9 additions & 1 deletion Quotient/events/roomevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ constexpr inline auto RoomIdKey = "room_id"_L1;
constexpr inline auto StateKeyKey = "state_key"_L1;
constexpr inline auto RedactedCauseKey = "redacted_because"_L1;
constexpr inline auto RelatesToKey = "m.relates_to"_L1;
constexpr inline auto UnsignedKey = "unsigned"_L1;

class RedactionEvent;
class EncryptedEvent;
Expand Down Expand Up @@ -49,6 +48,12 @@ class QUOTIENT_API RoomEvent : public Event {
//! The transaction_id JSON value for the event.
QString transactionId() const;

// State events are special in Matrix; so isStateEvent() and stateKey() are here,
// as an exception. For other event types (including base types), Event::is<>() and
// Quotient::is<>() should be used

bool isStateEvent() const;

QString stateKey() const;

//! \brief Fill the pending event object with the room id
Expand Down Expand Up @@ -89,6 +94,9 @@ using RoomEventPtr = event_ptr_tt<RoomEvent>;
using RoomEvents = EventsArray<RoomEvent>;
using RoomEventsRange = std::ranges::subrange<RoomEvents::iterator>;

//! \brief Determine whether a given event type is that of a state event
QUOTIENT_API bool isStateEvent(const QString& eventTypeId);

} // namespace Quotient
Q_DECLARE_METATYPE(Quotient::RoomEvent*)
Q_DECLARE_METATYPE(const Quotient::RoomEvent*)
55 changes: 29 additions & 26 deletions Quotient/events/roompowerlevelsevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,36 @@

using namespace Quotient;

// The default values used below are defined in
// https://spec.matrix.org/v1.3/client-server-api/#mroompower_levels
PowerLevelsEventContent::PowerLevelsEventContent(const QJsonObject& json) :
invite(json["invite"_L1].toInt(50)),
kick(json["kick"_L1].toInt(50)),
ban(json["ban"_L1].toInt(50)),
redact(json["redact"_L1].toInt(50)),
events(fromJson<QHash<QString, int>>(json["events"_L1])),
eventsDefault(json["events_default"_L1].toInt(0)),
stateDefault(json["state_default"_L1].toInt(50)),
users(fromJson<QHash<QString, int>>(json["users"_L1])),
usersDefault(json["users_default"_L1].toInt(0)),
notifications(Notifications{json["notifications"_L1].toObject()["room"_L1].toInt(50)})
{}

QJsonObject PowerLevelsEventContent::toJson() const
PowerLevelsEventContent JsonConverter<PowerLevelsEventContent>::load(const QJsonValue& jv)
{
return QJsonObject{ { u"invite"_s, invite },
{ u"kick"_s, kick },
{ u"ban"_s, ban },
{ u"redact"_s, redact },
{ u"events"_s, Quotient::toJson(events) },
{ u"events_default"_s, eventsDefault },
{ u"state_default"_s, stateDefault },
{ u"users"_s, Quotient::toJson(users) },
{ u"users_default"_s, usersDefault },
{ u"notifications"_s, QJsonObject{ { u"room"_s, notifications.room } } } };
const auto& jo = jv.toObject();
PowerLevelsEventContent c;
#define FROM_JSON(member) fromJson(jo[toSnakeCase(#member##_L1)], c.member)
FROM_JSON(invite);
FROM_JSON(kick);
FROM_JSON(ban);
FROM_JSON(redact);
FROM_JSON(events);
FROM_JSON(eventsDefault);
FROM_JSON(stateDefault);
FROM_JSON(users);
FROM_JSON(usersDefault);
fromJson(jo["notifications"_L1]["room"_L1], c.notifications.room);
#undef FROM_JSON
return c;
}

QJsonObject JsonConverter<PowerLevelsEventContent>::dump(const PowerLevelsEventContent& c) {
return QJsonObject{ { u"invite"_s, c.invite },
{ u"kick"_s, c.kick },
{ u"ban"_s, c.ban },
{ u"redact"_s, c.redact },
{ u"events"_s, Quotient::toJson(c.events) },
{ u"events_default"_s, c.eventsDefault },
{ u"state_default"_s, c.stateDefault },
{ u"users"_s, Quotient::toJson(c.users) },
{ u"users_default"_s, c.usersDefault },
{ u"notifications"_s, QJsonObject{ { u"room"_s, c.notifications.room } } } };
}

int RoomPowerLevelsEvent::powerLevelForEvent(const QString& eventTypeId) const
Expand Down
64 changes: 49 additions & 15 deletions Quotient/events/roompowerlevelsevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,33 @@
#include "stateevent.h"

namespace Quotient {

struct QUOTIENT_API PowerLevelsEventContent {
struct Notifications {
int room;
};
// See https://spec.matrix.org/v1.11/client-server-api/#mroompower_levels for the defaults

explicit PowerLevelsEventContent(const QJsonObject& json);
QJsonObject toJson() const;
int invite = 0;
int kick = 50;
int ban = 50;

int invite;
int kick;
int ban;
int redact = 50;

int redact;
QHash<QString, int> events{};
int eventsDefault = 0;
int stateDefault = 50;

QHash<QString, int> events;
int eventsDefault;
int stateDefault;
QHash<QString, int> users{};
int usersDefault = 0;

QHash<QString, int> users;
int usersDefault;
struct Notifications {
int room = 50;
};
Notifications notifications{};
};

Notifications notifications;
template <>
struct QUOTIENT_API JsonConverter<PowerLevelsEventContent> {
static PowerLevelsEventContent load(const QJsonValue& jv);
static QJsonObject dump(const PowerLevelsEventContent& c);
};

class QUOTIENT_API RoomPowerLevelsEvent
Expand All @@ -52,8 +57,37 @@ class QUOTIENT_API RoomPowerLevelsEvent

int roomNotification() const { return content().notifications.room; }

//! \brief Get the power level for message events of a given type
//!
//! \note You normally should not compare power levels returned from this
//! and other powerLevelFor*() functions directly; use
//! Room::canSendEvents() instead
int powerLevelForEvent(const QString& eventTypeId) const;

//! \brief Get the power level for state events of a given type
//!
//! \note You normally should not compare power levels returned from this
//! and other powerLevelFor*() functions directly; use
//! Room::canSetState() instead
int powerLevelForState(const QString& eventTypeId) const;

//! \brief Get the power level for a given user
//!
//! \note You normally should not directly use power levels returned by this
//! and other powerLevelFor*() functions; use Room API instead
//! \sa Room::canSend, Room::canSendEvents, Room::canSetState,
//! Room::effectivePowerLevel
int powerLevelForUser(const QString& userId) const;

template <EventClass EvT>
int powerLevelForEventType() const
{
if constexpr (std::is_base_of_v<StateEvent, EvT>) {
return powerLevelForState(EvT::TypeId);
} else {
return powerLevelForEvent(EvT::TypeId);
}
}
};

} // namespace Quotient
Loading

0 comments on commit 798d632

Please sign in to comment.