Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
85e911a
Unify updateLocalConversation
pcapriotti May 10, 2022
1df9842
Replace pair with record type
pcapriotti May 10, 2022
3f28696
Add RPC for remote MLS messaging
pcapriotti May 12, 2022
ac8d52a
Allow remote conversations in mls-message endpoint
pcapriotti May 13, 2022
1a01a34
Replace Text error with MLSProtocolError
pcapriotti May 13, 2022
f78d4c8
Ormolu
pcapriotti May 30, 2022
6642ef9
WIP: integration test
pcapriotti May 31, 2022
8893ff2
Add protocol field to conv created RPC
pcapriotti May 31, 2022
3be4548
Create MLS message in integration test
pcapriotti May 31, 2022
8770873
Set group ID mapping for remote conversations
pcapriotti May 31, 2022
6df1fa5
Fix mock in remote mls message test
pcapriotti Jun 1, 2022
d68a07e
Fix build and remote MLS message test
pcapriotti Jun 21, 2022
a77d75d
Update golden tests
pcapriotti Jun 22, 2022
332e5ee
Add federation test
pcapriotti Jun 22, 2022
ac37c1a
Add reply to mls messaging end-to-end test
pcapriotti Jun 22, 2022
5187eb6
Add CHANGELOG entry
pcapriotti Jun 24, 2022
4587ab0
Adapt to changes in MLS test tool
pcapriotti Jun 27, 2022
c7eda2c
Do not register conversation in integration test
pcapriotti Jun 28, 2022
0329a78
Pass conversation to notifyConversationAction
pcapriotti Jun 29, 2022
e4ffc30
Add optional metadata to on-conversation-update
pcapriotti Jun 30, 2022
ad3dfaa
Typo
pcapriotti Jul 1, 2022
fbed20a
Revert "Add optional metadata to on-conversation-update"
pcapriotti Jul 1, 2022
67d0580
Rename NewRemoteConversation → ConversationCreated
pcapriotti Jul 1, 2022
1fe9c06
Add on-new-remote-conversation RPC stub
pcapriotti Jul 1, 2022
3a5624a
Implement on-new-remote-conversation
pcapriotti Jul 1, 2022
f41ce4f
wip
stefanwire Jul 1, 2022
b8a8624
Clean up imports
pcapriotti Jul 4, 2022
507e688
Ormolu
pcapriotti Jul 4, 2022
5791d0a
Fix build
pcapriotti Jul 4, 2022
133a504
Fix ConversationCreated golden tests
pcapriotti Jul 4, 2022
c148380
Fix integration test
pcapriotti Jul 4, 2022
32c91e9
Fix mock in integration test
pcapriotti Jul 5, 2022
8852702
Fix on-conversation-updated assertions
pcapriotti Jul 5, 2022
d235f94
Add CHANGELOG entry about new-remote-conversation
pcapriotti Jul 6, 2022
1571e14
fix typo
stefanwire Jul 7, 2022
40927bf
Add check with FUTUREWORK
smatting Jul 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/6-federation/new-remote-conversation
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added new-remote-conversation RPC, used to notify a backend of a remote conversation the first time any user from that backend is added to it.
1 change: 1 addition & 0 deletions changelog.d/6-federation/remote-mls-messages
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added federation endpoint `send-mls-message` used to send messages to remote converesations
4 changes: 4 additions & 0 deletions charts/brig/templates/tests/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ data:
host: cargohold.{{ .Release.Namespace }}-fed2.svc.cluster.local
port: 8080

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

# TODO remove this
federator:
host: federator.{{ .Release.Namespace }}-fed2.svc.cluster.local
Expand Down
4 changes: 2 additions & 2 deletions libs/wai-utilities/src/Network/Wai/Utilities/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ data Error = Error
message :: !LText,
errorData :: Maybe ErrorData
}
deriving (Show, Typeable)
deriving (Eq, Show, Typeable)

mkError :: Status -> LText -> LText -> Error
mkError c l m = Error c l m Nothing
Expand All @@ -52,7 +52,7 @@ data ErrorData = FederationErrorData
{ federrDomain :: !Domain,
federrPath :: !Text
}
deriving (Show, Typeable)
deriving (Eq, Show, Typeable)

