Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federation: new endpoint: GET /conversations/{domain}/{cnv} #1566

Merged
merged 4 commits into from
Jun 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions libs/wire-api-federation/src/Wire/API/Federation/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Galley.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ::
Expand Down
3 changes: 2 additions & 1 deletion services/galley/src/Galley/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
36 changes: 33 additions & 3 deletions services/galley/src/Galley/API/Query.hs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

module Galley.API.Query
( getBotConversationH,
getUnqualifiedConversation,
getConversation,
getConversationRoles,
getConversationIds,
Expand All @@ -27,11 +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
Expand All @@ -48,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
import qualified Wire.API.Provider.Bot as Public

getBotConversationH :: BotId ::: ConvId ::: JSON -> Galley Response
Expand All @@ -68,11 +75,34 @@ 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 zusr (Qualified convId remoteDomain) = do
localDomain <- viewFederationDomain
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
case gcresConvs conversations of
[] -> throwM convNotFound
[conv] -> pure conv
_convs -> throwM (federationUnexpectedBody "expected one conversation, got multiple")

getConversationRoles :: UserId -> ConvId -> Galley Public.ConversationRolesList
getConversationRoles zusr cnv = do
void $ getConversationAndCheckMembership zusr cnv
Expand Down
49 changes: 47 additions & 2 deletions services/galley/test/integration/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -115,6 +116,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,
Expand Down Expand Up @@ -874,7 +876,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
Expand All @@ -895,12 +899,53 @@ 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]
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 =
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, _) <-
liftIO $
withTempMockFederator
opts
remoteDomain
(const remoteConversationResponse)
(getConvQualified' g alice remoteConv)
conv :: Conversation <- responseJsonUnsafe <$> (pure resp <!! const 200 === statusCode)
liftIO $ do
let actual = cmOthers $ cnvMembers conv
let expected = [OtherMember aliceQ Nothing roleNameWireAdmin]
assertEqual "other members should include remoteBob" expected actual

testAddRemoteMemberFailure :: TestM ()
testAddRemoteMemberFailure = do
alice <- randomUser
Expand Down
14 changes: 14 additions & 0 deletions services/galley/test/integration/API/Util.hs
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,20 @@ getConv u c = do
. zConn "conn"
. zType "access"

getConvQualified :: UserId -> Qualified ConvId -> TestM ResponseLBS
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]
. zUser u
. zConn "conn"
. zType "access"

getConvIds :: UserId -> Maybe (Either [ConvId] ConvId) -> Maybe Int32 -> TestM ResponseLBS
getConvIds u r s = do
g <- view tsGalley
Expand Down