mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-10 10:01:50 +00:00
wip
This commit is contained in:
@@ -2143,21 +2143,25 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage =
|
||||
createContentItem gInfo Nothing Nothing
|
||||
-- no delivery task - message already forwarded by relay
|
||||
pure Nothing
|
||||
Just m@GroupMember {memberId} -> do
|
||||
(gInfo', m', scopeInfo) <- mkGetMessageChatScope cxt user gInfo m content msgScope_
|
||||
if blockedByAdmin m'
|
||||
then createBlockedByAdmin gInfo' (Just m') scopeInfo $> Nothing
|
||||
else case prohibitedGroupContent gInfo' m' scopeInfo content ft_ fInv_ False of
|
||||
Just f -> rejected gInfo' (Just m') scopeInfo f $> Nothing
|
||||
Nothing ->
|
||||
withStore' (\db -> getCIModeration db cxt user gInfo' memberId sharedMsgId_) >>= \case
|
||||
Just ciModeration -> do
|
||||
applyModeration gInfo' m' scopeInfo ciModeration
|
||||
withStore' $ \db -> deleteCIModeration db gInfo' memberId sharedMsgId_
|
||||
pure Nothing
|
||||
Nothing -> do
|
||||
createContentItem gInfo' (Just m') scopeInfo
|
||||
pure $ Just $ infoToDeliveryContext gInfo' scopeInfo sentAsGroup
|
||||
Just m@GroupMember {memberId}
|
||||
-- only an owner may post as the channel; a non-owner's signed asGroup post (e.g. relay-injected) must not render as the channel
|
||||
| sentAsGroup && memberRole' m < GROwner ->
|
||||
messageError "x.msg.new: member is not allowed to send as group" $> Nothing
|
||||
| otherwise -> do
|
||||
(gInfo', m', scopeInfo) <- mkGetMessageChatScope cxt user gInfo m content msgScope_
|
||||
if blockedByAdmin m'
|
||||
then createBlockedByAdmin gInfo' (Just m') scopeInfo $> Nothing
|
||||
else case prohibitedGroupContent gInfo' m' scopeInfo content ft_ fInv_ False of
|
||||
Just f -> rejected gInfo' (Just m') scopeInfo f $> Nothing
|
||||
Nothing ->
|
||||
withStore' (\db -> getCIModeration db cxt user gInfo' memberId sharedMsgId_) >>= \case
|
||||
Just ciModeration -> do
|
||||
applyModeration gInfo' m' scopeInfo ciModeration
|
||||
withStore' $ \db -> deleteCIModeration db gInfo' memberId sharedMsgId_
|
||||
pure Nothing
|
||||
Nothing -> do
|
||||
createContentItem gInfo' (Just m') scopeInfo
|
||||
pure $ Just $ infoToDeliveryContext gInfo' scopeInfo sentAsGroup
|
||||
where
|
||||
rejected gInfo' m' scopeInfo f = newChatItem gInfo' m' scopeInfo (ciContentNoParse $ CIRcvGroupFeatureRejected f) Nothing Nothing False
|
||||
timed_ gInfo' = if forwarded then rcvCITimed_ (Just Nothing) itemTTL else rcvGroupCITimed gInfo' itemTTL
|
||||
|
||||
@@ -105,7 +105,10 @@ batchDeliveryTasks1 _vr maxLen = toResult . foldl' addToBatch ([], [], [], 0, 0)
|
||||
|
||||
-- | Encode a batch element for relay groups: ><GrpMsgForward>[/<sigs>]<body>.
|
||||
encodeFwdElement :: GrpMsgForward -> VerifiedMsg 'Json -> ByteString
|
||||
encodeFwdElement fwd verifiedMsg = ">" <> smpEncode fwd <> encodeBatchElement signedMsg_ msgBody
|
||||
encodeFwdElement fwd@GrpMsgForward {fwdSender} verifiedMsg
|
||||
| FwdChannel <- fwdSender, isJust signedMsg_ =
|
||||
error "encodeFwdElement: signed message must forward as FwdMember, not FwdChannel"
|
||||
| otherwise = ">" <> smpEncode fwd <> encodeBatchElement signedMsg_ msgBody
|
||||
where
|
||||
(_, signedMsg_, msgBody) = verifiedMsgParts verifiedMsg
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import qualified Data.Aeson as J
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import Data.Int (Int64)
|
||||
import qualified Data.List.NonEmpty as L
|
||||
import Data.Maybe (isNothing)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Time.Clock (UTCTime, getCurrentTime)
|
||||
@@ -155,7 +156,7 @@ getMsgDeliveryTask_ db taskId =
|
||||
toTask ((Only taskId') :. jobScopeRow :. (senderGMId, senderMemberId, senderMemberName, brokerTs, Binary msgBody, chatBinding_, sigs_, BI showGroupAsSender)) =
|
||||
case (toJobScope_ jobScopeRow, J.eitherDecodeStrict' msgBody) of
|
||||
(Just jobScope, Right chatMsg) ->
|
||||
let fwdSender = if showGroupAsSender then FwdChannel else FwdMember senderMemberId senderMemberName
|
||||
let fwdSender = if showGroupAsSender && isNothing chatBinding_ then FwdChannel else FwdMember senderMemberId senderMemberName
|
||||
-- Re-parsed from msg_body: validates stored content against current code.
|
||||
-- Signed: original bytes preserved (re-encoding would invalidate signature).
|
||||
-- Unsigned: re-encoded from parsed ChatMessage on forward (sanitizes content).
|
||||
|
||||
@@ -36,7 +36,7 @@ import Simplex.Chat.Messages (CIMention (..), CIMentionMember (..), ChatItemId)
|
||||
import Simplex.Chat.Messages.Batch (encodeBinaryBatch, encodeFwdElement)
|
||||
import Simplex.Chat.Messages.CIContent (publicGroupNoE2EText)
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Protocol (ChatMessage (ChatMessage), ChatMsgEvent (XGrpMemNew, XMsgUpdate), FwdSender (FwdMember), GrpMsgForward (GrpMsgForward), MsgMention (..), MsgContent (..), VerifiedMsg (VMUnsigned), msgContentText)
|
||||
import Simplex.Chat.Protocol (ChatMessage (ChatMessage), ChatMsgEvent (XGrpMemNew, XMsgUpdate, XMsgNew), FwdSender (FwdMember), GrpMsgForward (GrpMsgForward), MsgContainer (..), MsgMention (..), MsgContent (..), VerifiedMsg (VMUnsigned), mcSimple, msgContentText)
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.MemberRelations (MemberRelation (..), getRelation, setRelation)
|
||||
import Simplex.Chat.Types.Shared (GroupMemberRole (..), GroupAcceptance (..))
|
||||
@@ -334,6 +334,8 @@ chatGroupTests = do
|
||||
it "should delete member message in channel" testChannelMemberMessageDelete
|
||||
it "should sign member message and reuse signature on edit" testChannelMemberMessageSign
|
||||
it "should reject unsigned update of a signed item" testChannelMemberUpdateEnforcement
|
||||
it "should sign as-channel post and keep it displayed as the channel" testChannelAsGroupSign
|
||||
it "should reject a non-owner posting as the channel" testChannelAsGroupSpoof
|
||||
|
||||
testGroupCheckMessages :: HasCallStack => TestParams -> IO ()
|
||||
testGroupCheckMessages =
|
||||
@@ -12201,6 +12203,89 @@ testChannelMemberUpdateEnforcement ps =
|
||||
(Only smid : _) -> pure (SharedMsgId smid)
|
||||
_ -> fail "no shared_msg_id"
|
||||
|
||||
testChannelAsGroupSign :: HasCallStack => TestParams -> IO ()
|
||||
testChannelAsGroupSign ps =
|
||||
withNewTestChat ps "alice" aliceProfile $ \alice ->
|
||||
withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob ->
|
||||
withNewTestChat ps "cath" cathProfile $ \cath ->
|
||||
withNewTestChat ps "dan" danProfile $ \dan ->
|
||||
withNewTestChat ps "eve" eveProfile $ \eve -> do
|
||||
createChannel1Relay "team" alice bob cath dan eve
|
||||
|
||||
-- owner posts as the channel, signed: verifiable AND displayed as the channel
|
||||
alice ##> "/_send #1(as_group=on) sign=on text signed channel post"
|
||||
alice <# "#team signed channel post"
|
||||
bob <# "#team> signed channel post"
|
||||
[cath, dan, eve] *<# "#team> signed channel post [>>]"
|
||||
alice #$> ("/_get chat #1 count=1", chat, [(1, "signed channel post (signed)")])
|
||||
cath #$> ("/_get chat #1 count=1", chat, [(0, "signed channel post (signed)")])
|
||||
|
||||
-- owner posts as the channel, unsigned: anonymous (FwdChannel), no signature, still as the channel
|
||||
alice ##> "/_send #1(as_group=on) text plain channel post"
|
||||
alice <# "#team plain channel post"
|
||||
bob <# "#team> plain channel post"
|
||||
[cath, dan, eve] *<# "#team> plain channel post [>>]"
|
||||
alice #$> ("/_get chat #1 count=1", chat, [(1, "plain channel post")])
|
||||
cath #$> ("/_get chat #1 count=1", chat, [(0, "plain channel post")])
|
||||
|
||||
testChannelAsGroupSpoof :: HasCallStack => TestParams -> IO ()
|
||||
testChannelAsGroupSpoof ps =
|
||||
withNewTestChat ps "alice" aliceProfile $ \alice ->
|
||||
withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob ->
|
||||
withNewTestChat ps "cath" cathProfile $ \cath ->
|
||||
withNewTestChat ps "dan" danProfile $ \dan ->
|
||||
withNewTestChat ps "eve" eveProfile $ \eve -> do
|
||||
createChannel1Relay "team" alice bob cath dan eve
|
||||
promoteChannelMember "team" alice bob cath [dan, eve]
|
||||
|
||||
-- cath posts legitimately (introduces cath to dan as a member)
|
||||
cath #> "#team hi from cath"
|
||||
bob <# "#team cath> hi from cath"
|
||||
concurrentlyN_
|
||||
[ alice <# "#team cath> hi from cath [>>]",
|
||||
do dan <### [EndsWith "updated to cath"]
|
||||
dan <## "#team: bob introduced cath (Catherine) in the channel"
|
||||
dan <# "#team cath> hi from cath [>>]",
|
||||
do eve <### [EndsWith "updated to cath"]
|
||||
eve <## "#team: bob introduced cath (Catherine) in the channel"
|
||||
eve <# "#team cath> hi from cath [>>]"
|
||||
]
|
||||
|
||||
-- the relay forges an asGroup=True post attributed to non-owner cath; dan rejects (owner guard, §2)
|
||||
cathMemId <- memberIdByName bob "cath"
|
||||
connId <- relayConnIdToMember bob "dan"
|
||||
ts <- getCurrentTime
|
||||
let ChatController {smpAgent = bobAgent} = chatController bob
|
||||
container = (mcSimple (MCText "fake channel announcement")) {asGroup = Just True}
|
||||
chatMsg = ChatMessage chatInitialVRange Nothing (XMsgNew container)
|
||||
fwd = GrpMsgForward (FwdMember cathMemId "cath") ts
|
||||
body = encodeBinaryBatch [encodeFwdElement fwd (VMUnsigned chatMsg)]
|
||||
sent <- runExceptT $ sendMessages bobAgent [(connId, PQEncOff, MsgFlags False, vrValue body)]
|
||||
either (fail . show) (const $ pure ()) sent
|
||||
dan <##. "error: x.msg.new: member is not allowed to send as group"
|
||||
-- not rendered as the channel: dan still holds only the legitimate member message
|
||||
threadDelay 1000000
|
||||
dan #$> ("/_get chat #1 count=1", chat, [(0, "hi from cath")])
|
||||
where
|
||||
memberIdByName :: TestCC -> T.Text -> IO MemberId
|
||||
memberIdByName cc name = do
|
||||
rows <- withCCTransaction cc $ \db ->
|
||||
DB.query db "SELECT member_id FROM group_members WHERE local_display_name = ?" (Only name) :: IO [Only ByteString]
|
||||
case rows of
|
||||
(Only mid : _) -> pure (MemberId mid)
|
||||
_ -> fail $ "no member " <> T.unpack name
|
||||
relayConnIdToMember :: TestCC -> T.Text -> IO ByteString
|
||||
relayConnIdToMember cc name = do
|
||||
rows <- withCCTransaction cc $ \db ->
|
||||
DB.query
|
||||
db
|
||||
"SELECT c.agent_conn_id FROM connections c JOIN group_members m ON m.group_member_id = c.group_member_id WHERE m.local_display_name = ?"
|
||||
(Only name) ::
|
||||
IO [Only ByteString]
|
||||
case rows of
|
||||
(Only connId : _) -> pure connId
|
||||
_ -> fail $ "no relay connection to member " <> T.unpack name
|
||||
|
||||
testGroupLinkContentFilter :: HasCallStack => TestParams -> IO ()
|
||||
testGroupLinkContentFilter =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
|
||||
Reference in New Issue
Block a user