instance ToJSON ErrorData where
toJSON (FederationErrorData d p) =
Expand Down
58 changes: 42 additions & 16 deletions libs/wire-api-federation/src/Wire/API/Federation/API/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import Data.Qualified
import Data.Range
import Data.Time.Clock (UTCTime)
import Imports
import qualified Network.Wai.Utilities.Error as Wai
import Servant.API
import Wire.API.Arbitrary (Arbitrary, GenericUniform (..))
import Wire.API.Conversation
Expand All @@ -44,8 +45,14 @@ import Wire.API.Util.Aeson (CustomEncoded (..))

-- | For conventions see /docs/developer/federation-api-conventions.md
type GalleyApi =
-- | Register a new conversation
FedEndpoint "on-conversation-created" (NewRemoteConversation ConvId) ()
-- | Register a new conversation. This is only called on backends of users
-- that are part of a conversation at creation time. Since MLS conversations
-- are always created empty (i.e. they only contain the creator), this RPC is
-- never invoked for such conversations.
FedEndpoint "on-conversation-created" (ConversationCreated ConvId) ()
-- This endpoint is called the first time a user from this backend is
-- added to a remote conversation.
:<|> FedEndpoint "on-new-remote-conversation" NewRemoteConversation EmptyResponse
:<|> FedEndpoint "get-conversations" GetConversationsRequest GetConversationsResponse
-- used by the backend that owns a conversation to inform this backend of
-- changes to the conversation
Expand All @@ -61,6 +68,7 @@ type GalleyApi =
:<|> FedEndpoint "update-conversation" ConversationUpdateRequest ConversationUpdateResponse
:<|> FedEndpoint "mls-welcome" MLSWelcomeRequest EmptyResponse
:<|> FedEndpoint "on-mls-message-sent" RemoteMLSMessage EmptyResponse
:<|> FedEndpoint "send-mls-message" MessageSendRequest MLSMessageResponse

