mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-26 23:55:53 +00:00
core, ui: item about no e2ee in public channels (#6886)
* core, ui: item about no e2ee in public channels * fix, refactor * all tests * update bot api types --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
This commit is contained in:
@@ -172,8 +172,8 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case .rcvBlocked: deletedItemView()
|
||||
case let .sndDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo))
|
||||
case let .rcvDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo))
|
||||
case .sndGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText())
|
||||
case .rcvGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText())
|
||||
case let .sndGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo))
|
||||
case let .rcvGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo))
|
||||
case .chatBanner: EmptyView()
|
||||
case let .invalidJSON(json): CIInvalidJSONView(json: json)
|
||||
}
|
||||
@@ -257,6 +257,12 @@ struct ChatItemContentView<Content: View>: View {
|
||||
e2eeInfoText("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.")
|
||||
}
|
||||
|
||||
private func groupE2EEInfoText(_ info: E2EEInfo) -> Text {
|
||||
info.public == true
|
||||
? e2eeInfoText("Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages.")
|
||||
: e2eeInfoNoPQText()
|
||||
}
|
||||
|
||||
private func e2eeInfoText(_ s: LocalizedStringKey) -> Text {
|
||||
Text(s)
|
||||
.font(.caption)
|
||||
|
||||
@@ -4092,8 +4092,8 @@ public enum CIContent: Decodable, ItemContent, Hashable {
|
||||
case .rcvBlocked: return NSLocalizedString("blocked by admin", comment: "blocked chat item")
|
||||
case let .sndDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo)
|
||||
case let .rcvDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo)
|
||||
case .sndGroupE2EEInfo: return e2eeInfoNoPQStr
|
||||
case .rcvGroupE2EEInfo: return e2eeInfoNoPQStr
|
||||
case let .sndGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo)
|
||||
case let .rcvGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo)
|
||||
case .chatBanner: return ""
|
||||
case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item")
|
||||
}
|
||||
@@ -4105,6 +4105,12 @@ public enum CIContent: Decodable, ItemContent, Hashable {
|
||||
: e2eeInfoNoPQStr
|
||||
}
|
||||
|
||||
private func groupE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String {
|
||||
e2eeInfo.public == true
|
||||
? NSLocalizedString("Messages in this channel are not end-to-end encrypted. Chat relays can see these messages.", comment: "E2EE info chat item")
|
||||
: e2eeInfoNoPQStr
|
||||
}
|
||||
|
||||
private var e2eeInfoNoPQStr: String {
|
||||
NSLocalizedString("This chat is protected by end-to-end encryption.", comment: "E2EE info chat item")
|
||||
}
|
||||
@@ -5319,6 +5325,7 @@ public enum CIGroupInvitationStatus: String, Decodable, Hashable {
|
||||
|
||||
public struct E2EEInfo: Decodable, Hashable {
|
||||
public var pqEnabled: Bool?
|
||||
public var `public`: Bool?
|
||||
}
|
||||
|
||||
public enum RcvDirectEvent: Decodable, Hashable {
|
||||
|
||||
+6
-3
@@ -3800,8 +3800,8 @@ sealed class CIContent: ItemContent {
|
||||
is RcvBlocked -> generalGetString(MR.strings.blocked_by_admin_item_description)
|
||||
is SndDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo)
|
||||
is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo)
|
||||
is SndGroupE2EEInfo -> e2eeInfoNoPQStr
|
||||
is RcvGroupE2EEInfo -> e2eeInfoNoPQStr
|
||||
is SndGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo)
|
||||
is RcvGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo)
|
||||
is ChatBanner -> ""
|
||||
is InvalidJSON -> "invalid data"
|
||||
}
|
||||
@@ -3838,6 +3838,9 @@ sealed class CIContent: ItemContent {
|
||||
|
||||
private val e2eeInfoNoPQStr: String = generalGetString(MR.strings.e2ee_info_no_pq_short)
|
||||
|
||||
fun groupE2EEInfoStr(e2EEInfo: E2EEInfo): String =
|
||||
if (e2EEInfo.public == true) generalGetString(MR.strings.e2ee_info_no_e2ee) else e2eeInfoNoPQStr
|
||||
|
||||
fun featureText(feature: Feature, enabled: String, param: Int?, role: GroupMemberRole? = null): String =
|
||||
(if (feature.hasParam) {
|
||||
"${feature.text}: ${timeText(param)}"
|
||||
@@ -4364,7 +4367,7 @@ enum class CIGroupInvitationStatus {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class E2EEInfo (val pqEnabled: Boolean?) {}
|
||||
class E2EEInfo (val pqEnabled: Boolean?, val public: Boolean? = null) {}
|
||||
|
||||
object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) {
|
||||
|
||||
+10
-14
@@ -680,21 +680,17 @@ fun ChatItemView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun E2EEInfoNoPQText() {
|
||||
e2eeInfoText(MR.strings.e2ee_info_no_pq)
|
||||
fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
e2eeInfoText(when (e2EEInfo.pqEnabled) {
|
||||
true -> MR.strings.e2ee_info_pq
|
||||
false -> MR.strings.e2ee_info_no_pq
|
||||
null -> MR.strings.e2ee_info_e2ee
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
if (e2EEInfo.pqEnabled != null) {
|
||||
if (e2EEInfo.pqEnabled) {
|
||||
e2eeInfoText(MR.strings.e2ee_info_pq)
|
||||
} else {
|
||||
E2EEInfoNoPQText()
|
||||
}
|
||||
} else {
|
||||
e2eeInfoText(MR.strings.e2ee_info_e2ee)
|
||||
}
|
||||
fun GroupE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
e2eeInfoText(if (e2EEInfo.public == true) MR.strings.e2ee_info_no_e2ee else MR.strings.e2ee_info_no_pq)
|
||||
}
|
||||
|
||||
if (cItem.meta.itemDeleted != null && (!revealed.value || cItem.isDeletedContent)) {
|
||||
@@ -794,8 +790,8 @@ fun ChatItemView(
|
||||
is CIContent.RcvBlocked -> DeletedItem()
|
||||
is CIContent.SndDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText()
|
||||
is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText()
|
||||
is CIContent.SndGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.RcvGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.ChatBanner -> Spacer(modifier = Modifier.size(0.dp))
|
||||
is CIContent.InvalidJSON -> {
|
||||
CIInvalidJSONView(c.json)
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
<string name="e2ee_info_e2ee"><![CDATA[Messages are protected by <b>end-to-end encryption</b>.]]></string>
|
||||
<string name="e2ee_info_no_pq"><![CDATA[Messages, files and calls are protected by <b>end-to-end encryption</b> with perfect forward secrecy, repudiation and break-in recovery.]]></string>
|
||||
<string name="e2ee_info_pq"><![CDATA[Messages, files and calls are protected by <b>quantum resistant e2e encryption</b> with perfect forward secrecy, repudiation and break-in recovery.]]></string>
|
||||
<string name="e2ee_info_no_e2ee"><![CDATA[Messages in this channel are <b>not end-to-end encrypted</b>. Chat relays can see these messages.]]></string>
|
||||
<string name="e2ee_info_no_pq_short">This chat is protected by end-to-end encryption.</string>
|
||||
<string name="e2ee_info_pq_short">This chat is protected by quantum resistant end-to-end encryption.</string>
|
||||
|
||||
|
||||
@@ -1847,6 +1847,7 @@ connFullLink + ((' ' + connShortLink) if connShortLink is not None else '') # Py
|
||||
## E2EInfo
|
||||
|
||||
**Record type**:
|
||||
- public: bool?
|
||||
- pqEnabled: bool?
|
||||
|
||||
|
||||
|
||||
@@ -2094,6 +2094,7 @@ export interface DroppedMsg {
|
||||
}
|
||||
|
||||
export interface E2EInfo {
|
||||
public?: boolean
|
||||
pqEnabled?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -2008,7 +2008,7 @@ processChatCommand vr nm = \case
|
||||
let cd = CDDirectRcv ct
|
||||
createItem sharedMsgId content = createChatItem user cd False content sharedMsgId Nothing
|
||||
cInfo = DirectChat ct
|
||||
void $ createItem Nothing $ CIRcvDirectE2EEInfo $ E2EInfo $ connRequestPQEncryption cReq
|
||||
void $ createItem Nothing $ CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ connRequestPQEncryption cReq
|
||||
void $ createFeatureEnabledItems_ user ct
|
||||
aci <- mapM (createItem welcomeSharedMsgId . CIRcvMsgContent) message
|
||||
let chat = case aci of
|
||||
@@ -3858,7 +3858,7 @@ processChatCommand vr nm = \case
|
||||
createNewGroupItems user gInfo = do
|
||||
let cd = CDGroupSnd gInfo Nothing
|
||||
createInternalChatItem user cd CIChatBanner (Just epochStart)
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing
|
||||
createGroupFeatureItems user cd CISndGroupFeature gInfo
|
||||
sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> CM ()
|
||||
sendGrpInvitation user ct@Contact {contactId, localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership, businessChat} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do
|
||||
|
||||
@@ -1029,7 +1029,7 @@ acceptBusinessJoinRequestAsync
|
||||
createJoiningMemberConnection db user uclId connIds chatV cReqChatVRange groupMemberId subMode
|
||||
let cd = CDGroupSnd gInfo Nothing
|
||||
-- TODO [short links] move to profileContactRequest?
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CISndGroupE2EEInfo $ e2eInfoGroup gInfo) Nothing
|
||||
createGroupFeatureItems user cd CISndGroupFeature gInfo
|
||||
-- TODO [short links] get updated business chat group and member? (currently not used)
|
||||
pure (gInfo, clientMember)
|
||||
@@ -1485,7 +1485,7 @@ createContactPQSndItem :: User -> Contact -> Connection -> PQEncryption -> CM (C
|
||||
createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' =
|
||||
flip catchAllErrors (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of
|
||||
(Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled')
|
||||
(Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo $ Just pqSndEnabled')
|
||||
(Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (e2eInfoEncrypted $ Just pqSndEnabled')
|
||||
_ -> pure (ct, conn)
|
||||
where
|
||||
createPQItem ciContent = do
|
||||
@@ -1500,7 +1500,7 @@ updateContactPQRcv :: User -> Contact -> Connection -> PQEncryption -> CM (Conta
|
||||
updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' =
|
||||
flip catchAllErrors (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of
|
||||
(Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled')
|
||||
(Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo $ Just pqRcvEnabled')
|
||||
(Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (e2eInfoEncrypted $ Just pqRcvEnabled')
|
||||
_ -> pure (ct, conn)
|
||||
where
|
||||
updatePQ ciContent = do
|
||||
|
||||
@@ -590,7 +590,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
-- [incognito] print incognito profile used for this contact
|
||||
incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId)
|
||||
toView $ CEvtContactConnected user ct' (fmap fromLocalProfile incognitoProfile)
|
||||
let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo $ Just pqEnc) Nothing
|
||||
let createE2EItem = createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ Just pqEnc) Nothing
|
||||
-- TODO [short links] get contact request by contactRequestId, check encryption (UserContactRequest.pqSupport)?
|
||||
when (directOrUsed ct') $ case (preparedContact ct', contactRequestId' ct') of
|
||||
(Nothing, Nothing) -> do
|
||||
@@ -842,7 +842,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
firstConnectedHost
|
||||
( do
|
||||
let cd = CDGroupRcv gInfo'' scopeInfo m''
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo $ e2eInfoGroup gInfo'') Nothing
|
||||
let prepared = preparedGroup gInfo''
|
||||
unless (isJust prepared) $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo''
|
||||
memberConnectedChatItem gInfo'' scopeInfo m''
|
||||
@@ -1356,7 +1356,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
upsertDirectRequestItem cd (requestMsg_, prevSharedMsgId_)
|
||||
Nothing -> do
|
||||
void $ createChatItem user (CDDirectSnd ct) False CIChatBanner Nothing (Just epochStart)
|
||||
let e2eContent = CIRcvDirectE2EEInfo $ E2EInfo $ Just $ CR.pqSupportToEnc $ reqPQSup
|
||||
let e2eContent = CIRcvDirectE2EEInfo $ e2eInfoEncrypted $ Just $ CR.pqSupportToEnc $ reqPQSup
|
||||
void $ createChatItem user cd False e2eContent Nothing Nothing
|
||||
void $ createFeatureEnabledItems_ user ct
|
||||
forM_ (autoReply addressSettings) $ \mc -> forM_ welcomeSharedMsgId $ \sharedMsgId ->
|
||||
@@ -2546,7 +2546,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
-- create item in both scopes
|
||||
let gInfo' = gInfo {membership = membership'}
|
||||
cd = CDGroupRcv gInfo' Nothing m
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = Just PQEncOff}) Nothing
|
||||
createInternalChatItem user cd (CIRcvGroupE2EEInfo $ e2eInfoGroup gInfo') Nothing
|
||||
let prepared = preparedGroup gInfo'
|
||||
unless (isJust prepared) $ createGroupFeatureItems user cd CIRcvGroupFeature gInfo'
|
||||
let welcomeMsgId_ = (\PreparedGroup {welcomeSharedMsgId = mId} -> mId) <$> preparedGroup gInfo'
|
||||
|
||||
@@ -177,9 +177,16 @@ data CIContent (d :: MsgDirection) where
|
||||
|
||||
deriving instance Show (CIContent d)
|
||||
|
||||
data E2EInfo = E2EInfo {pqEnabled :: Maybe PQEncryption}
|
||||
-- stored in database, all changed must be backward compatible
|
||||
data E2EInfo = E2EInfo {public :: Maybe Bool, pqEnabled :: Maybe PQEncryption}
|
||||
deriving (Eq, Show)
|
||||
|
||||
e2eInfoEncrypted :: Maybe PQEncryption -> E2EInfo
|
||||
e2eInfoEncrypted pqEnabled = E2EInfo {public = Nothing, pqEnabled}
|
||||
|
||||
e2eInfoGroup :: GroupInfo -> E2EInfo
|
||||
e2eInfoGroup g = E2EInfo {public = if useRelays' g then Just True else Nothing, pqEnabled = Just PQEncOff}
|
||||
|
||||
ciMsgContent :: CIContent d -> Maybe MsgContent
|
||||
ciMsgContent = \case
|
||||
CISndMsgContent mc -> Just mc
|
||||
@@ -315,9 +322,14 @@ directE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of
|
||||
Nothing -> simpleE2EText
|
||||
|
||||
groupE2EInfoToText :: E2EInfo -> Text
|
||||
groupE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of
|
||||
Just _ -> e2eInfoNoPQText
|
||||
Nothing -> simpleE2EText
|
||||
groupE2EInfoToText E2EInfo {pqEnabled, public} = case public of
|
||||
Just True -> publicGroupNoE2EText
|
||||
_ -> case pqEnabled of
|
||||
Just _ -> e2eInfoNoPQText
|
||||
Nothing -> simpleE2EText
|
||||
|
||||
publicGroupNoE2EText :: Text
|
||||
publicGroupNoE2EText = "This channel or group is NOT end-to-end encrypted."
|
||||
|
||||
simpleE2EText :: Text
|
||||
simpleE2EText = "This conversation is protected by end-to-end encryption"
|
||||
|
||||
@@ -28,6 +28,7 @@ import Simplex.Chat.Controller (ChatConfig (..), ChatHooks (..), defaultChatHook
|
||||
import Simplex.Chat.Library.Internal (uniqueMsgMentions, updatedMentionNames)
|
||||
import Simplex.Chat.Markdown (parseMaybeMarkdownList)
|
||||
import Simplex.Chat.Messages (CIMention (..), CIMentionMember (..), ChatItemId)
|
||||
import Simplex.Chat.Messages.CIContent (publicGroupNoE2EText)
|
||||
import Simplex.Chat.Options
|
||||
import Simplex.Chat.Protocol (MsgMention (..), MsgContent (..), msgContentText)
|
||||
import Simplex.Chat.Types
|
||||
@@ -8857,7 +8858,7 @@ testChannelLinkAfterWelcomeUpdate ps =
|
||||
shortLink' `shouldBe` shortLink
|
||||
fullLink' `shouldBe` fullLink
|
||||
memberJoinChannel "team" [bob] [alice] shortLink' fullLink' dan
|
||||
dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, e2eeInfoNoPQStr), (0, "connected")])
|
||||
dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, T.unpack publicGroupNoE2EText), (0, "connected")])
|
||||
|
||||
alice #> "#team hi"
|
||||
bob <# "#team> hi"
|
||||
|
||||
Reference in New Issue
Block a user