From 7c8eefc8c6d9b0dc8fbbef78ab0ba72eba47f1c0 Mon Sep 17 00:00:00 2001 From: jschaul Date: Wed, 2 Jun 2021 15:49:24 +0200 Subject: [PATCH 1/4] GET /conversations/domain/cnv - local conversations --- .../src/Wire/API/Routes/Public/Galley.hs | 9 +++++++++ services/galley/src/Galley/API/Public.hs | 3 ++- services/galley/src/Galley/API/Query.hs | 16 ++++++++++++++-- services/galley/test/integration/API.hs | 6 ++++-- services/galley/test/integration/API/Util.hs | 10 ++++++++++ 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs index 4a32ede0e24..6fba2d295fc 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs @@ -21,6 +21,7 @@ module Wire.API.Routes.Public.Galley where import Data.CommaSeparatedList +import Data.Domain import Data.Id (ConvId, TeamId) import Data.Range import Data.Swagger @@ -54,11 +55,19 @@ instance ToSchema Servant.NoContent where data Api routes = Api { -- Conversations + getUnqualifiedConversation :: + routes + :- Summary "Get a conversation by ID" + :> ZUser + :> "conversations" + :> Capture "cnv" ConvId + :> Get '[Servant.JSON] Public.Conversation, getConversation :: routes :- Summary "Get a conversation by ID" :> ZUser :> "conversations" + :> Capture "domain" Domain :> Capture "cnv" ConvId :> Get '[Servant.JSON] Public.Conversation, getConversationRoles :: diff --git a/services/galley/src/Galley/API/Public.hs b/services/galley/src/Galley/API/Public.hs index aacbf62b215..574ddb1e2b7 100644 --- a/services/galley/src/Galley/API/Public.hs +++ b/services/galley/src/Galley/API/Public.hs @@ -76,7 +76,8 @@ servantSitemap :: ServerT GalleyAPI.ServantAPI Galley servantSitemap = genericServerT $ GalleyAPI.Api - { GalleyAPI.getConversation = Query.getConversation, + { GalleyAPI.getUnqualifiedConversation = Query.getUnqualifiedConversation, + GalleyAPI.getConversation = Query.getConversation, GalleyAPI.getConversationRoles = Query.getConversationRoles, GalleyAPI.getConversationIds = Query.getConversationIds, GalleyAPI.getConversations = Query.getConversations, diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 4531f6861c6..e13eafba307 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -17,6 +17,7 @@ module Galley.API.Query ( getBotConversationH, + getUnqualifiedConversation, getConversation, getConversationRoles, getConversationIds, @@ -68,11 +69,22 @@ getBotConversation zbot zcnv = do | otherwise = Just (OtherMember (Qualified (memId m) domain) (memService m) (memConvRoleName m)) -getConversation :: UserId -> ConvId -> Galley Public.Conversation -getConversation zusr cnv = do +getUnqualifiedConversation :: UserId -> ConvId -> Galley Public.Conversation +getUnqualifiedConversation zusr cnv = do c <- getConversationAndCheckMembership zusr cnv Mapping.conversationView zusr c +getConversation :: UserId -> Domain -> ConvId -> Galley Public.Conversation +getConversation zusr domain cnv = do + localDomain <- viewFederationDomain + if domain == localDomain + then getUnqualifiedConversation zusr cnv + else getRemoteConversation zusr (Qualified cnv domain) + +getRemoteConversation :: UserId -> Qualified ConvId -> Galley Public.Conversation +getRemoteConversation = do + undefined + getConversationRoles :: UserId -> ConvId -> Galley Public.ConversationRolesList getConversationRoles zusr cnv = do void $ getConversationAndCheckMembership zusr cnv diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index c791f33c9db..66d4fdaf301 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -874,7 +874,9 @@ leaveConnectConversation = do -- See also the comment in Galley.API.Update.addMembers for some other checks that are necessary. testAddRemoteMember :: TestM () testAddRemoteMember = do - alice <- randomUser + aliceQ <- randomQualifiedUser + let alice = qUnqualified aliceQ + let localDomain = qDomain aliceQ bobId <- randomId let remoteDomain = Domain "far-away.example.com" remoteBob = Qualified bobId remoteDomain @@ -895,7 +897,7 @@ testAddRemoteMember = do -- FUTUREWORK: implement returning remote users in the event. -- evtData e @?= Just (EdMembersJoin (SimpleMembers [remoteBob])) evtFrom e @?= alice - conv <- responseJsonUnsafeWithMsg "conversation" <$> getConv alice convId + conv <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified alice (Qualified convId localDomain) liftIO $ do let actual = cmOthers $ cnvMembers conv let expected = [OtherMember remoteBob Nothing roleNameWireAdmin] diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index e85c10c03f2..878cd938b12 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -695,6 +695,16 @@ getConv u c = do . zConn "conn" . zType "access" +getConvQualified :: UserId -> Qualified ConvId -> TestM ResponseLBS +getConvQualified u (Qualified conv domain) = do + g <- view tsGalley + get $ + g + . paths ["conversations", toByteString' domain, toByteString' conv] + . zUser u + . zConn "conn" + . zType "access" + getConvIds :: UserId -> Maybe (Either [ConvId] ConvId) -> Maybe Int32 -> TestM ResponseLBS getConvIds u r s = do g <- view tsGalley From 757c6d4fe6c9a6ac8082973827d52e1428464357 Mon Sep 17 00:00:00 2001 From: jschaul Date: Wed, 2 Jun 2021 16:15:14 +0200 Subject: [PATCH 2/4] failing test for getting a remote conversation by id --- services/galley/src/Galley/API/Query.hs | 5 ++- services/galley/test/integration/API.hs | 40 ++++++++++++++++++++ services/galley/test/integration/API/Util.hs | 6 ++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index e13eafba307..c4e1717788b 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -28,6 +28,7 @@ module Galley.API.Query ) where +import Control.Monad.Catch (throwM) import Data.CommaSeparatedList import Data.Domain (Domain) import Data.Id as Id @@ -82,8 +83,8 @@ getConversation zusr domain cnv = do else getRemoteConversation zusr (Qualified cnv domain) getRemoteConversation :: UserId -> Qualified ConvId -> Galley Public.Conversation -getRemoteConversation = do - undefined +getRemoteConversation _zusr _convId = do + throwM convNotFound getConversationRoles :: UserId -> ConvId -> Galley Public.ConversationRolesList getConversationRoles zusr cnv = do diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 66d4fdaf301..105aa2de451 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -115,6 +115,7 @@ tests s = test s "fail to add members when not connected" postMembersFail, test s "fail to add too many members" postTooManyMembersFail, test s "add remote members" testAddRemoteMember, + test s "get remote conversation" testGetRemoteConversation, test s "add non-existing remote members" testAddRemoteMemberFailure, test s "add deleted remote members" testAddDeletedRemoteUser, test s "add remote members on invalid domain" testAddRemoteMemberInvalidDomain, @@ -903,6 +904,45 @@ testAddRemoteMember = do let expected = [OtherMember remoteBob Nothing roleNameWireAdmin] assertEqual "other members should include remoteBob" expected actual +testGetRemoteConversation :: TestM () +testGetRemoteConversation = do + aliceQ <- randomQualifiedUser + let alice = qUnqualified aliceQ + bobId <- randomId + convId <- randomId + let remoteDomain = Domain "far-away.example.com" + remoteConv = Qualified convId remoteDomain + + let aliceAsOtherMember = OtherMember aliceQ Nothing roleNameWireAdmin + bobAsMember = Member bobId Nothing False Nothing Nothing False Nothing False Nothing roleNameWireAdmin + remoteConversationResponse = + Conversation + { cnvId = convId, + cnvType = RegularConv, + cnvCreator = alice, + cnvAccess = [], + cnvAccessRole = ActivatedAccessRole, + cnvName = Just "federated gossip", + cnvMembers = ConvMembers bobAsMember [aliceAsOtherMember], + cnvTeam = Nothing, + cnvMessageTimer = Nothing, + cnvReceiptMode = Nothing + } + opts <- view tsGConf + g <- view tsGalley + (resp, _) <- + liftIO $ + withTempMockFederator + opts + remoteDomain + (const remoteConversationResponse) + (getConvQualified' g alice remoteConv) + conv :: Conversation <- responseJsonUnsafe <$> (pure resp Qualified ConvId -> TestM ResponseLBS -getConvQualified u (Qualified conv domain) = do +getConvQualified u convId = do g <- view tsGalley + getConvQualified' g u convId + +getConvQualified' :: (MonadIO m, MonadHttp m) => GalleyR -> UserId -> Qualified ConvId -> m ResponseLBS +getConvQualified' g u (Qualified conv domain) = do get $ g . paths ["conversations", toByteString' domain, toByteString' conv] From fbf3faf8e725e82dee91c110ff2c04f2a58d99b9 Mon Sep 17 00:00:00 2001 From: jschaul Date: Wed, 2 Jun 2021 16:43:14 +0200 Subject: [PATCH 3/4] get remote conversation: basic rpc --- services/galley/src/Galley/API/Query.hs | 24 +++++++++++++++++++--- services/galley/test/integration/API.hs | 27 ++++++++++++++----------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index c4e1717788b..9d72512fb32 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -28,12 +28,13 @@ module Galley.API.Query ) where +import Control.Error (runExceptT) import Control.Monad.Catch (throwM) import Data.CommaSeparatedList import Data.Domain (Domain) import Data.Id as Id import Data.Proxy -import Data.Qualified (Qualified (Qualified)) +import Data.Qualified (Qualified (..)) import Data.Range import Galley.API.Error import qualified Galley.API.Mapping as Mapping @@ -50,6 +51,10 @@ import Network.Wai.Predicate hiding (result, setStatus) import Network.Wai.Utilities import qualified Wire.API.Conversation as Public import qualified Wire.API.Conversation.Role as Public +import Wire.API.Federation.API.Galley (gcresConvs) +import qualified Wire.API.Federation.API.Galley as FederatedGalley +import Wire.API.Federation.Client (executeFederated) +import Wire.API.Federation.Error (federationErrorToWai) import qualified Wire.API.Provider.Bot as Public getBotConversationH :: BotId ::: ConvId ::: JSON -> Galley Response @@ -83,8 +88,21 @@ getConversation zusr domain cnv = do else getRemoteConversation zusr (Qualified cnv domain) getRemoteConversation :: UserId -> Qualified ConvId -> Galley Public.Conversation -getRemoteConversation _zusr _convId = do - throwM convNotFound +getRemoteConversation zusr (Qualified convId remoteDomain) = do + localDomain <- viewFederationDomain + let qualifiedZUser = Qualified zusr localDomain + req = FederatedGalley.GetConversationsRequest qualifiedZUser [convId] + rpc = FederatedGalley.getConversations FederatedGalley.clientRoutes req + conversations <- + runExceptT (executeFederated remoteDomain rpc) + >>= either (throwM . federationErrorToWai) pure + + -- TODO: check membership + -- TODO: any other checks? + case gcresConvs conversations of + [] -> throwM convNotFound + [conv] -> pure conv + _convs -> throwM convNotFound -- TODO something odd happened here. getConversationRoles :: UserId -> ConvId -> Galley Public.ConversationRolesList getConversationRoles zusr cnv = do diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 105aa2de451..1b3a8a7f179 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -65,6 +65,7 @@ import TestHelpers import TestSetup import Util.Options (Endpoint (Endpoint)) import Wire.API.Conversation.Member (Member (..)) +import Wire.API.Federation.API.Galley (GetConversationsResponse (GetConversationsResponse)) import Wire.API.User.Client (UserClientPrekeyMap, getUserClientPrekeyMap) tests :: IO TestSetup -> TestTree @@ -916,18 +917,20 @@ testGetRemoteConversation = do let aliceAsOtherMember = OtherMember aliceQ Nothing roleNameWireAdmin bobAsMember = Member bobId Nothing False Nothing Nothing False Nothing False Nothing roleNameWireAdmin remoteConversationResponse = - Conversation - { cnvId = convId, - cnvType = RegularConv, - cnvCreator = alice, - cnvAccess = [], - cnvAccessRole = ActivatedAccessRole, - cnvName = Just "federated gossip", - cnvMembers = ConvMembers bobAsMember [aliceAsOtherMember], - cnvTeam = Nothing, - cnvMessageTimer = Nothing, - cnvReceiptMode = Nothing - } + GetConversationsResponse + [ Conversation + { cnvId = convId, + cnvType = RegularConv, + cnvCreator = alice, + cnvAccess = [], + cnvAccessRole = ActivatedAccessRole, + cnvName = Just "federated gossip", + cnvMembers = ConvMembers bobAsMember [aliceAsOtherMember], + cnvTeam = Nothing, + cnvMessageTimer = Nothing, + cnvReceiptMode = Nothing + } + ] opts <- view tsGConf g <- view tsGalley (resp, _) <- From cca6431931c78c5820dc83181a2155f370928016 Mon Sep 17 00:00:00 2001 From: jschaul Date: Wed, 2 Jun 2021 17:00:15 +0200 Subject: [PATCH 4/4] cleanup --- .../wire-api-federation/src/Wire/API/Federation/Error.hs | 7 +++++++ services/galley/src/Galley/API/Query.hs | 9 ++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/libs/wire-api-federation/src/Wire/API/Federation/Error.hs b/libs/wire-api-federation/src/Wire/API/Federation/Error.hs index a4b08614b6d..1c455b84fc1 100644 --- a/libs/wire-api-federation/src/Wire/API/Federation/Error.hs +++ b/libs/wire-api-federation/src/Wire/API/Federation/Error.hs @@ -88,6 +88,13 @@ federationInvalidBody msg = "federation-invalid-body" ("Could not parse remote federator response: " <> LT.fromStrict msg) +federationUnexpectedBody :: Text -> Wai.Error +federationUnexpectedBody msg = + Wai.Error + unexpectedFederationResponseStatus + "federation-unexpected-body" + ("Could parse body, but response was not expected: " <> LT.fromStrict msg) + federationNotConfigured :: Wai.Error federationNotConfigured = Wai.Error diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 9d72512fb32..505460cebfc 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -54,7 +54,7 @@ import qualified Wire.API.Conversation.Role as Public import Wire.API.Federation.API.Galley (gcresConvs) import qualified Wire.API.Federation.API.Galley as FederatedGalley import Wire.API.Federation.Client (executeFederated) -import Wire.API.Federation.Error (federationErrorToWai) +import Wire.API.Federation.Error import qualified Wire.API.Provider.Bot as Public getBotConversationH :: BotId ::: ConvId ::: JSON -> Galley Response @@ -93,16 +93,15 @@ getRemoteConversation zusr (Qualified convId remoteDomain) = do let qualifiedZUser = Qualified zusr localDomain req = FederatedGalley.GetConversationsRequest qualifiedZUser [convId] rpc = FederatedGalley.getConversations FederatedGalley.clientRoutes req + -- we expect the remote galley to make adequate checks on conversation + -- membership and just pass through the reponse conversations <- runExceptT (executeFederated remoteDomain rpc) >>= either (throwM . federationErrorToWai) pure - - -- TODO: check membership - -- TODO: any other checks? case gcresConvs conversations of [] -> throwM convNotFound [conv] -> pure conv - _convs -> throwM convNotFound -- TODO something odd happened here. + _convs -> throwM (federationUnexpectedBody "expected one conversation, got multiple") getConversationRoles :: UserId -> ConvId -> Galley Public.ConversationRolesList getConversationRoles zusr cnv = do