data GetConversationsRequest = GetConversationsRequest
{ gcrUserId :: UserId,
Expand Down Expand Up @@ -105,31 +113,41 @@ newtype GetConversationsResponse = GetConversationsResponse
--
-- FUTUREWORK: Think about extracting common conversation metadata into a
-- separarate data type that can be reused in several data types in this module.
data NewRemoteConversation conv = NewRemoteConversation
data ConversationCreated conv = ConversationCreated
{ -- | The time when the conversation was created
rcTime :: UTCTime,
ccTime :: UTCTime,
-- | The user that created the conversation. This is implicitly qualified
-- by the requesting domain, since it is impossible to create a regular/group
-- conversation on a remote backend.
rcOrigUserId :: UserId,
ccOrigUserId :: UserId,
-- | The conversation ID, local to the backend invoking the RPC
rcCnvId :: conv,
ccCnvId :: conv,
-- | The conversation type
rcCnvType :: ConvType,
rcCnvAccess :: [Access],
rcCnvAccessRoles :: Set AccessRoleV2,
ccCnvType :: ConvType,
ccCnvAccess :: [Access],
ccCnvAccessRoles :: Set AccessRoleV2,
-- | The conversation name,
rcCnvName :: Maybe Text,
ccCnvName :: Maybe Text,
-- | Members of the conversation apart from the creator
rcNonCreatorMembers :: Set OtherMember,
rcMessageTimer :: Maybe Milliseconds,
rcReceiptMode :: Maybe ReceiptMode
ccNonCreatorMembers :: Set OtherMember,
ccMessageTimer :: Maybe Milliseconds,
ccReceiptMode :: Maybe ReceiptMode,
ccProtocol :: Protocol
}
deriving stock (Eq, Show, Generic, Functor)
deriving (ToJSON, FromJSON) via (CustomEncoded (NewRemoteConversation conv))
deriving (ToJSON, FromJSON) via (CustomEncoded (ConversationCreated conv))

rcRemoteOrigUserId :: NewRemoteConversation (Remote ConvId) -> Remote UserId
rcRemoteOrigUserId rc = qualifyAs (rcCnvId rc) (rcOrigUserId rc)
ccRemoteOrigUserId :: ConversationCreated (Remote ConvId) -> Remote UserId
ccRemoteOrigUserId cc = qualifyAs (ccCnvId cc) (ccOrigUserId cc)

data NewRemoteConversation = NewRemoteConversation
{ -- | The conversation ID, local to the backend invoking the RPC.
nrcConvId :: ConvId,
-- | The conversation protocol.
nrcProtocol :: Protocol
}
deriving stock (Eq, Show, Generic)
deriving (ToJSON, FromJSON) via (CustomEncoded NewRemoteConversation)

data ConversationUpdate = ConversationUpdate
{ cuTime :: UTCTime,
Expand Down Expand Up @@ -275,3 +293,11 @@ newtype MLSWelcomeRequest = MLSWelcomeRequest
deriving stock (Eq, Generic, Show)
deriving (Arbitrary) via (GenericUniform MLSWelcomeRequest)
deriving (FromJSON, ToJSON) via (CustomEncoded MLSWelcomeRequest)

data MLSMessageResponse
= MLSMessageResponseError GalleyError
| MLSMessageResponseProtocolError Text
| MLSMessageResponseProposalFailure Wai.Error
| MLSMessageResponseUpdates [ConversationUpdate]
deriving stock (Eq, Show, Generic)
deriving (ToJSON, FromJSON) via (CustomEncoded MLSMessageResponse)
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
-- You should have received a copy of the GNU Affero General Public License along
-- with this program. If not, see <https://www.gnu.org/licenses/>.

module Test.Wire.API.Federation.Golden.NewRemoteConversation where
module Test.Wire.API.Federation.Golden.ConversationCreated where

import Data.Domain
import Data.Id
Expand All @@ -25,21 +25,22 @@ import qualified Data.Set as Set
import qualified Data.UUID as UUID
import Imports
import Wire.API.Conversation
import Wire.API.Conversation.Protocol
import Wire.API.Conversation.Role
import Wire.API.Federation.API.Galley
import Wire.API.Provider.Service

testObject_NewRemoteConversation1 :: NewRemoteConversation ConvId
testObject_NewRemoteConversation1 =
NewRemoteConversation
{ rcTime = read "1864-04-12 12:22:43.673 UTC",
rcOrigUserId = Id (fromJust (UUID.fromString "eed9dea3-5468-45f8-b562-7ad5de2587d0")),
rcCnvId = Id (fromJust (UUID.fromString "d13dbe58-d4e3-450f-9c0c-1e632f548740")),
rcCnvType = RegularConv,
rcCnvAccess = [InviteAccess, CodeAccess],
rcCnvAccessRoles = Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole],
rcCnvName = Just "gossip",
rcNonCreatorMembers =
testObject_ConversationCreated1 :: ConversationCreated ConvId
testObject_ConversationCreated1 =
ConversationCreated
{ ccTime = read "1864-04-12 12:22:43.673 UTC",
ccOrigUserId = Id (fromJust (UUID.fromString "eed9dea3-5468-45f8-b562-7ad5de2587d0")),
ccCnvId = Id (fromJust (UUID.fromString "d13dbe58-d4e3-450f-9c0c-1e632f548740")),
ccCnvType = RegularConv,
ccCnvAccess = [InviteAccess, CodeAccess],
ccCnvAccessRoles = Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole],
ccCnvName = Just "gossip",
ccNonCreatorMembers =
Set.fromList
[ OtherMember
{ omQualifiedId =
Expand All @@ -64,21 +65,23 @@ testObject_NewRemoteConversation1 =
omConvRoleName = roleNameWireMember
}
],
rcMessageTimer = Just (Ms 1000),
rcReceiptMode = Just (ReceiptMode 42)
ccMessageTimer = Just (Ms 1000),
ccReceiptMode = Just (ReceiptMode 42),
ccProtocol = ProtocolProteus
}

testObject_NewRemoteConversation2 :: NewRemoteConversation ConvId
testObject_NewRemoteConversation2 =
NewRemoteConversation
{ rcTime = read "1864-04-12 12:22:43.673 UTC",
rcOrigUserId = Id (fromJust (UUID.fromString "eed9dea3-5468-45f8-b562-7ad5de2587d0")),
rcCnvId = Id (fromJust (UUID.fromString "d13dbe58-d4e3-450f-9c0c-1e632f548740")),
rcCnvType = One2OneConv,
rcCnvAccess = [],
rcCnvAccessRoles = Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole],
rcCnvName = Nothing,
rcNonCreatorMembers = Set.fromList [],
rcMessageTimer = Nothing,
rcReceiptMode = Nothing
testObject_ConversationCreated2 :: ConversationCreated ConvId
testObject_ConversationCreated2 =
ConversationCreated
{ ccTime = read "1864-04-12 12:22:43.673 UTC",
ccOrigUserId = Id (fromJust (UUID.fromString "eed9dea3-5468-45f8-b562-7ad5de2587d0")),
ccCnvId = Id (fromJust (UUID.fromString "d13dbe58-d4e3-450f-9c0c-1e632f548740")),
ccCnvType = One2OneConv,
ccCnvAccess = [],
ccCnvAccessRoles = Set.fromList [TeamMemberAccessRole, NonTeamMemberAccessRole],
ccCnvName = Nothing,
ccNonCreatorMembers = Set.fromList [],
ccMessageTimer = Nothing,
ccReceiptMode = Nothing,
ccProtocol = ProtocolMLS (ConversationMLSData (GroupId "group") (Epoch 3))
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ module Test.Wire.API.Federation.Golden.GoldenSpec where

import Imports
import Test.Hspec
import qualified Test.Wire.API.Federation.Golden.ConversationCreated as ConversationCreated
import qualified Test.Wire.API.Federation.Golden.ConversationUpdate as ConversationUpdate
import qualified Test.Wire.API.Federation.Golden.LeaveConversationRequest as LeaveConversationRequest
import qualified Test.Wire.API.Federation.Golden.LeaveConversationResponse as LeaveConversationResponse
import qualified Test.Wire.API.Federation.Golden.MessageSendResponse as MessageSendResponse
import qualified Test.Wire.API.Federation.Golden.NewConnectionRequest as NewConnectionRequest
import qualified Test.Wire.API.Federation.Golden.NewConnectionResponse as NewConnectionResponse
import qualified Test.Wire.API.Federation.Golden.NewRemoteConversation as NewRemoteConversation
import Test.Wire.API.Federation.Golden.Runner (testObjects)

spec :: Spec
Expand Down Expand Up @@ -60,6 +60,6 @@ spec =
(NewConnectionResponse.testObject_NewConnectionResponse4, "testObject_NewConnectionResponse4.json")
]
testObjects
[ (NewRemoteConversation.testObject_NewRemoteConversation1, "testObject_NewRemoteConversation1.json"),
(NewRemoteConversation.testObject_NewRemoteConversation2, "testObject_NewRemoteConversation2.json")
[ (ConversationCreated.testObject_ConversationCreated1, "testObject_ConversationCreated1.json"),
(ConversationCreated.testObject_ConversationCreated2, "testObject_ConversationCreated2.json")
]
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
}
],
"orig_user_id": "eed9dea3-5468-45f8-b562-7ad5de2587d0",
"protocol": {
"protocol": "proteus"
},
"receipt_mode": 42,
"time": "1864-04-12T12:22:43.673Z"
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"message_timer": null,
"non_creator_members": [],
"orig_user_id": "eed9dea3-5468-45f8-b562-7ad5de2587d0",
"protocol": {
"epoch": 3,
"group_id": "Z3JvdXA=",
"protocol": "mls"
},
"receipt_mode": null,
"time": "1864-04-12T12:22:43.673Z"
}
2 changes: 1 addition & 1 deletion libs/wire-api-federation/wire-api-federation.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ test-suite spec
main-is: Spec.hs
other-modules:
Test.Wire.API.Federation.API.BrigSpec
Test.Wire.API.Federation.Golden.ConversationCreated
Test.Wire.API.Federation.Golden.ConversationUpdate
Test.Wire.API.Federation.Golden.GoldenSpec
Test.Wire.API.Federation.Golden.LeaveConversationRequest
Test.Wire.API.Federation.Golden.LeaveConversationResponse
Test.Wire.API.Federation.Golden.MessageSendResponse
Test.Wire.API.Federation.Golden.NewConnectionRequest
Test.Wire.API.Federation.Golden.NewConnectionResponse
Test.Wire.API.Federation.Golden.NewRemoteConversation
Test.Wire.API.Federation.Golden.Runner
Paths_wire_api_federation
hs-source-dirs:
Expand Down
1 change: 1 addition & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,7 @@ type MLSMessagingAPI =
:<|> Named
"mls-message"
( Summary "Post an MLS message"
:> CanThrow 'ConvAccessDenied
:> CanThrow 'ConvNotFound
:> CanThrow 'MLSKeyPackageRefNotFound
:> CanThrow 'MLSClientMismatch
Expand Down
58 changes: 54 additions & 4 deletions services/brig/test/integration/Federation/End2end.hs
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ spec ::
Brig ->
Galley ->
CargoHold ->
Cannon ->
IO TestTree
spec _brigOpts mg brig galley cargohold cannon _federator brigTwo galleyTwo cargoholdTwo =
spec _brigOpts mg brig galley cargohold cannon _federator brigTwo galleyTwo cargoholdTwo cannonTwo =
pure $
testGroup
"federation-end2end-user"
Expand All @@ -116,7 +117,7 @@ spec _brigOpts mg brig galley cargohold cannon _federator brigTwo galleyTwo carg
test mg "download remote asset" $ testRemoteAsset brig brigTwo cargohold cargoholdTwo,
test mg "claim remote key packages" $ claimRemoteKeyPackages brig brigTwo,
test mg "send an MLS message to a remote user" $
testSendMLSMessage brig brigTwo galleyTwo cannon
testSendMLSMessage brig brigTwo galley galleyTwo cannon cannonTwo
]

