Skip to content
4 changes: 4 additions & 0 deletions charts/brig/templates/tests/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ data:
host: brig.{{ .Release.Namespace }}-fed2.svc.cluster.local
port: 8080

galley:
host: galley.{{ .Release.Namespace }}-fed2.svc.cluster.local
port: 8080

# TODO remove this
federator:
host: federator.{{ .Release.Namespace }}-fed2.svc.cluster.local
Expand Down
1 change: 1 addition & 0 deletions libs/wire-api/src/Wire/API/Conversation/Member.hs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ modelConversationMembers = Doc.defineModel "ConversationMembers" $ do
--------------------------------------------------------------------------------
-- Members

-- FUTUREWORK: Add a qualified Id here.
data Member = Member
{ memId :: UserId,
memService :: Maybe ServiceRef,
Expand Down
39 changes: 29 additions & 10 deletions services/brig/test/integration/Federation/End2end.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module Federation.End2end where
import API.Search.Util
import API.User.Util (getUserClientsQualified)
import Bilge
import Bilge.Assert ((!!!), (===))
import Bilge.Assert ((!!!), (<!!), (===))
import Brig.API.Client (pubClient)
import qualified Brig.Options as BrigOpts
import Brig.Types
Expand All @@ -35,13 +35,13 @@ import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.Map as Map
import Data.Qualified
import qualified Data.Set as Set
import Federation.Util (generateClientPrekeys)
import Federation.Util (generateClientPrekeys, getConvQualified)
import Imports
import Test.Tasty
import Test.Tasty.HUnit
import Util
import Util.Options (Endpoint)
import Wire.API.Conversation (InviteQualified (..), NewConv (..), NewConvUnmanaged (..), cnvId)
import Wire.API.Conversation
import Wire.API.Conversation.Role (roleNameWireAdmin)
import Wire.API.Message (UserClients (UserClients))
import Wire.API.User (ListUsersQuery (ListUsersByIds))
Expand All @@ -59,8 +59,8 @@ import Wire.API.User.Client (QualifiedUserClients (..), mkQualifiedUserClientPre
-- - Remote discovery succeeds but server doesn't exist
-- - Remote federator fails to respond in many ways (protocol error, timeout, etc.)
-- - SRV record has two servers but higher priority one always fails
spec :: BrigOpts.Opts -> Manager -> Brig -> Galley -> Endpoint -> Brig -> IO TestTree
spec _brigOpts mg brig galley _federator brigTwo =
spec :: BrigOpts.Opts -> Manager -> Brig -> Galley -> Endpoint -> Brig -> Galley -> IO TestTree
spec _brigOpts mg brig galley _federator brigTwo galleyTwo =
pure $
testGroup
"federation-end2end-user"
Expand All @@ -71,7 +71,7 @@ spec _brigOpts mg brig galley _federator brigTwo =
test mg "claim prekey bundle" $ testClaimPrekeyBundleSuccess brig brigTwo,
test mg "claim multi-prekey bundle" $ testClaimMultiPrekeyBundleSuccess brig brigTwo,
test mg "list user clients" $ testListUserClients brig brigTwo,
test mg "add remote users to local conversation" $ testAddRemoteUsersToLocalConv brig galley brigTwo
test mg "add remote users to local conversation" $ testAddRemoteUsersToLocalConv brig galley brigTwo galleyTwo
]

-- | Path covered by this test:
Expand Down Expand Up @@ -210,12 +210,12 @@ testClaimMultiPrekeyBundleSuccess brig1 brig2 = do
const 200 === statusCode
const (Just ucm) === responseJsonMaybe

testAddRemoteUsersToLocalConv :: Brig -> Galley -> Brig -> Http ()
testAddRemoteUsersToLocalConv brig1 galley1 brig2 = do
testAddRemoteUsersToLocalConv :: Brig -> Galley -> Brig -> Galley -> Http ()
testAddRemoteUsersToLocalConv brig1 galley1 brig2 galley2 = do
alice <- randomUser brig1
bob <- randomUser brig2

let conv = NewConvUnmanaged $ NewConv [] [] (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin
let newConv = NewConvUnmanaged $ NewConv [] [] (Just "gossip") mempty Nothing Nothing Nothing Nothing roleNameWireAdmin
convId <-
cnvId . responseJsonUnsafe
<$> post
Expand All @@ -224,9 +224,13 @@ testAddRemoteUsersToLocalConv brig1 galley1 brig2 = do
. zUser (userId alice)
. zConn "conn"
. header "Z-Type" "access"
. json conv
. json newConv
)

let backend1Domain = qDomain (userQualifiedId alice)
-- FUTUREWORK add qualified conversation Id to Conversation data type, then use that from the conversation creation response
qualifiedConvId = Qualified convId backend1Domain

let invite = InviteQualified (userQualifiedId bob :| []) roleNameWireAdmin
post
( galley1
Expand All @@ -238,6 +242,21 @@ testAddRemoteUsersToLocalConv brig1 galley1 brig2 = do
)
!!! (const 200 === statusCode)

-- test GET /conversations/:backend1Domain/:cnv
liftIO $ putStrLn "search for conversation on backend 1..."
res <- getConvQualified galley1 (userId alice) qualifiedConvId <!! (const 200 === statusCode)
let conv = responseJsonUnsafeWithMsg ("backend 1 - get /conversations/domain/cnvId") res
actual = cmOthers $ cnvMembers conv
expected = [OtherMember (userQualifiedId bob) Nothing roleNameWireAdmin]
liftIO $ actual @?= expected

liftIO $ putStrLn "search for conversation on backend 2..."
res' <- getConvQualified galley2 (userId bob) qualifiedConvId <!! (const 200 === statusCode)
let conv' = responseJsonUnsafeWithMsg ("backend 2 - get /conversations/domain/cnvId") res'
actual' = cmOthers $ cnvMembers conv'
expected' = [OtherMember (userQualifiedId alice) Nothing roleNameWireAdmin]
liftIO $ actual' @?= expected'

testListUserClients :: Brig -> Brig -> Http ()
testListUserClients brig1 brig2 = do
alice <- randomUser brig1
Expand Down
16 changes: 15 additions & 1 deletion services/brig/test/integration/Federation/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
module Federation.Util where

import Bilge
import Bilge.Assert ((!!!), (<!!), (===))
import qualified Brig.Options as Opt
import Brig.Types
import qualified Control.Concurrent.Async as Async
Expand All @@ -34,11 +35,12 @@ import Control.Monad.Trans.Except
import Control.Retry
import Data.Aeson (FromJSON, Value, decode, (.=))
import qualified Data.Aeson as Aeson
import Data.ByteString.Conversion (toByteString')
import Data.Domain (Domain (Domain))
import Data.Handle (fromHandle)
import Data.Id
import qualified Data.Map.Strict as Map
import Data.Qualified (Qualified (qDomain, qUnqualified))
import Data.Qualified (Qualified (..))
import Data.String.Conversions (cs)
import qualified Data.Text as Text
import qualified Database.Bloodhound as ES
Expand All @@ -61,6 +63,9 @@ import Text.RawString.QQ (r)
import UnliftIO (Concurrently (..), runConcurrently)
import Util
import Util.Options (Endpoint (Endpoint))
import Wire.API.Conversation (Conversation (cnvMembers))
import Wire.API.Conversation.Member (OtherMember (OtherMember), cmOthers)
import Wire.API.Conversation.Role (roleNameWireAdmin)
import Wire.API.Federation.GRPC.Types (FederatedRequest, Outward, OutwardResponse (..))
import qualified Wire.API.Federation.Mock as Mock
import Wire.API.Team.Feature (TeamFeatureStatusValue (..))
Expand Down Expand Up @@ -108,3 +113,12 @@ assertRight = \case

assertRightT :: (MonadIO m, Show a, HasCallStack) => ExceptT a m b -> m b
assertRightT = assertRight <=< runExceptT

getConvQualified :: Galley -> UserId -> Qualified ConvId -> Http ResponseLBS
getConvQualified g u (Qualified cnvId domain) =
get $
g
. paths ["conversations", toByteString' domain, toByteString' cnvId]
. zUser u
. zConn "conn"
. header "Z-Type" "access"
5 changes: 4 additions & 1 deletion services/brig/test/integration/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import Util.Test

data BackendConf = BackendConf
{ remoteBrig :: Endpoint,
remoteGalley :: Endpoint,
remoteFederatorInternal :: Endpoint,
remoteFederatorExternal :: Endpoint
}
Expand All @@ -72,6 +73,7 @@ instance FromJSON BackendConf where
parseJSON = withObject "BackendConf" $ \o ->
BackendConf
<$> o .: "brig"
<*> o .: "galley"
<*> o .: "federatorInternal"
<*> o .: "federatorExternal"

Expand Down Expand Up @@ -105,6 +107,7 @@ runTests iConf brigOpts otherArgs = do
s = mkRequest $ spar iConf
f = federatorInternal iConf
brigTwo = mkRequest $ remoteBrig (backendTwo iConf)
galleyTwo = mkRequest $ remoteGalley (backendTwo iConf)

let turnFile = Opts.servers . Opts.turn $ brigOpts
turnFileV2 = (Opts.serversV2 . Opts.turn) brigOpts
Expand All @@ -129,7 +132,7 @@ runTests iConf brigOpts otherArgs = do
createIndex <- Index.Create.spec brigOpts
browseTeam <- TeamUserSearch.tests brigOpts mg g b
userPendingActivation <- UserPendingActivation.tests brigOpts mg db b g s
federationEnd2End <- Federation.End2end.spec brigOpts mg b g f brigTwo
federationEnd2End <- Federation.End2end.spec brigOpts mg b g f brigTwo galleyTwo
federationEndpoints <- API.Federation.tests mg b fedBrigClient
includeFederationTests <- (== Just "1") <$> Blank.getEnv "INTEGRATION_FEDERATION_TESTS"
internalApi <- API.Internal.tests brigOpts mg b (brig iConf) gd
Expand Down
3 changes: 2 additions & 1 deletion services/galley/galley.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 63f50e23b5853d8dd4b8b87b8588dc8499783ec5e7e72a181b0ccb399089a6ed
-- hash: 9fb2b9ba716dce4ffe27891b8718f10b22ca443bf73c84744b150c53aae5a5c1

name: galley
version: 0.83.0
Expand Down Expand Up @@ -377,6 +377,7 @@ test-suite galley-types-tests
other-modules:
Test.Galley.API
Test.Galley.Intra.User
Test.Galley.Mapping
Test.Galley.Roundtrip
Paths_galley
hs-source-dirs:
Expand Down
9 changes: 4 additions & 5 deletions services/galley/src/Galley/API/Federation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,11 @@ federationSitemap =
}

getConversations :: GetConversationsRequest -> Galley GetConversationsResponse
getConversations (GetConversationsRequest (Qualified uid domain) gcrConvIds) = do
localDomain <- viewFederationDomain
getConversations (GetConversationsRequest qUid gcrConvIds) = do
domain <- viewFederationDomain
convs <- Data.conversations gcrConvIds
if domain == localDomain
then GetConversationsResponse . catMaybes <$> for convs (Mapping.conversationViewMaybe uid)
else error "FUTUREWORK: implement & exstend integration test when schema ready"
let convViews = Mapping.conversationViewMaybeQualified domain qUid <$> convs
pure $ GetConversationsResponse . catMaybes $ convViews

-- FUTUREWORK: also remove users from conversation
updateConversationMemberships :: ConversationMemberUpdate -> Galley ()
Expand Down
48 changes: 39 additions & 9 deletions services/galley/src/Galley/API/Mapping.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Control.Monad.Catch
import Data.Domain (Domain)
import Data.Id (UserId, idToText)
import qualified Data.List as List
import Data.Qualified (Qualified (Qualified))
import Data.Qualified (Qualified (..))
import Data.Tagged (unTagged)
import Galley.API.Util (viewFederationDomain)
import Galley.App
Expand Down Expand Up @@ -53,17 +53,30 @@ conversationView uid conv = do
throwM badState
badState = mkError status500 "bad-state" "Bad internal member state."

conversationViewMaybe :: UserId -> Data.Conversation -> Galley (Maybe Public.Conversation)
conversationViewMaybe u conv = do
localDomain <- viewFederationDomain
pure $ conversationViewMaybeQualified localDomain (Qualified u localDomain) conv

-- | View for a given user of a stored conversation.
-- Returns 'Nothing' when the user is not part of the conversation.
conversationViewMaybe :: UserId -> Data.Conversation -> Galley (Maybe Public.Conversation)
conversationViewMaybe u Data.Conversation {..} = do
domain <- viewFederationDomain
let (me, localThem) = List.partition ((u ==) . Internal.memId) convLocalMembers
let localMembers = localToOther domain <$> localThem
conversationViewMaybeQualified :: Domain -> Qualified UserId -> Data.Conversation -> Maybe Public.Conversation
conversationViewMaybeQualified localDomain qUid Data.Conversation {..} = do
let localMembers = localToOther localDomain <$> convLocalMembers
let remoteMembers = remoteToOther <$> convRemoteMembers
for (listToMaybe me) $ \m -> do
let mems = Public.ConvMembers (toMember m) (localMembers <> remoteMembers)
return $! Public.Conversation convId convType convCreator convAccess convAccessRole convName mems convTeam convMessageTimer convReceiptMode
let me = List.find ((qUid ==) . Public.omQualifiedId) (localMembers <> remoteMembers)
let otherMembers = filter ((qUid /=) . Public.omQualifiedId) (localMembers <> remoteMembers)
let userAndConvOnSameBackend = find ((qUnqualified qUid ==) . Internal.memId) convLocalMembers
let selfMember =
-- if the user and the conversation are on the same backend, we can create a real self member
-- otherwise, we need to fall back to a default self member (see futurework)
-- (Note: the extra domain check is done to catch the edge case where two users in a conversation have the same unqualified UUID)
if isJust userAndConvOnSameBackend && localDomain == qDomain qUid
then toMember <$> userAndConvOnSameBackend
else incompleteSelfMember <$> me
selfMember <&> \m -> do
let mems = Public.ConvMembers m otherMembers
Public.Conversation convId convType convCreator convAccess convAccessRole convName mems convTeam convMessageTimer convReceiptMode
where
localToOther :: Domain -> Internal.LocalMember -> Public.OtherMember
localToOther domain x =
Expand All @@ -81,6 +94,23 @@ conversationViewMaybe u Data.Conversation {..} = do
Public.omConvRoleName = Internal.rmConvRoleName x
}

