{-# LANGUAGE OverloadedLabels #-}
module Test.Federation where
import qualified API.Brig as BrigP
import API.Galley
import Control.Lens hiding ((&&~))
import Control.Monad.Codensity
import Control.Monad.Reader
import qualified Data.ProtoLens as Proto
import Data.ProtoLens.Labels ()
import MLS.Util
import Notifications
import Numeric.Lens
import qualified Proto.Otr as Proto
import qualified Proto.Otr_Fields as Proto
import SetupHelpers
import Testlib.Prelude
import Testlib.ResourcePool
import Testlib.VersionedFed
testBasicFederationAcrossVersions :: (HasCallStack) => FedVersionPair -> App ()
testBasicFederationAcrossVersions :: HasCallStack => FedVersionPair -> App ()
testBasicFederationAcrossVersions FedVersionPair
pair = do
FedVersionPair -> (Value -> Value -> App ()) -> App ()
forall a.
HasCallStack =>
FedVersionPair -> (Value -> Value -> App a) -> App a
withFederationDomains FedVersionPair
pair ((Value -> Value -> App ()) -> App ())
-> (Value -> Value -> App ()) -> App ()
forall a b. (a -> b) -> a -> b
$ Ciphersuite -> Value -> Value -> App ()
federationTestActionMLS Ciphersuite
cs
where
cs :: Ciphersuite
cs :: Ciphersuite
cs =
if (Maybe Integer -> Bool) -> Bool
anyVersionSatisfies (Bool -> (Integer -> Bool) -> Maybe Integer -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (\Integer
i -> Integer
i Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
2))
then String -> Ciphersuite
Ciphersuite String
"0x0001"
else Ciphersuite
forall a. Default a => a
def
where
anyVersionSatisfies :: (Maybe Integer -> Bool) -> Bool
anyVersionSatisfies :: (Maybe Integer -> Bool) -> Bool
anyVersionSatisfies Maybe Integer -> Bool
p = (Maybe Integer -> Bool) -> [Maybe Integer] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Maybe Integer -> Bool
p [FedVersion -> Maybe Integer
versionToInt FedVersionPair
pair.localFedVersion, FedVersion -> Maybe Integer
versionToInt FedVersionPair
pair.remoteFedVersion]
versionToInt :: FedVersion -> Maybe Integer
versionToInt :: FedVersion -> Maybe Integer
versionToInt (FedVersionLegacy Integer
i) = Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
i
versionToInt FedVersion
_ = Maybe Integer
forall a. Maybe a
Nothing
withFederationDomains :: (HasCallStack) => FedVersionPair -> (Value -> Value -> App a) -> App a
withFederationDomains :: forall a.
HasCallStack =>
FedVersionPair -> (Value -> Value -> App a) -> App a
withFederationDomains FedVersionPair
pair' Value -> Value -> App a
action =
if FedVersion
FedVersionCurrentStable FedVersion -> [FedVersion] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [FedVersionPair
pair'.localFedVersion, FedVersionPair
pair'.remoteFedVersion]
then [ServiceOverrides] -> ([String] -> App a) -> App a
forall a.
HasCallStack =>
[ServiceOverrides] -> ([String] -> App a) -> App a
startDynamicBackends [[String] -> ServiceOverrides
currentVersionSettings [String
"development"]] (([String] -> App a) -> App a) -> ([String] -> App a) -> App a
forall a b. (a -> b) -> a -> b
$ \[String
stable] -> Maybe String -> App a
runWithStableDomain (String -> Maybe String
forall a. a -> Maybe a
Just String
stable)
else Maybe String -> App a
runWithStableDomain Maybe String
forall a. Maybe a
Nothing
where
runWithStableDomain :: Maybe String -> App a
runWithStableDomain Maybe String
stableDomain = do
localDomain <- HasCallStack => FedVersion -> Maybe String -> App Value
FedVersion -> Maybe String -> App Value
resolveDomain FedVersionPair
pair'.localFedVersion Maybe String
stableDomain
remoteDomain <- resolveDomain pair'.remoteFedVersion stableDomain
action localDomain remoteDomain
resolveDomain :: (HasCallStack) => FedVersion -> Maybe String -> App Value
resolveDomain :: HasCallStack => FedVersion -> Maybe String -> App Value
resolveDomain (FedVersionLegacy Integer
version) Maybe String
_ = AnyFedDomain -> App Value
forall a. (MakesValue a, HasCallStack) => a -> App Value
make (Integer -> AnyFedDomain
AnyFedDomain Integer
version)
resolveDomain FedVersion
FedVersionCurrentStable (Just String
stableDomain) = String -> App Value
forall a. (MakesValue a, HasCallStack) => a -> App Value
make String
stableDomain
resolveDomain FedVersion
FedVersionCurrentDevelopment Maybe String
_ = Domain -> App Value
forall a. (MakesValue a, HasCallStack) => a -> App Value
make Domain
OwnDomain
resolveDomain FedVersion
FedVersionCurrentStable Maybe String
Nothing = String -> App Value
forall a. HasCallStack => String -> a
error String
"stable federation version has no dynamic backend"
currentVersionSettings :: [String] -> ServiceOverrides
currentVersionSettings :: [String] -> ServiceOverrides
currentVersionSettings [String]
disabledVersions =
ServiceOverrides
forall a. Default a => a
def
{ brigCfg = setField "optSettings.setDisabledFederationAPIVersions" disabledVersions,
galleyCfg = setField "settings.disabledFederationAPIVersions" disabledVersions,
cargoholdCfg = setField "settings.disabledFederationAPIVersions" disabledVersions
}
federationTestActionMLS :: Ciphersuite -> Value -> Value -> App ()
federationTestActionMLS :: Ciphersuite -> Value -> Value -> App ()
federationTestActionMLS Ciphersuite
cs Value
localDomain Value
remoteDomain = do
(localAdmin, localTeam, [localMember]) <- Value -> Int -> App (Value, String, [Value])
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> Int -> App (Value, String, [Value])
createTeam Value
localDomain Int
2
(remoteAdmin, _remoteTeam, [remoteMember]) <- createTeam remoteDomain 2
for_ [remoteAdmin, remoteMember] (connectTwoUsers localAdmin)
clients@[localAdmin1, _, remoteAdmin1, _] <- traverse (createMLSClient def {ciphersuites = [cs]}) [localAdmin, localMember, remoteAdmin, remoteMember]
for_ clients (uploadNewKeyPackage cs)
convId <- createNewGroupWith cs localAdmin1 defMLS {team = Just localTeam}
void
$ createAddCommit localAdmin1 convId [localMember, remoteAdmin, remoteMember]
>>= sendAndConsumeCommitBundle
withWebSockets [localMember, remoteAdmin, remoteMember] $ \[WebSocket]
wss -> do
localMessage <- HasCallStack =>
ConvId -> ClientIdentity -> String -> App MessagePackage
ConvId -> ClientIdentity -> String -> App MessagePackage
createApplicationMessage ConvId
convId ClientIdentity
localAdmin1 String
"message from the local team"
postMLSMessage localMessage.sender localMessage.message >>= assertSuccess
for_ wss (awaitMatch (isMessageForConversation convId))
withWebSockets [localAdmin, localMember, remoteMember] $ \[WebSocket]
wss -> do
remoteMessage <- HasCallStack =>
ConvId -> ClientIdentity -> String -> App MessagePackage
ConvId -> ClientIdentity -> String -> App MessagePackage
createApplicationMessage ConvId
convId ClientIdentity
remoteAdmin1 String
"message from the remote team"
postMLSMessage remoteMessage.sender remoteMessage.message >>= assertSuccess
for_ wss (awaitMatch (isMessageForConversation convId))
where
isMessageForConversation :: ConvId -> Value -> App Bool
isMessageForConversation :: ConvId -> Value -> App Bool
isMessageForConversation ConvId
convId' Value
notification =
Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isNewMLSMessageNotif Value
notification App Bool -> App Bool -> App Bool
&&~ ConvId -> Value -> App Bool
forall a.
(HasCallStack, MakesValue a, HasCallStack) =>
ConvId -> a -> App Bool
isNotifConvId ConvId
convId' Value
notification
testNotificationsForOfflineBackends :: (HasCallStack) => App ()
testNotificationsForOfflineBackends :: HasCallStack => App ()
testNotificationsForOfflineBackends = do
resourcePool <- (Env -> ResourcePool BackendResource)
-> App (ResourcePool BackendResource)
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (.resourcePool)
[delUser, otherUser, otherUser2] <- createUsers [OwnDomain, OtherDomain, OtherDomain]
delClient <- objId $ bindResponse (BrigP.addClient delUser def) $ getJSON 201
otherClient <- objId $ bindResponse (BrigP.addClient otherUser def) $ getJSON 201
otherClient2 <- objId $ bindResponse (BrigP.addClient otherUser2 def) $ getJSON 201
runCodensity (acquireResources 1 resourcePool) $ \[BackendResource
downBackend] -> do
(downUser1, downClient1, downUser2, upBackendConv, downBackendConv) <- Codensity App String -> forall b. (String -> App b) -> App b
forall k (m :: k -> *) a.
Codensity m a -> forall (b :: k). (a -> m b) -> m b
runCodensity (HasCallStack =>
BackendResource -> ServiceOverrides -> Codensity App String
BackendResource -> ServiceOverrides -> Codensity App String
startDynamicBackend BackendResource
downBackend ServiceOverrides
forall a. Monoid a => a
mempty) ((String -> App (Value, String, Value, Value, Value))
-> App (Value, String, Value, Value, Value))
-> (String -> App (Value, String, Value, Value, Value))
-> App (Value, String, Value, Value, Value)
forall a b. (a -> b) -> a -> b
$ \String
_ -> do
downUser1 <- String -> CreateUser -> App Value
forall domain.
(HasCallStack, MakesValue domain) =>
domain -> CreateUser -> App Value
randomUser BackendResource
downBackend.berDomain CreateUser
forall a. Default a => a
def
downUser2 <- randomUser downBackend.berDomain def
downClient1 <- objId $ bindResponse (BrigP.addClient downUser1 def) $ getJSON 201
connectTwoUsers delUser otherUser
connectTwoUsers delUser otherUser2
connectTwoUsers delUser downUser1
connectTwoUsers delUser downUser2
connectTwoUsers downUser1 otherUser
upBackendConv <- bindResponse (postConversation delUser (defProteus {qualifiedUsers = [otherUser, otherUser2, downUser1]})) $ getJSON 201
downBackendConv <- bindResponse (postConversation downUser1 (defProteus {qualifiedUsers = [otherUser, delUser]})) $ getJSON 201
pure (downUser1, downClient1, downUser2, upBackendConv, downBackendConv)
withWebSocket otherUser $ \WebSocket
ws -> do
successfulMsgForOtherUsers <- Value -> [(Value, [String])] -> String -> App QualifiedUserEntry
forall domain user client.
(HasCallStack, MakesValue domain, MakesValue user,
MakesValue client) =>
domain -> [(user, [client])] -> String -> App QualifiedUserEntry
mkProteusRecipients Value
otherUser [(Value
otherUser, [String
otherClient]), (Value
otherUser2, [String
otherClient2])] String
"success message for other user"
successfulMsgForDownUser <- mkProteusRecipient downUser1 downClient1 "success message for down user"
let successfulMsg =
forall msg. Message msg => msg
Proto.defMessage @Proto.QualifiedNewOtrMessage
QualifiedNewOtrMessage
-> (QualifiedNewOtrMessage -> QualifiedNewOtrMessage)
-> QualifiedNewOtrMessage
forall a b. a -> (a -> b) -> b
& (ClientId -> Identity ClientId)
-> QualifiedNewOtrMessage -> Identity QualifiedNewOtrMessage
#sender ((ClientId -> Identity ClientId)
-> QualifiedNewOtrMessage -> Identity QualifiedNewOtrMessage)
-> ((Word64 -> Identity Word64) -> ClientId -> Identity ClientId)
-> (Word64 -> Identity Word64)
-> QualifiedNewOtrMessage
-> Identity QualifiedNewOtrMessage
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word64 -> Identity Word64) -> ClientId -> Identity ClientId
forall (f :: * -> *) s a.
(Functor f, HasField s "client" a) =>
LensLike' f s a
Proto.client ((Word64 -> Identity Word64)
-> QualifiedNewOtrMessage -> Identity QualifiedNewOtrMessage)
-> Word64 -> QualifiedNewOtrMessage -> QualifiedNewOtrMessage
forall s t a b. ASetter s t a b -> b -> s -> t
.~ (String
delClient String -> Getting (Endo Word64) String Word64 -> Word64
forall s a. HasCallStack => s -> Getting (Endo a) s a -> a
^?! Getting (Endo Word64) String Word64
forall a. Integral a => Prism' String a
Prism' String Word64
hex)
QualifiedNewOtrMessage
-> (QualifiedNewOtrMessage -> QualifiedNewOtrMessage)
-> QualifiedNewOtrMessage
forall a b. a -> (a -> b) -> b
& ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
[QualifiedUserEntry]
[QualifiedUserEntry]
#recipients ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
[QualifiedUserEntry]
[QualifiedUserEntry]
-> [QualifiedUserEntry]
-> QualifiedNewOtrMessage
-> QualifiedNewOtrMessage
forall s t a b. ASetter s t a b -> b -> s -> t
.~ [QualifiedUserEntry
successfulMsgForOtherUsers, QualifiedUserEntry
successfulMsgForDownUser]
QualifiedNewOtrMessage
-> (QualifiedNewOtrMessage -> QualifiedNewOtrMessage)
-> QualifiedNewOtrMessage
forall a b. a -> (a -> b) -> b
& ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
ClientMismatchStrategy'ReportAll
ClientMismatchStrategy'ReportAll
#reportAll ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
ClientMismatchStrategy'ReportAll
ClientMismatchStrategy'ReportAll
-> ClientMismatchStrategy'ReportAll
-> QualifiedNewOtrMessage
-> QualifiedNewOtrMessage
forall s t a b. ASetter s t a b -> b -> s -> t
.~ ClientMismatchStrategy'ReportAll
forall msg. Message msg => msg
Proto.defMessage
bindResponse (postProteusMessage delUser upBackendConv successfulMsg) assertSuccess
failedMsgForOtherUser <- mkProteusRecipient otherUser otherClient "failed message for other user"
failedMsgForDownUser <- mkProteusRecipient downUser1 downClient1 "failed message for down user"
let failedMsg =
forall msg. Message msg => msg
Proto.defMessage @Proto.QualifiedNewOtrMessage
QualifiedNewOtrMessage
-> (QualifiedNewOtrMessage -> QualifiedNewOtrMessage)
-> QualifiedNewOtrMessage
forall a b. a -> (a -> b) -> b
& (ClientId -> Identity ClientId)
-> QualifiedNewOtrMessage -> Identity QualifiedNewOtrMessage
#sender ((ClientId -> Identity ClientId)
-> QualifiedNewOtrMessage -> Identity QualifiedNewOtrMessage)
-> ((Word64 -> Identity Word64) -> ClientId -> Identity ClientId)
-> (Word64 -> Identity Word64)
-> QualifiedNewOtrMessage
-> Identity QualifiedNewOtrMessage
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word64 -> Identity Word64) -> ClientId -> Identity ClientId
forall (f :: * -> *) s a.
(Functor f, HasField s "client" a) =>
LensLike' f s a
Proto.client ((Word64 -> Identity Word64)
-> QualifiedNewOtrMessage -> Identity QualifiedNewOtrMessage)
-> Word64 -> QualifiedNewOtrMessage -> QualifiedNewOtrMessage
forall s t a b. ASetter s t a b -> b -> s -> t
.~ (String
delClient String -> Getting (Endo Word64) String Word64 -> Word64
forall s a. HasCallStack => s -> Getting (Endo a) s a -> a
^?! Getting (Endo Word64) String Word64
forall a. Integral a => Prism' String a
Prism' String Word64
hex)
QualifiedNewOtrMessage
-> (QualifiedNewOtrMessage -> QualifiedNewOtrMessage)
-> QualifiedNewOtrMessage
forall a b. a -> (a -> b) -> b
& ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
[QualifiedUserEntry]
[QualifiedUserEntry]
#recipients ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
[QualifiedUserEntry]
[QualifiedUserEntry]
-> [QualifiedUserEntry]
-> QualifiedNewOtrMessage
-> QualifiedNewOtrMessage
forall s t a b. ASetter s t a b -> b -> s -> t
.~ [QualifiedUserEntry
failedMsgForOtherUser, QualifiedUserEntry
failedMsgForDownUser]
QualifiedNewOtrMessage
-> (QualifiedNewOtrMessage -> QualifiedNewOtrMessage)
-> QualifiedNewOtrMessage
forall a b. a -> (a -> b) -> b
& ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
ClientMismatchStrategy'ReportAll
ClientMismatchStrategy'ReportAll
#reportAll ASetter
QualifiedNewOtrMessage
QualifiedNewOtrMessage
ClientMismatchStrategy'ReportAll
ClientMismatchStrategy'ReportAll
-> ClientMismatchStrategy'ReportAll
-> QualifiedNewOtrMessage
-> QualifiedNewOtrMessage
forall s t a b. ASetter s t a b -> b -> s -> t
.~ ClientMismatchStrategy'ReportAll
forall msg. Message msg => msg
Proto.defMessage
bindResponse (postProteusMessage delUser downBackendConv failedMsg) $ \Response
resp ->
Response
resp.status Int -> [Value] -> App ()
forall a b.
(MakesValue a, MakesValue b, HasCallStack) =>
a -> b -> App ()
`shouldMatchOneOf` [Scientific -> Value
Number Scientific
521, Scientific -> Value
Number Scientific
533]
bindResponse (postConversation delUser (defProteus {qualifiedUsers = [otherUser, downUser1]})) $ \Response
resp ->
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
533
otherUser3 <- randomUser OtherDomain def
connectTwoUsers delUser otherUser3
bindResponse (addMembers delUser upBackendConv def {users = [otherUser3]}) $ \Response
resp ->
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (addMembers delUser upBackendConv def {users = [downUser2]}) $ \Response
resp ->
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
533
bindResponse (removeMember delUser upBackendConv otherUser2) $ \Response
resp ->
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
bindResponse (removeMember delUser upBackendConv delUser) $ \Response
resp ->
Response
resp.status Int -> Int -> App ()
forall a. (MakesValue a, HasCallStack) => a -> Int -> App ()
`shouldMatchInt` Int
200
deleteUser delUser
let isOtherUser2LeaveUpConvNotif = [Value -> App Bool] -> Value -> App Bool
forall (f :: * -> *) a.
Applicative f =>
[a -> f Bool] -> a -> f Bool
allPreds [Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvLeaveNotif, Value -> Value -> App Bool
forall conv a.
(HasCallStack, MakesValue conv, MakesValue a, HasCallStack) =>
conv -> a -> App Bool
isNotifConv Value
upBackendConv, Value -> Value -> App Bool
forall conv a.
(HasCallStack, MakesValue conv, MakesValue a, HasCallStack) =>
conv -> a -> App Bool
isNotifForUser Value
otherUser2]
isDelUserLeaveUpConvNotif = [Value -> App Bool] -> Value -> App Bool
forall (f :: * -> *) a.
Applicative f =>
[a -> f Bool] -> a -> f Bool
allPreds [Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvLeaveNotif, Value -> Value -> App Bool
forall conv a.
(HasCallStack, MakesValue conv, MakesValue a, HasCallStack) =>
conv -> a -> App Bool
isNotifConv Value
upBackendConv, Value -> Value -> App Bool
forall conv a.
(HasCallStack, MakesValue conv, MakesValue a, HasCallStack) =>
conv -> a -> App Bool
isNotifForUser Value
delUser]
do
newMsgNotif <- awaitMatch isNewMessageNotif ws
newMsgNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv
newMsgNotif %. "payload.0.data.text" `shouldMatchBase64` fromString "success message for other user"
void $ awaitMatch isOtherUser2LeaveUpConvNotif ws
void $ awaitMatch isDelUserLeaveUpConvNotif ws
delUserDeletedNotif <- nPayload $ awaitMatch isDeleteUserNotif ws
objQid delUserDeletedNotif `shouldMatch` objQid delUser
runCodensity (startDynamicBackend downBackend mempty) $ \String
_ -> do
newMsgNotif <- Value -> String -> Maybe Value -> (Value -> App Bool) -> App Value
forall user client lastNotifId.
(HasCallStack, MakesValue user, MakesValue client,
MakesValue lastNotifId) =>
user
-> client -> Maybe lastNotifId -> (Value -> App Bool) -> App Value
awaitNotificationClient Value
downUser1 String
downClient1 Maybe Value
noValue Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isNewMessageNotif
newMsgNotif %. "payload.0.qualified_conversation" `shouldMatch` objQidObject upBackendConv
newMsgNotif %. "payload.0.data.text" `shouldMatchBase64` fromString "success message for down user"
let isDelUserLeaveDownConvNotif =
[Value -> App Bool] -> Value -> App Bool
forall (f :: * -> *) a.
Applicative f =>
[a -> f Bool] -> a -> f Bool
allPreds
[ Value -> App Bool
forall a. (HasCallStack, MakesValue a) => a -> App Bool
isConvLeaveNotif,
Value -> Value -> App Bool
forall conv a.
(HasCallStack, MakesValue conv, MakesValue a, HasCallStack) =>
conv -> a -> App Bool
isNotifConv Value
downBackendConv,
Value -> Value -> App Bool
forall conv a.
(HasCallStack, MakesValue conv, MakesValue a, HasCallStack) =>
conv -> a -> App Bool
isNotifForUser Value
delUser
]
void $ awaitNotificationClient downUser1 (Just downClient1) (Just newMsgNotif) isDelUserLeaveDownConvNotif
delUserDeletedNotif <- nPayload $ awaitNotificationClient downUser1 downClient1 (Just newMsgNotif) isDeleteUserNotif
objQid delUserDeletedNotif `shouldMatch` objQid delUser