-- | Path covered by this test:
Expand Down Expand Up @@ -691,8 +692,8 @@ claimRemoteKeyPackages brig1 brig2 = do

-- bob creates an MLS conversation on domain 2 with alice on domain 1, then sends a
-- message to alice
testSendMLSMessage :: Brig -> Brig -> Galley -> Cannon -> Http ()
testSendMLSMessage brig1 brig2 galley2 cannon1 = do
testSendMLSMessage :: Brig -> Brig -> Galley -> Galley -> Cannon -> Cannon -> Http ()
testSendMLSMessage brig1 brig2 galley1 galley2 cannon1 cannon2 = do
let cli :: String -> FilePath -> [String] -> CreateProcess
cli store tmp args =
proc "mls-test-cli" $
Expand Down Expand Up @@ -832,6 +833,32 @@ testSendMLSMessage brig1 brig2 galley2 cannon1 = do
spawn
(cli bobClientId tmp ["message", "--group", tmp </> "group.json", "dove"])
Nothing

-- alice creates the group and replies
void . liftIO $
spawn
( cli
aliceClientId
tmp
[ "group",
"from-welcome",
"--group-out",
tmp </> "groupA.json",
tmp </> "welcome"
]
)
Nothing
reply <-
liftIO $
spawn
( cli
aliceClientId
tmp
["message", "--group", tmp </> "groupA.json", "raven"]
)
Nothing

-- send welcome, commit and dove
WS.bracketR cannon1 (userId alice) $ \wsAlice -> do
post
( galley2
Expand Down Expand Up @@ -894,3 +921,26 @@ testSendMLSMessage brig1 brig2 galley2 cannon1 = do
evtType e @?= MLSMessageAdd
evtFrom e @?= userQualifiedId bob
evtData e @?= EdMLSMessage dove

-- send the reply and assert reception
WS.bracketR cannon2 (userId bob) $ \wsBob -> do
post
( galley1
. paths
["mls", "messages"]
. zUser (userId alice)
. zConn "conn"
. header "Z-Type" "access"
. content "message/mls"
. bytes reply
)
!!! const 201 === statusCode

-- verify that bob receives the reply
WS.assertMatch_ (5 # Second) wsBob $ \n -> do
let e = List1.head (WS.unpackPayload n)
ntfTransient n @?= False
evtConv e @?= qconvId
evtType e @?= MLSMessageAdd
evtFrom e @?= userQualifiedId alice
evtData e @?= EdMLSMessage reply
Loading