-- FUTUREWORK(federation): we currently don't store muted, archived etc status for users who are on a different backend than a conversation
-- but we should. Once this information is available, the code should be changed to use the stored information, rather than these defaults.
incompleteSelfMember :: Public.OtherMember -> Public.Member
incompleteSelfMember m =
Public.Member
{ memId = qUnqualified (Public.omQualifiedId m),
memService = Nothing,
memOtrMuted = False,
memOtrMutedStatus = Nothing,
memOtrMutedRef = Nothing,
memOtrArchived = False,
memOtrArchivedRef = Nothing,
memHidden = False,
memHiddenRef = Nothing,
memConvRoleName = Public.omConvRoleName m
}

toMember :: Internal.LocalMember -> Public.Member
toMember x@Internal.InternalMember {..} =
Public.Member {memId = Internal.memId x, ..}
4 changes: 2 additions & 2 deletions services/galley/test/integration/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1029,11 +1029,11 @@ testAddRemoteMember = do
]

e <- responseJsonUnsafe <$> (pure resp <!! const 200 === statusCode)
let bobMember = SimpleMember remoteBob roleNameWireAdmin
liftIO $ do
evtConv e @?= qconvId
evtType e @?= MemberJoin
-- FUTUREWORK: implement returning remote users in the event.
-- evtData e @?= Just (EdMembersJoin (SimpleMembers [remoteBob]))
evtData e @?= EdMembersJoin (SimpleMembers [bobMember])
evtFrom e @?= qalice
conv <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified alice qconvId
liftIO $ do
Expand Down
2 changes: 2 additions & 0 deletions services/galley/test/unit/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ where
import Imports
import qualified Test.Galley.API
import qualified Test.Galley.Intra.User
import qualified Test.Galley.Mapping
import qualified Test.Galley.Roundtrip
import Test.Tasty

Expand All @@ -32,5 +33,6 @@ main =
=<< sequence
[ pure Test.Galley.API.tests,
pure Test.Galley.Intra.User.tests,
pure Test.Galley.Mapping.tests,
Test.Galley.Roundtrip.tests
]
Loading