mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-03 09:01:59 +00:00
core: fix group link use after admin demotion (#7111)
* Fix group link use after admin demotion * fix: group role change * size limit * fix * allow delete * do not remove link * query plan * relay test * refactor --------- Co-authored-by: Paul Bottinelli <paul.bottinelli@trailofbits.com> Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
@@ -731,8 +731,11 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
ct <- getContactViaMember db cxt user m
|
||||
liftIO $ setNewContactMemberConnRequest db user m cReq
|
||||
liftIO $ (ct,) <$> getGroupLinkId db user gInfo
|
||||
sendGrpInvitation ct m groupLinkId
|
||||
toView $ CEvtSentGroupInvitation user gInfo ct m
|
||||
if memberRole' membership >= GRAdmin
|
||||
then do
|
||||
sendGrpInvitation ct m groupLinkId
|
||||
toView $ CEvtSentGroupInvitation user gInfo ct m
|
||||
else messageError "processGroupMessage: group link host no longer has admin role"
|
||||
where
|
||||
sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> CM ()
|
||||
sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do
|
||||
@@ -1535,9 +1538,12 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
Just gli@GroupLinkInfo {groupId, memberRole = gLinkMemRole} -> do
|
||||
-- TODO [short links] deduplicate request by xContactId?
|
||||
gInfo <- withStore $ \db -> getGroupInfo db cxt user groupId
|
||||
if useRelays' gInfo
|
||||
then messageWarning $ "processContactConnMessage (group " <> groupName' gInfo <> "): ignored direct join request from " <> displayName <> " (group uses relays)"
|
||||
else do
|
||||
if
|
||||
| useRelays' gInfo ->
|
||||
messageWarning $ "processContactConnMessage (group " <> groupName' gInfo <> "): ignored direct join request from " <> displayName <> " (group uses relays)"
|
||||
| memberRole' (membership gInfo) < GRAdmin ->
|
||||
messageWarning $ "processContactConnMessage (group " <> groupName' gInfo <> "): ignored join request because host is no longer admin"
|
||||
| otherwise -> do
|
||||
acceptMember_ <- asks $ acceptMember . chatHooks . config
|
||||
maybe (pure $ Right (GAAccepted, gLinkMemRole)) (\am -> liftIO $ am gInfo gli p) acceptMember_ >>= \case
|
||||
Right (acceptance, useRole)
|
||||
@@ -1566,20 +1572,23 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
createRelayRequestGroup db cxt user groupRelayInv invId chatVRange initialDelay GSMemAccepted RSInvited
|
||||
lift $ void $ getRelayRequestWorker True
|
||||
xGrpRelayTest :: InvitationId -> VersionRangeChat -> ByteString -> CM ()
|
||||
xGrpRelayTest invId chatVRange challenge = do
|
||||
privKey_ <- withAgent $ \a -> getConnLinkPrivKey a (aConnId conn)
|
||||
case privKey_ of
|
||||
Nothing -> eToView $ ChatError (CEInternalError "no short link key for relay address")
|
||||
Just privKey -> do
|
||||
let sig = C.signatureBytes $ C.sign' privKey challenge
|
||||
msg = XGrpRelayTest challenge (Just sig)
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
chatVR <- chatVersionRange
|
||||
let chatV = chatVR `peerConnChatVersion` chatVRange
|
||||
(cmdId, acId) <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
|
||||
withStore $ \db -> do
|
||||
Connection {connId = testCId} <- createRelayTestConnection db cxt user acId ConnAccepted chatV subMode
|
||||
liftIO $ setCommandConnId db user cmdId testCId
|
||||
xGrpRelayTest invId chatVRange challenge
|
||||
| isTrue userChatRelay && isNothing ucGroupId_ =
|
||||
withAgent (`getConnLinkPrivKey` aConnId conn) >>= \case
|
||||
Nothing -> eToView $ ChatError (CEInternalError "no short link key for relay address")
|
||||
Just privKey -> do
|
||||
let sig = C.signatureBytes $ C.sign' privKey challenge
|
||||
msg = XGrpRelayTest challenge (Just sig)
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
chatVR <- chatVersionRange
|
||||
let chatV = chatVR `peerConnChatVersion` chatVRange
|
||||
(cmdId, acId) <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
|
||||
withStore $ \db -> do
|
||||
Connection {connId = testCId} <- createRelayTestConnection db cxt user acId ConnAccepted chatV subMode
|
||||
liftIO $ setCommandConnId db user cmdId testCId
|
||||
| otherwise = messageError "relay test sent to non-relay link"
|
||||
where
|
||||
User {userChatRelay} = user
|
||||
-- TODO [relays] owner, relays: TBC how to communicate member rejection rules from owner to relays
|
||||
-- TODO [relays] relay: TBC communicate rejection when memberId already exists (currently checked in createJoiningMember)
|
||||
memberJoinRequestViaRelay :: InvitationId -> VersionRangeChat -> Profile -> MemberId -> MemberKey -> CM ()
|
||||
@@ -3113,7 +3122,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
where
|
||||
GroupMember {memberId = membershipMemId} = membership
|
||||
changeMemberRole gInfo' member@GroupMember {memberRole = fromRole} gEvent
|
||||
| senderRole < GRAdmin || senderRole < fromRole =
|
||||
| senderRole < maximum ([GRAdmin, fromRole, memRole] :: [GroupMemberRole]) =
|
||||
messageError "x.grp.mem.role with insufficient member permissions" $> Nothing
|
||||
| otherwise = do
|
||||
withStore' $ \db -> updateGroupMemberRole db user member memRole
|
||||
|
||||
@@ -932,7 +932,7 @@ parseChatMessages msg = case B.head msg of
|
||||
Right (compressed :: L.NonEmpty Compressed) -> case traverse decompressedSize compressed of
|
||||
Nothing -> [Left "compressed size not specified"]
|
||||
Just sizes
|
||||
| sum sizes > maxDecompressedMsgLength -> [Left "decompressed size exceeds limit"]
|
||||
| any (maxDecompressedMsgLength <) sizes || maxDecompressedMsgLength < sum sizes -> [Left "decompressed size exceeds limit"]
|
||||
| otherwise -> concatMap (either (\e -> [Left e]) parseUncompressed' . decompress1) compressed
|
||||
parseUncompressed' "" = [Left "empty string"]
|
||||
parseUncompressed' s = parseUncompressed (B.head s) s
|
||||
|
||||
@@ -1134,6 +1134,19 @@ Query:
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE group_members
|
||||
SET member_role = 'owner'
|
||||
WHERE member_category = 'user'
|
||||
AND group_id IN (
|
||||
SELECT group_id FROM groups WHERE local_display_name = 'team'
|
||||
)
|
||||
|
||||
Plan:
|
||||
SEARCH group_members USING INDEX idx_group_members_group_id_index_in_group (group_id=?)
|
||||
LIST SUBQUERY 1
|
||||
SCAN groups USING COVERING INDEX sqlite_autoindex_groups_1
|
||||
|
||||
Query:
|
||||
DELETE FROM chat_item_reactions
|
||||
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ?
|
||||
|
||||
@@ -81,6 +81,7 @@ chatGroupTests = do
|
||||
it "group live message" testGroupLiveMessage
|
||||
it "update group profile" testUpdateGroupProfile
|
||||
it "update member role" testUpdateMemberRole
|
||||
it "check owner role change" testOwnerRoleChange
|
||||
it "group description is shown as the first message to new members" testGroupDescription
|
||||
it "moderate message of another group member" testGroupModerate
|
||||
it "moderate own message (should process as deletion)" testGroupModerateOwn
|
||||
@@ -108,6 +109,7 @@ chatGroupTests = do
|
||||
it "invitee incognito" testGroupLinkInviteeIncognito
|
||||
it "incognito - join/invite" testGroupLinkIncognitoJoinInvite
|
||||
it "group link member role" testGroupLinkMemberRole
|
||||
it "demotion does not remove group link" testGroupLinkDemotedAdmin
|
||||
it "host profile received" testGroupLinkHostProfileReceived
|
||||
it "existing contact merged" testGroupLinkExistingContactMerged
|
||||
describe "group links - member screening" $ do
|
||||
@@ -1608,6 +1610,37 @@ testUpdateMemberRole =
|
||||
alice ##> "/mr team alice admin"
|
||||
alice <## "bad chat command: can't change role for self"
|
||||
|
||||
testOwnerRoleChange :: HasCallStack => TestParams -> IO ()
|
||||
testOwnerRoleChange =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
\alice bob cath -> do
|
||||
createGroup3 "team" alice bob cath
|
||||
void $ withCCTransaction cath $ \db ->
|
||||
DB.execute_
|
||||
db
|
||||
[sql|
|
||||
UPDATE group_members
|
||||
SET member_role = 'owner'
|
||||
WHERE member_category = 'user'
|
||||
AND group_id IN (
|
||||
SELECT group_id FROM groups WHERE local_display_name = 'team'
|
||||
)
|
||||
|]
|
||||
|
||||
cath ##> "/mr #team bob owner"
|
||||
cath <## "#team: you changed the role of bob to owner"
|
||||
concurrentlyN_
|
||||
[ alice <## "error: x.grp.mem.role with insufficient member permissions",
|
||||
bob <## "error: x.grp.mem.role with insufficient member permissions"
|
||||
]
|
||||
|
||||
bob ##> "/ms team"
|
||||
bob
|
||||
<### [ "alice (Alice): owner, host, connected",
|
||||
"bob (Bob): admin, you, connected",
|
||||
"cath (Catherine): admin, connected"
|
||||
]
|
||||
|
||||
testGroupDescription :: HasCallStack => TestParams -> IO ()
|
||||
testGroupDescription = testChat4 aliceProfile bobProfile cathProfile danProfile $ \alice bob cath dan -> do
|
||||
connectUsers alice bob
|
||||
@@ -2929,6 +2962,25 @@ testGroupLinkMemberRole =
|
||||
bob <## "#team: cath changed your role from member to admin"
|
||||
alice <## "#team: cath changed the role of bob from member to admin"
|
||||
|
||||
testGroupLinkDemotedAdmin :: HasCallStack => TestParams -> IO ()
|
||||
testGroupLinkDemotedAdmin =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
\alice bob _cath -> do
|
||||
createGroup2' "team" alice (bob, GRAdmin) True
|
||||
|
||||
bob ##> "/create link #team member"
|
||||
_gLink <- getGroupLink bob "team" GRMember True
|
||||
|
||||
alice ##> "/mr #team bob member"
|
||||
concurrentlyN_
|
||||
[ alice <## "#team: you changed the role of bob to member",
|
||||
bob <## "#team: alice changed your role from admin to member"
|
||||
]
|
||||
|
||||
-- demotion does not remove bob's group link (it is preserved, usable again on re-promotion)
|
||||
bob ##> "/show link #team"
|
||||
void $ getGroupLink bob "team" GRMember False
|
||||
|
||||
testGroupLinkHostIncognito :: HasCallStack => TestParams -> IO ()
|
||||
testGroupLinkHostIncognito =
|
||||
testChat2 aliceProfile bobProfile $
|
||||
|
||||
Reference in New Issue
Block a user