From 67354eb3a35543a7fce62fabdb44613f75fd229d Mon Sep 17 00:00:00 2001 From: James Graham Date: Sun, 15 Sep 2024 16:43:03 +0100 Subject: [PATCH] Upstream canSetState and canSendEvent from NeoChat and use to check before setting state to avoid pinging the server with no chance of success. --- Quotient/events/roompowerlevelsevent.cpp | 10 ++++++ Quotient/events/roompowerlevelsevent.h | 6 ++++ Quotient/room.cpp | 45 ++++++++++++++++++++++++ Quotient/room.h | 12 +++++++ Quotient/user.cpp | 4 +++ autotests/testolmaccount.cpp | 1 + 6 files changed, 78 insertions(+) diff --git a/Quotient/events/roompowerlevelsevent.cpp b/Quotient/events/roompowerlevelsevent.cpp index 0a68b6044..e2bf65248 100644 --- a/Quotient/events/roompowerlevelsevent.cpp +++ b/Quotient/events/roompowerlevelsevent.cpp @@ -48,3 +48,13 @@ int RoomPowerLevelsEvent::powerLevelForUser(const QString& userId) const { return users().value(userId, usersDefault()); } + +bool RoomPowerLevelsEvent::canSendEvent(const QString& eventTypeId, const QString& memberId) const +{ + return powerLevelForUser(memberId) >= powerLevelForEvent(eventTypeId); +} + +bool RoomPowerLevelsEvent::canSetState(const QString& eventTypeId, const QString& memberId) const +{ + return powerLevelForUser(memberId) >= powerLevelForState(eventTypeId); +} diff --git a/Quotient/events/roompowerlevelsevent.h b/Quotient/events/roompowerlevelsevent.h index aedc7a725..1d1ab7482 100644 --- a/Quotient/events/roompowerlevelsevent.h +++ b/Quotient/events/roompowerlevelsevent.h @@ -55,5 +55,11 @@ class QUOTIENT_API RoomPowerLevelsEvent int powerLevelForEvent(const QString& eventTypeId) const; int powerLevelForState(const QString& eventTypeId) const; int powerLevelForUser(const QString& userId) const; + + //! Convenience function to check if the given member ID can set the given event type + bool canSendEvent(const QString& eventTypeId, const QString& memberId) const; + + //! Convenience function to check if the given member ID can set the given state event type + bool canSetState(const QString& eventTypeId, const QString& memberId) const; }; } // namespace Quotient diff --git a/Quotient/room.cpp b/Quotient/room.cpp index 8a461b6e9..c9aae8b2f 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -2212,6 +2212,32 @@ QString Room::postJson(const QString& matrixType, const QJsonObject& eventConten return d->sendEvent(loadEvent(matrixType, eventContent))->transactionId(); } +bool Room::canSendEvent(const QString &eventTypeId, const QString& memberId) const +{ + int eventPowerLevel; + auto plEvent = currentState().get(); + if (plEvent == nullptr) { + eventPowerLevel = 0; + } else { + eventPowerLevel = plEvent->powerLevelForEvent(eventTypeId); + } + + return memberEffectivePowerLevel(memberId) >= eventPowerLevel; +} + +bool Room::canSetState(const QString &eventTypeId, const QString& memberId) const +{ + int statePowerLevel; + const auto plEvent = currentState().get(); + if (plEvent == nullptr) { + statePowerLevel = 50; + } else { + statePowerLevel = plEvent->powerLevelForState(eventTypeId); + } + + return memberEffectivePowerLevel(memberId) >= statePowerLevel; +} + SetRoomStateWithKeyJob* Room::setState(const StateEvent& evt) { return setState(evt.matrixType(), evt.stateKey(), evt.contentJson()); @@ -2226,25 +2252,40 @@ SetRoomStateWithKeyJob* Room::setState(const QString& evtType, void Room::setName(const QString& newName) { + if (!canSetState(RoomNameEvent::TypeId)) { + qCWarning(EVENTS) << "You do not have permission to rename the room"; + } setState(newName); } void Room::setCanonicalAlias(const QString& newAlias) { + if (!canSetState(RoomCanonicalAliasEvent::TypeId)) { + qCWarning(EVENTS) << "You do not have permission to set the room canonical alias"; + } setState(newAlias, altAliases()); } void Room::setPinnedEvents(const QStringList& events) { + if (canSetState(RoomPinnedEventsEvent::TypeId)) { + qCWarning(EVENTS) << "You do not have permission to pin an event in the room"; + } setState(events); } void Room::setLocalAliases(const QStringList& aliases) { + if (canSetState(RoomCanonicalAliasEvent::TypeId)) { + qCWarning(EVENTS) << "You do not have permission to set room local aliases"; + } setState(canonicalAlias(), aliases); } void Room::setTopic(const QString& newTopic) { + if (canSetState(RoomTopicEvent::TypeId)) { + qCWarning(EVENTS) << "You do not have permission to set the room topic"; + } setState(newTopic); } @@ -3457,6 +3498,10 @@ void Room::activateEncryption() qCWarning(E2EE) << "Room" << objectName() << "is already encrypted"; return; } + if (!canSetState(EncryptionEvent::TypeId)) { + qCWarning(E2EE) << "You do not have permission to encrypt the room"; + return; + } setState(EncryptionType::MegolmV1AesSha2); } diff --git a/Quotient/room.h b/Quotient/room.h index f2d86c8e0..7dc8cdb37 100644 --- a/Quotient/room.h +++ b/Quotient/room.h @@ -702,6 +702,18 @@ class QUOTIENT_API Room : public QObject { PendingEventItem::future_type whenMessageMerged(QString txnId) const; + //! True if the given user can send the given event type + //! + //! \param eventTypedId the Matrix type for the event. + //! \param memberId the Matrix ID of the member to check. If blank the local member is used. + Q_INVOKABLE bool canSendEvent(const QString &eventTypeId, const QString& memberId = {}) const; + + //! True if the given user can send the given state event type + //! + //! \param eventTypedId the Matrix type for the event. + //! \param memberId the Matrix ID of the member to check. If blank the local member is used. + Q_INVOKABLE bool canSetState(const QString &eventTypeId, const QString& memberId = {}) const; + //! Send a request to update the room state with the given event SetRoomStateWithKeyJob* setState(const StateEvent& evt); diff --git a/Quotient/user.cpp b/Quotient/user.cpp index ea3de6ce7..3f64f0255 100644 --- a/Quotient/user.cpp +++ b/Quotient/user.cpp @@ -99,6 +99,10 @@ void User::rename(const QString& newName, Room* r) rename(newName); return; } + if (!r->canSetState(RoomMemberEvent::TypeId, id())) { + qCWarning(MAIN) << "You do not have permission is rename" << id(); + return; + } // #481: take the current state and update it with the new name if (const auto& maybeEvt = r->currentState().get(id())) { auto content = maybeEvt->content(); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 37d29c7a6..10be640e9 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -457,6 +457,7 @@ void TestOlmAccount::enableEncryption() QThread::sleep(100); } auto room = alice->rooms(JoinState::Join)[0]; + qWarning() << room->memberEffectivePowerLevel(alice->userId()); room->activateEncryption(); QSignalSpy encryptionSpy(room, &Room::encryption); QVERIFY(encryptionSpy.wait(10000));