diff --git a/changelog.d/1-api-changes/get-conversation-v3-leak b/changelog.d/1-api-changes/get-conversation-v3-leak new file mode 100644 index 0000000000..8f5f313314 --- /dev/null +++ b/changelog.d/1-api-changes/get-conversation-v3-leak @@ -0,0 +1 @@ +The unqualified `GET /conversations/:id` endpoint has been removed from API v3, and is restored to the previous behaviour of returning a Conversation using the v2 schema. Similarly, its qualified counterpart `GET /conversations/:domain/:id` now returns a v2 Conversation when accessed through API v2. diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs index 3c877fe475..65dc97b08b 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/Conversation.hs @@ -102,12 +102,13 @@ type ConversationAPI = Named "get-unqualified-conversation" ( Summary "Get a conversation by ID" + :> Until 'V3 :> CanThrow 'ConvNotFound :> CanThrow 'ConvAccessDenied :> ZLocalUser :> "conversations" :> Capture "cnv" ConvId - :> Get '[Servant.JSON] Conversation + :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V2 200 "Conversation" Conversation) ) :<|> Named "get-unqualified-conversation-legalhold-alias" @@ -120,18 +121,31 @@ type ConversationAPI = :> "legalhold" :> "conversations" :> Capture "cnv" ConvId - :> Get '[Servant.JSON] Conversation + :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V2 200 "Conversation" Conversation) + ) + :<|> Named + "get-conversation@v2" + ( Summary "Get a conversation by ID" + :> Until 'V3 + :> MakesFederatedCall 'Galley "get-conversations" + :> CanThrow 'ConvNotFound + :> CanThrow 'ConvAccessDenied + :> ZLocalUser + :> "conversations" + :> QualifiedCapture "cnv" ConvId + :> MultiVerb1 'GET '[JSON] (VersionedRespond 'V2 200 "Conversation" Conversation) ) :<|> Named "get-conversation" ( Summary "Get a conversation by ID" + :> From 'V3 :> MakesFederatedCall 'Galley "get-conversations" :> CanThrow 'ConvNotFound :> CanThrow 'ConvAccessDenied :> ZLocalUser :> "conversations" :> QualifiedCapture "cnv" ConvId - :> Get '[Servant.JSON] Conversation + :> Get '[JSON] Conversation ) :<|> Named "get-conversation-roles" diff --git a/services/brig/src/Brig/Effects/GalleyProvider.hs b/services/brig/src/Brig/Effects/GalleyProvider.hs index e92c14e720..afbe6b5875 100644 --- a/services/brig/src/Brig/Effects/GalleyProvider.hs +++ b/services/brig/src/Brig/Effects/GalleyProvider.hs @@ -7,6 +7,7 @@ import Brig.Team.Types (ShowOrHideInvitationUrl (..)) import qualified Data.Currency as Currency import Data.Id import Data.Json.Util (UTCTimeMillis) +import Data.Qualified import qualified Galley.Types.Teams.Intra as Team import Imports import qualified Network.Wai.Utilities.Error as Wai @@ -25,7 +26,7 @@ data GalleyProvider m a where GalleyProvider m () GetConv :: UserId -> - ConvId -> + Local ConvId -> GalleyProvider m (Maybe Conversation) GetTeamConv :: UserId -> diff --git a/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs b/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs index e84ef002e0..a3cb6c2e37 100644 --- a/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs +++ b/services/brig/src/Brig/Effects/GalleyProvider/RPC.hs @@ -18,6 +18,7 @@ import Data.Coerce (coerce) import qualified Data.Currency as Currency import Data.Id import Data.Json.Util (UTCTimeMillis) +import Data.Qualified import Data.Range import qualified Galley.Types.Teams as Team import qualified Galley.Types.Teams.Intra as Team @@ -27,8 +28,10 @@ import Network.HTTP.Types.Status import qualified Network.Wai.Utilities.Error as Wai import Polysemy import Polysemy.Error +import Servant.API (toHeader) import System.Logger (Msg, field, msg, val) import Wire.API.Conversation hiding (Member) +import Wire.API.Routes.Version import Wire.API.Team import qualified Wire.API.Team.Conversation as Conv import Wire.API.Team.Feature @@ -84,7 +87,7 @@ createSelfConv u = do void $ ServiceRPC.request @'Galley POST req where req = - path "/conversations/self" + paths ["v" <> toHeader (maxBound :: Version), "conversations", "self"] . zUser u . expect2xx @@ -97,12 +100,13 @@ getConv :: ] r => UserId -> - ConvId -> + Local ConvId -> Sem r (Maybe Conversation) -getConv usr cnv = do +getConv usr lcnv = do debug $ remote "galley" - . field "conv" (toByteString cnv) + . field "domain" (toByteString (tDomain lcnv)) + . field "conv" (toByteString (tUnqualified lcnv)) . msg (val "Getting conversation") rs <- ServiceRPC.request @'Galley GET req case Bilge.statusCode rs of @@ -110,7 +114,12 @@ getConv usr cnv = do _ -> pure Nothing where req = - paths ["conversations", toByteString' cnv] + paths + [ "v" <> toHeader (maxBound :: Version), + "conversations", + toByteString' (tDomain lcnv), + toByteString' (tUnqualified lcnv) + ] . zUser usr . expect [status200, status404] @@ -137,7 +146,13 @@ getTeamConv usr tid cnv = do _ -> pure Nothing where req = - paths ["teams", toByteString' tid, "conversations", toByteString' cnv] + paths + [ "v" <> toHeader (maxBound :: Version), + "teams", + toByteString' tid, + "conversations", + toByteString' cnv + ] . zUser usr . expect [status200, status404] diff --git a/services/brig/src/Brig/Provider/API.hs b/services/brig/src/Brig/Provider/API.hs index 84ea17a47a..529cc5a776 100644 --- a/services/brig/src/Brig/Provider/API.hs +++ b/services/brig/src/Brig/Provider/API.hs @@ -897,7 +897,8 @@ addBot zuid zcon cid add = do let pid = addBotProvider add let sid = addBotService add -- Get the conversation and check preconditions - cnv <- lift (liftSem $ GalleyProvider.getConv zuid cid) >>= maybeConvNotFound + lcid <- qualifyLocal cid + cnv <- lift (liftSem $ GalleyProvider.getConv zuid lcid) >>= maybeConvNotFound -- Check that the user is a conversation admin and therefore is allowed to add a bot to this conversation. -- Note that this precondition is also checked in the internal galley API, -- but by having this check here we prevent any (useless) data to be written to the database @@ -981,7 +982,8 @@ removeBotH (zusr ::: zcon ::: cid ::: bid) = do removeBot :: Members '[GalleyProvider] r => UserId -> ConnId -> ConvId -> BotId -> (Handler r) (Maybe Public.RemoveBotResponse) removeBot zusr zcon cid bid = do -- Get the conversation and check preconditions - cnv <- lift (liftSem $ GalleyProvider.getConv zusr cid) >>= maybeConvNotFound + lcid <- qualifyLocal cid + cnv <- lift (liftSem $ GalleyProvider.getConv zusr lcid) >>= maybeConvNotFound -- Check that the user is a conversation admin and therefore is allowed to remove a bot from the conversation. -- Note that this precondition is also checked in the internal galley API. -- However, in case we refine the roles model in the future, this check might not be granular enough. diff --git a/services/brig/test/integration/API/Provider.hs b/services/brig/test/integration/API/Provider.hs index 02e73d1ce5..93172e6ad6 100644 --- a/services/brig/test/integration/API/Provider.hs +++ b/services/brig/test/integration/API/Provider.hs @@ -734,7 +734,7 @@ testDeleteConvBotTeam config db brig galley cannon = withTestService config db b svcAssertConvDelete buf quid2 qcid -- Check that the conversation no longer exists forM_ [uid1, uid2] $ \uid -> - getConversation galley uid cid !!! const 404 === statusCode + getConversationQualified galley uid qcid !!! const 404 === statusCode getBotConv galley bid cid !!! const 404 === statusCode testDeleteTeamBotTeam :: Config -> DB.ClientState -> Brig -> Galley -> Cannon -> Http () @@ -758,7 +758,7 @@ testDeleteTeamBotTeam config db brig galley cannon = withTestService config db b forM_ [uid1, uid2] $ \uid -> do void $ retryWhileN 20 (/= Intra.Deleted) (getStatus brig uid) chkStatus brig uid Intra.Deleted - aFewTimes 11 (getConversation galley uid cid) ((== 404) . statusCode) + aFewTimes 11 (getConversationQualified galley uid qcid) ((== 404) . statusCode) -- Check the bot cannot see the conversation either getBotConv galley bid cid !!! const 404 === statusCode @@ -2088,7 +2088,7 @@ testMessageBotUtil quid uc cid pid sid sref buf brig galley cannon = do assertEqual "id" cid (bcnv ^. Ext.botConvId) assertEqual "members" [OtherMember quid Nothing roleNameWireAdmin] (bcnv ^. Ext.botConvMembers) -- The user can identify the bot in the member list - mems <- fmap cnvMembers . responseJsonError =<< getConversation galley uid cid + mems <- fmap cnvMembers . responseJsonError =<< getConversationQualified galley uid qcid let other = listToMaybe (cmOthers mems) liftIO $ do assertEqual "id" (Just buid) (qUnqualified . omQualifiedId <$> other) diff --git a/services/brig/test/integration/API/User/Auth.hs b/services/brig/test/integration/API/User/Auth.hs index 834bd46a69..3005674924 100644 --- a/services/brig/test/integration/API/User/Auth.hs +++ b/services/brig/test/integration/API/User/Auth.hs @@ -46,7 +46,7 @@ import Data.Handle (Handle (Handle)) import Data.Id import Data.Misc (PlainTextPassword (..)) import Data.Proxy -import Data.Qualified (Qualified (qUnqualified)) +import Data.Qualified import Data.Range (unsafeRange) import qualified Data.Text as Text import Data.Text.Ascii (AsciiChars (validate)) @@ -256,7 +256,7 @@ testNginzLegalHold b g n = do get (apiVersion "v1" . n . paths ["legalhold", "conversations", toByteString' (qUnqualified qconv)] . header "Authorization" ("Bearer " <> toByteString' t)) !!! const 200 === statusCode - get (n . paths ["conversations", toByteString' (qUnqualified qconv)] . header "Authorization" ("Bearer " <> toByteString' t)) !!! const 200 === statusCode + get (apiVersion "v2" . n . paths ["conversations", toByteString' (qUnqualified qconv)] . header "Authorization" ("Bearer " <> toByteString' t)) !!! const 200 === statusCode -- | Corner case for 'testNginz': when upgrading a wire backend from the old behavior (setting -- cookie domain to eg. @*.wire.com@) to the new behavior (leaving cookie domain empty, diff --git a/services/brig/test/integration/API/User/Connection.hs b/services/brig/test/integration/API/User/Connection.hs index d7f0bcd432..f25a2f594d 100644 --- a/services/brig/test/integration/API/User/Connection.hs +++ b/services/brig/test/integration/API/User/Connection.hs @@ -178,11 +178,11 @@ testCreateMutualConnections brig galley = do assertConnections brig uid2 [ConnectionStatus uid2 uid1 Accepted] case responseJsonMaybe rsp >>= ucConvId of Nothing -> liftIO $ assertFailure "incomplete connection" - Just (Qualified cnv _) -> do - getConversation galley uid1 cnv !!! do + Just qcnv -> do + getConversationQualified galley uid1 qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonMaybe - getConversation galley uid2 cnv !!! do + getConversationQualified galley uid2 qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonMaybe @@ -304,32 +304,32 @@ testCancelConnection2 brig galley = do rsp <- putConnection brig uid1 uid2 Cancelled do conv <- responseJsonMaybe rs Just (cnvType conv) -- A is a past member, cannot see the conversation - getConversation galley uid1 cnv !!! do + getConversationQualified galley uid1 qcnv !!! do const 403 === statusCode -- A finally accepts putConnection brig uid1 uid2 Accepted !!! const 200 === statusCode assertConnections brig uid1 [ConnectionStatus uid1 uid2 Accepted] assertConnections brig uid2 [ConnectionStatus uid2 uid1 Accepted] - getConversation galley uid1 cnv !!! do + getConversationQualified galley uid1 qcnv !!! do const 200 === statusCode - getConversation galley uid2 cnv !!! do + getConversationQualified galley uid2 qcnv !!! do const 200 === statusCode testCancelConnectionQualified2 :: Brig -> Galley -> Http () @@ -483,10 +483,10 @@ testBlockAndResendConnection brig galley = do assertConnections brig uid1 [ConnectionStatus uid1 uid2 Accepted] assertConnections brig uid2 [ConnectionStatus uid2 uid1 Blocked] -- B never accepted and thus does not see the conversation - let Just (Qualified cnv _) = ucConvId =<< responseJsonMaybe rsp - getConversation galley uid2 cnv !!! const 403 === statusCode + let Just qcnv = ucConvId =<< responseJsonMaybe rsp + getConversationQualified galley uid2 qcnv !!! const 403 === statusCode -- A can see the conversation and is a current member - getConversation galley uid1 cnv !!! do + getConversationQualified galley uid1 qcnv !!! do const 200 === statusCode testBlockAndResendConnectionQualified :: Brig -> Galley -> Http () diff --git a/services/brig/test/integration/Util.hs b/services/brig/test/integration/Util.hs index 649195714d..37cfac2e5e 100644 --- a/services/brig/test/integration/Util.hs +++ b/services/brig/test/integration/Util.hs @@ -710,13 +710,6 @@ getTeamMember u tid galley = . expect2xx ) -getConversation :: (MonadIO m, MonadHttp m) => Galley -> UserId -> ConvId -> m ResponseLBS -getConversation galley usr cnv = - get $ - galley - . paths ["conversations", toByteString' cnv] - . zAuthAccess usr "conn" - getConversationQualified :: (MonadIO m, MonadHttp m) => Galley -> UserId -> Qualified ConvId -> m ResponseLBS getConversationQualified galley usr cnv = get $ diff --git a/services/galley/src/Galley/API/Public/Conversation.hs b/services/galley/src/Galley/API/Public/Conversation.hs index c080d83b04..800c9f4654 100644 --- a/services/galley/src/Galley/API/Public/Conversation.hs +++ b/services/galley/src/Galley/API/Public/Conversation.hs @@ -32,6 +32,7 @@ conversationAPI :: API ConversationAPI GalleyEffects conversationAPI = mkNamedAPI @"get-unqualified-conversation" getUnqualifiedConversation <@> mkNamedAPI @"get-unqualified-conversation-legalhold-alias" getUnqualifiedConversation + <@> mkNamedAPI @"get-conversation@v2" (callsFed getConversation) <@> mkNamedAPI @"get-conversation" (callsFed getConversation) <@> mkNamedAPI @"get-conversation-roles" getConversationRoles <@> mkNamedAPI @"get-group-info" (callsFed getGroupInfo) diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index f193cbfee6..7c001655ca 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -98,6 +98,8 @@ import Wire.API.Message import qualified Wire.API.Message as Message import Wire.API.Routes.MultiTablePaging import Wire.API.Routes.Named +import Wire.API.Routes.Version +import Wire.API.Routes.Versioned import qualified Wire.API.Team.Feature as Public import qualified Wire.API.Team.Member as Teams import Wire.API.User @@ -125,6 +127,7 @@ tests s = "Main Conversations API" [ test s "status" status, test s "metrics" metrics, + test s "fetch conversation by qualified ID (v2)" testGetConvQualifiedV2, test s "create Proteus conversation" postProteusConvOk, test s "create conversation with remote users" postConvWithRemoteUsersOk, test s "get empty conversations" getConvsOk, @@ -270,24 +273,46 @@ metrics = do -- Should contain the request duration metric in its output const (Just "TYPE http_request_duration_seconds histogram") =~= responseBody +testGetConvQualifiedV2 :: TestM () +testGetConvQualifiedV2 = do + alice <- randomUser + bob <- randomUser + connectUsers alice (list1 bob []) + conv <- + responseJsonError + =<< postConvQualified + alice + defNewProteusConv + { newConvUsers = [bob] + } + do rsp <- postConv alice [bob, jane] (Just nameMaxSize) [] Nothing Nothing getConv usr cnv + convView cnv usr = do + r <- getConv usr cnv responseJsonError r checkWs qalice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do ntfTransient n @?= False let e = List1.head (WS.unpackPayload n) @@ -318,7 +343,8 @@ postConvWithRemoteUsersOk = do withTempMockFederator (const ()) $ postConvQualified alice defNewProteusConv {newConvName = checked nameMaxSize, newConvQualifiedUsers = [qAlex, qAmy, qChad, qCharlie, qDee]} getConv usr cnv + convView cnv usr = do + r <- getConv usr cnv responseJsonError r checkWs qalice (cnv, ws) = WS.awaitMatch (5 # Second) ws $ \n -> do ntfTransient n @?= False let e = List1.head (WS.unpackPayload n) @@ -2141,14 +2169,13 @@ postSelfConvOk = do postO2OConvOk :: TestM () postO2OConvOk = do - qalice <- randomQualifiedUser - let alice = qUnqualified qalice - bob <- randomUser + (alice, qalice) <- randomUserTuple + (bob, qbob) <- randomUserTuple connectUsers alice (singleton bob) a <- postO2OConv alice bob Nothing postConnectConv alice bob "Alice" "come to zeta!" Nothing - putConvAccept bob cnv !!! const 200 === statusCode - getConv alice cnv !!! do + qcnv <- decodeQualifiedConvId <$> postConnectConv alice bob "Alice" "come to zeta!" Nothing + putConvAccept bob (qUnqualified qcnv) !!! const 200 === statusCode + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonUnsafe - getConv bob cnv !!! do + getConvQualified bob qcnv !!! do const 200 === statusCode const (Just One2OneConv) === fmap cnvType . responseJsonUnsafe @@ -2223,7 +2250,7 @@ postMutualConnectConvOk = do getConv bob convId + cnvX <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified bob qconvId liftIO $ do ConnectConv @=? cnvType cnvX Just "B" @=? cnvName cnvX privateAccess @=? cnvAccess cnvX -- Alice accepts, finally turning it into a 1-1 putConvAccept alice convId !!! const 200 === statusCode - cnv4 <- responseJsonUnsafeWithMsg "conversation" <$> getConv alice convId + cnv4 <- responseJsonUnsafeWithMsg "conversation" <$> getConvQualified alice qconvId liftIO $ do One2OneConv @=? cnvType cnv4 Just "B" @=? cnvName cnv4 @@ -2286,29 +2314,30 @@ putBlockConvOk = do alice <- randomUser bob <- randomUser conv <- responseJsonUnsafeWithMsg "conversation" <$> postConnectConv alice bob "Alice" "connect with me!" (Just "me@me.com") - let convId = qUnqualified . cnvQualifiedId $ conv - getConv alice convId !!! const 200 === statusCode - getConv bob convId !!! const 403 === statusCode + let qconvId = cnvQualifiedId conv + let convId = qUnqualified qconvId + getConvQualified alice qconvId !!! const 200 === statusCode + getConvQualified bob qconvId !!! const 403 === statusCode put (g . paths ["/i/conversations", toByteString' convId, "block"] . zUser bob) !!! const 200 === statusCode -- A is still the only member of the 1-1 - getConv alice convId !!! do + getConvQualified alice qconvId !!! do const 200 === statusCode const (cnvMembers conv) === cnvMembers . responseJsonUnsafeWithMsg "conversation" -- B accepts the conversation by unblocking put (g . paths ["/i/conversations", toByteString' convId, "unblock"] . zUser bob) !!! const 200 === statusCode - getConv bob convId !!! const 200 === statusCode + getConvQualified bob qconvId !!! const 200 === statusCode -- B blocks A in the 1-1 put (g . paths ["/i/conversations", toByteString' convId, "block"] . zUser bob) !!! const 200 === statusCode -- B no longer sees the 1-1 - getConv bob convId !!! const 403 === statusCode + getConvQualified bob qconvId !!! const 403 === statusCode -- B unblocks A in the 1-1 put (g . paths ["/i/conversations", toByteString' convId, "unblock"] . zUser bob) !!! const 200 === statusCode -- B sees the blocked 1-1 again - getConv bob convId !!! do + getConvQualified bob qconvId !!! do const 200 === statusCode getConvOk :: TestM () @@ -2677,7 +2706,8 @@ testAddRemoteMemberFederationDisabled :: TestM () testAddRemoteMemberFederationDisabled = do alice <- randomUser remoteBob <- flip Qualified (Domain "some-remote-backend.example.com") <$> randomId - convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + qconvId <- decodeQualifiedConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + let convId = qUnqualified qconvId connectWithRemoteUser alice remoteBob -- federator endpoint not configured is equivalent to federation being disabled @@ -2689,14 +2719,15 @@ testAddRemoteMemberFederationDisabled = do const (Right "federation-not-enabled") === fmap label . responseJsonEither -- the member is not actually added to the conversation - conv <- responseJsonError =<< getConv alice convId randomId - convId <- decodeConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + qconvId <- decodeQualifiedConvId <$> postConv alice [] (Just "remote gossip") [] Nothing Nothing + let convId = qUnqualified qconvId connectWithRemoteUser alice remoteBob -- federator endpoint being configured in brig and/or galley, but not being @@ -2711,7 +2742,7 @@ testAddRemoteMemberFederationUnavailable = do -- in this case, we discover that federation is unavailable too late, and the -- member has already been added to the conversation - conv <- responseJsonError =<< getConv alice convId assertFailure $ "Unexpected event data: " ++ show x -- Verify new member state - rs <- getConv bob conv responseJsonUnsafe rs liftIO $ do assertBool "user" (isJust bob') @@ -3496,13 +3527,13 @@ putReceiptModeOk = do let qcnv = Qualified cnv (qDomain qalice) WS.bracketR3 c alice bob jane $ \(_wsA, wsB, _wsJ) -> do -- By default, nothing is set - getConv alice cnv !!! do + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just Nothing) === fmap cnvReceiptMode . responseJsonUnsafe -- Set receipt mode putReceiptMode alice cnv (ReceiptMode 0) !!! const 200 === statusCode -- Ensure the field is properly set - getConv alice cnv !!! do + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just $ Just (ReceiptMode 0)) === fmap cnvReceiptMode . responseJsonUnsafe void . liftIO $ checkWs qalice (qcnv, wsB) @@ -3511,11 +3542,11 @@ putReceiptModeOk = do -- No event should have been generated WS.assertNoEvent (1 # Second) [wsB] -- Ensure that the new field remains unchanged - getConv alice cnv !!! do + getConvQualified alice qcnv !!! do const 200 === statusCode const (Just $ Just (ReceiptMode 0)) === fmap cnvReceiptMode . responseJsonUnsafe - cnv' <- decodeConvId <$> postConvWithReceipt alice [bob, jane] (Just "gossip") [] Nothing Nothing (ReceiptMode 0) - getConv alice cnv' !!! do + qcnv' <- decodeQualifiedConvId <$> postConvWithReceipt alice [bob, jane] (Just "gossip") [] Nothing Nothing (ReceiptMode 0) + getConvQualified alice qcnv' !!! do const 200 === statusCode const (Just (Just (ReceiptMode 0))) === fmap cnvReceiptMode . responseJsonUnsafe where @@ -3775,11 +3806,9 @@ removeUserNoFederation = do connectUsers alice' (list1 bob' [carl']) - conv1 <- decodeConvId <$> postConv alice' [bob'] (Just "gossip") [] Nothing Nothing - conv2 <- decodeConvId <$> postConv alice' [bob', carl'] (Just "gossip2") [] Nothing Nothing - conv3 <- decodeConvId <$> postConv alice' [carl'] (Just "gossip3") [] Nothing Nothing - let qconv1 = Qualified conv1 (qDomain bob) - qconv2 = Qualified conv2 (qDomain bob) + qconv1 <- decodeQualifiedConvId <$> postConv alice' [bob'] (Just "gossip") [] Nothing Nothing + qconv2 <- decodeQualifiedConvId <$> postConv alice' [bob', carl'] (Just "gossip2") [] Nothing Nothing + qconv3 <- decodeQualifiedConvId <$> postConv alice' [carl'] (Just "gossip3") [] Nothing Nothing WS.bracketR3 c alice' bob' carl' $ \(wsA, wsB, wsC) -> do deleteUser bob' !!! const 200 === statusCode @@ -3791,9 +3820,9 @@ removeUserNoFederation = do WS.assertMatchN (5 # Second) [wsA, wsB, wsC] $ wsAssertMembersLeave qconv2 bob [bob] -- Check memberships - mems1 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice' conv1 - mems2 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice' conv2 - mems3 <- fmap cnvMembers . responseJsonUnsafe <$> getConv alice' conv3 + mems1 <- fmap cnvMembers . responseJsonUnsafe <$> getConvQualified alice' qconv1 + mems2 <- fmap cnvMembers . responseJsonUnsafe <$> getConvQualified alice' qconv2 + mems3 <- fmap cnvMembers . responseJsonUnsafe <$> getConvQualified alice' qconv3 let other u = find ((== u) . omQualifiedId) . cmOthers liftIO $ do (mems1 >>= other bob) @?= Nothing @@ -3828,17 +3857,14 @@ removeUser = do connectWithRemoteUser alice' dwight connectWithRemoteUser alexDel' dory - convA1 <- decodeConvId <$> postConv alice' [alexDel'] (Just "gossip") [] Nothing Nothing - convA2 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, amy, berta, dwight]} - convA3 <- decodeConvId <$> postConv alice' [amy'] (Just "gossip3") [] Nothing Nothing - convA4 <- decodeConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, bart, carl]} + qconvA1 <- decodeQualifiedConvId <$> postConv alice' [alexDel'] (Just "gossip") [] Nothing Nothing + qconvA2 <- decodeQualifiedConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, amy, berta, dwight]} + qconvA3 <- decodeQualifiedConvId <$> postConv alice' [amy'] (Just "gossip3") [] Nothing Nothing + qconvA4 <- decodeQualifiedConvId <$> postConvWithRemoteUsers alice' defNewProteusConv {newConvQualifiedUsers = [alexDel, bart, carl]} convB1 <- randomId -- a remote conversation at 'bDomain' that Alice, AlexDel and Bart will be in convB2 <- randomId -- a remote conversation at 'bDomain' that AlexDel and Bart will be in convC1 <- randomId -- a remote conversation at 'cDomain' that AlexDel and Carl will be in convD1 <- randomId -- a remote conversation at 'cDomain' that AlexDel and Dory will be in - let qconvA1 = Qualified convA1 (qDomain alexDel) - qconvA2 = Qualified convA2 (qDomain alexDel) - now <- liftIO getCurrentTime fedGalleyClient <- view tsFedGalleyClient let nc cid creator quids = @@ -3912,12 +3938,12 @@ removeUser = do let bConvUpdateRPCs = filter (matchFedRequest bDomain "on-conversation-updated") fedRequests bConvUpdates <- mapM (assertRight . eitherDecode . frBody) bConvUpdateRPCs - bConvUpdatesA2 <- assertOne $ filter (\cu -> cuConvId cu == convA2) bConvUpdates + bConvUpdatesA2 <- assertOne $ filter (\cu -> cuConvId cu == qUnqualified qconvA2) bConvUpdates cuOrigUserId bConvUpdatesA2 @?= alexDel cuAction bConvUpdatesA2 @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers bConvUpdatesA2 @?= [qUnqualified berta] - bConvUpdatesA4 <- assertOne $ filter (\cu -> cuConvId cu == convA4) bConvUpdates + bConvUpdatesA4 <- assertOne $ filter (\cu -> cuConvId cu == qUnqualified qconvA4) bConvUpdates cuOrigUserId bConvUpdatesA4 @?= alexDel cuAction bConvUpdatesA4 @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers bConvUpdatesA4 @?= [qUnqualified bart] @@ -3925,7 +3951,7 @@ removeUser = do liftIO $ do cConvUpdateRPC <- assertOne $ filter (matchFedRequest cDomain "on-conversation-updated") fedRequests Right convUpdate <- pure . eitherDecode . frBody $ cConvUpdateRPC - cuConvId convUpdate @?= convA4 + cuConvId convUpdate @?= qUnqualified qconvA4 cuOrigUserId convUpdate @?= alexDel cuAction convUpdate @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers convUpdate @?= [qUnqualified carl] @@ -3933,16 +3959,16 @@ removeUser = do liftIO $ do dConvUpdateRPC <- assertOne $ filter (matchFedRequest dDomain "on-conversation-updated") fedRequests Right convUpdate <- pure . eitherDecode . frBody $ dConvUpdateRPC - cuConvId convUpdate @?= convA2 + cuConvId convUpdate @?= qUnqualified qconvA2 cuOrigUserId convUpdate @?= alexDel cuAction convUpdate @?= SomeConversationAction (sing @'ConversationLeaveTag) () cuAlreadyPresentUsers convUpdate @?= [qUnqualified dwight] -- Check memberships - mems1 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA1 - mems2 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA2 - mems3 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA3 - mems4 <- fmap cnvMembers . responseJsonError =<< getConv alice' convA4 + mems1 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA1 + mems2 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA2 + mems3 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA3 + mems4 <- fmap cnvMembers . responseJsonError =<< getConvQualified alice' qconvA4 let findOther u = find ((== u) . omQualifiedId) . cmOthers liftIO $ do findOther alexDel mems1 @?= Nothing diff --git a/services/galley/test/integration/API/Federation.hs b/services/galley/test/integration/API/Federation.hs index 23bb350eed..e33915ce83 100644 --- a/services/galley/test/integration/API/Federation.hs +++ b/services/galley/test/integration/API/Federation.hs @@ -1064,8 +1064,7 @@ updateConversationByRemoteAdmin = do postConvQualified alice defNewProteusConv {newConvName = checked convName, newConvQualifiedUsers = [qbob, qcharlie]} viewFederationDomain + [(alice, qalice), (bob, qbob), (jane, qjane)] <- replicateM 3 randomUserTuple connectUsers alice (list1 bob [jane]) rsp <- postConv alice [bob, jane] Nothing [] Nothing mtimer viewFederationDomain + [(alice, qalice), (bob, qbob), (jane, qjane)] <- replicateM 3 randomUserTuple connectUsers alice (list1 bob [jane]) rsp <- postConv alice [bob, jane] Nothing [] Nothing Nothing viewFederationDomain + [(alice, qalice), (bob, qbob), (jane, qjane)] <- replicateM 3 randomUserTuple connectUsers alice (list1 bob [jane]) rsp <- postConv alice [bob, jane] Nothing [] Nothing Nothing cid -- Try to change the timer (as a non admin, guest user) and observe failure putMessageTimerUpdate guest cid (ConversationMessageTimerUpdate timer1sec) !!! do const 403 === statusCode const "action-denied" === (label . responseJsonUnsafeWithMsg "error label") - getConv guest cid + getConvQualified guest qcid !!! const Nothing === (cnvMessageTimer <=< responseJsonUnsafe) -- Try to change the timer (as a non admin, team member) and observe failure too putMessageTimerUpdate member cid (ConversationMessageTimerUpdate timer1sec) !!! do @@ -203,43 +200,40 @@ messageTimerChangeWithoutAllowedAction = do -- Finally try to change the timer (as an admin) and observe success putMessageTimerUpdate owner cid (ConversationMessageTimerUpdate timer1sec) !!! do const 200 === statusCode - getConv guest cid + getConvQualified guest qcid !!! const timer1sec === (cnvMessageTimer <=< responseJsonUnsafe) messageTimerChangeO2O :: TestM () messageTimerChangeO2O = do -- Create a 1:1 conversation - [alice, bob] <- randomUsers 2 - qAlice <- Qualified alice <$> viewFederationDomain + [(alice, qalice), (bob, qbob)] <- replicateM 2 randomUserTuple connectUsers alice (singleton bob) rsp <- postO2OConv alice bob Nothing viewFederationDomain + [(alice, qalice), (bob, qbob)] <- replicateM 2 randomUserTuple connectUsers alice (singleton bob) rsp <- postConv alice [bob] Nothing [] Nothing Nothing do let update = ConversationMessageTimerUpdate timer1sec - qcid = Qualified cid localDomain - qalice = Qualified alice localDomain putMessageTimerUpdate alice cid update !!! const 200 === statusCode void . liftIO $ diff --git a/services/galley/test/integration/API/Roles.hs b/services/galley/test/integration/API/Roles.hs index ed769f7d96..b5ed4cb27c 100644 --- a/services/galley/test/integration/API/Roles.hs +++ b/services/galley/test/integration/API/Roles.hs @@ -84,8 +84,8 @@ handleConversationRoleAdmin = do localDomain <- viewFederationDomain c <- view tsCannon (alice, qalice) <- randomUserTuple - bob <- randomUser - chuck <- randomUser + (bob, qbob) <- randomUserTuple + (chuck, qchuck) <- randomUserTuple (eve, qeve) <- randomUserTuple (jack, qjack) <- randomUserTuple connectUsers alice (list1 bob [chuck, eve, jack]) @@ -94,7 +94,7 @@ handleConversationRoleAdmin = do let role = roleNameWireAdmin cid <- WS.bracketR3 c alice bob chuck $ \(wsA, wsB, wsC) -> do rsp <- postConvWithRole alice [bob, chuck] (Just "gossip") [] Nothing Nothing role - void $ assertConvWithRole rsp RegularConv alice qalice [bob, chuck] (Just "gossip") Nothing role + void $ assertConvWithRole rsp RegularConv alice qalice [qbob, qchuck] (Just "gossip") Nothing role let cid = decodeConvId rsp qcid = Qualified cid localDomain -- Make sure everyone gets the correct event @@ -123,10 +123,9 @@ handleConversationRoleMember :: TestM () handleConversationRoleMember = do localDomain <- viewFederationDomain c <- view tsCannon - alice <- randomUser - let qalice = Qualified alice localDomain - bob <- randomUser - chuck <- randomUser + (alice, qalice) <- randomUserTuple + (bob, qbob) <- randomUserTuple + (chuck, qchuck) <- randomUserTuple eve <- randomUser let qeve = Qualified eve localDomain jack <- randomUser @@ -136,7 +135,7 @@ handleConversationRoleMember = do let role = roleNameWireMember cid <- WS.bracketR3 c alice bob chuck $ \(wsA, wsB, wsC) -> do rsp <- postConvWithRole alice [bob, chuck] (Just "gossip") [] Nothing Nothing role - void $ assertConvWithRole rsp RegularConv alice qalice [bob, chuck] (Just "gossip") Nothing role + void $ assertConvWithRole rsp RegularConv alice qalice [qbob, qchuck] (Just "gossip") Nothing role let cid = decodeConvId rsp qcid = Qualified cid localDomain -- Make sure everyone gets the correct event diff --git a/services/galley/test/integration/API/Teams.hs b/services/galley/test/integration/API/Teams.hs index 72cb13c55b..63c416f644 100644 --- a/services/galley/test/integration/API/Teams.hs +++ b/services/galley/test/integration/API/Teams.hs @@ -794,7 +794,7 @@ testCreateTeamMLSConv = do Nothing Nothing Nothing - Right conv <- responseJsonError <$> getConv owner (tUnqualified lConvId) + Right conv <- responseJsonError <$> getConvQualified owner (tUntagged lConvId) liftIO $ do assertEqual "protocol mismatch" ProtocolMLSTag (protocolTag (cnvProtocol conv)) checkConvCreateEvent (tUnqualified lConvId) wsOwner diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index adfc6d1b29..11255a97e0 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -1033,12 +1033,43 @@ listConvs u req = do . zType "access" . json req -getConv :: (MonadIO m, MonadHttp m, HasGalley m, HasCallStack) => UserId -> ConvId -> m ResponseLBS +getConv :: + ( MonadIO m, + MonadHttp m, + MonadReader TestSetup m, + HasCallStack + ) => + UserId -> + ConvId -> + m ResponseLBS getConv u c = do - g <- viewGalley + g <- view tsUnversionedGalley + get $ + g + . paths ["v2", "conversations", toByteString' c] + . zUser u + . zConn "conn" + . zType "access" + +getConvQualifiedV2 :: + ( Monad m, + MonadReader TestSetup m, + MonadHttp m, + MonadIO m + ) => + UserId -> + Qualified ConvId -> + m ResponseLBS +getConvQualifiedV2 u qcnv = do + g <- view tsUnversionedGalley get $ g - . paths ["conversations", toByteString' c] + . paths + [ "v2", + "conversations", + toByteString' (qDomain qcnv), + toByteString' (qUnqualified qcnv) + ] . zUser u . zConn "conn" . zType "access" @@ -1531,61 +1562,13 @@ assertConv :: ConvType -> UserId -> Qualified UserId -> - [UserId] -> + [Qualified UserId] -> Maybe Text -> Maybe Milliseconds -> - TestM ConvId + TestM (Qualified ConvId) assertConv r t c s us n mt = assertConvWithRole r t c s us n mt roleNameWireAdmin assertConvWithRole :: - HasCallStack => - Response (Maybe Lazy.ByteString) -> - ConvType -> - UserId -> - Qualified UserId -> - [UserId] -> - Maybe Text -> - Maybe Milliseconds -> - RoleName -> - TestM ConvId -assertConvWithRole r t c s us n mt role = do - cId <- fromBS $ getHeader' "Location" r - let cnv = responseJsonMaybe @Conversation r - let _self = cmSelf . cnvMembers <$> cnv - let others = cmOthers . cnvMembers <$> cnv - liftIO $ do - assertEqual "id" (Just cId) (qUnqualified . cnvQualifiedId <$> cnv) - assertEqual "name" n (cnv >>= cnvName) - assertEqual "type" (Just t) (cnvType <$> cnv) - assertEqual "creator" (Just c) (cnvCreator <$> cnv) - assertEqual "message_timer" (Just mt) (cnvMessageTimer <$> cnv) - assertEqual "self" (Just s) (memId <$> _self) - assertEqual "others" (Just . Set.fromList $ us) (Set.fromList . map (qUnqualified . omQualifiedId) . toList <$> others) - assertEqual "creator is always and admin" (Just roleNameWireAdmin) (memConvRoleName <$> _self) - assertBool "others role" (all (== role) $ maybe (error "Cannot be null") (map omConvRoleName . toList) others) - assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef =<< _self)) - assertBool "otr archived not false" (Just False == (memOtrArchived <$> _self)) - assertBool "otr archived ref not empty" (isNothing (memOtrArchivedRef =<< _self)) - case t of - SelfConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - ConnectConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - One2OneConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - _ -> pure () - pure cId - -assertConvQualified :: - HasCallStack => - Response (Maybe Lazy.ByteString) -> - ConvType -> - UserId -> - Qualified UserId -> - [Qualified UserId] -> - Maybe Text -> - Maybe Milliseconds -> - TestM ConvId -assertConvQualified r t c s us n mt = assertConvQualifiedWithRole r t c s us n mt roleNameWireAdmin - -assertConvQualifiedWithRole :: HasCallStack => Response (Maybe Lazy.ByteString) -> ConvType -> @@ -1595,31 +1578,31 @@ assertConvQualifiedWithRole :: Maybe Text -> Maybe Milliseconds -> RoleName -> - TestM ConvId -assertConvQualifiedWithRole r t c s us n mt role = do + TestM (Qualified ConvId) +assertConvWithRole r t c s us n mt role = do cId <- fromBS $ getHeader' "Location" r - let cnv = responseJsonMaybe @Conversation r - let _self = cmSelf . cnvMembers <$> cnv - let others = cmOthers . cnvMembers <$> cnv + cnv <- responseJsonError r + let _self = cmSelf (cnvMembers cnv) + let others = cmOthers (cnvMembers cnv) liftIO $ do - assertEqual "id" (Just cId) (qUnqualified . cnvQualifiedId <$> cnv) - assertEqual "name" n (cnv >>= cnvName) - assertEqual "type" (Just t) (cnvType <$> cnv) - assertEqual "creator" (Just c) (cnvCreator <$> cnv) - assertEqual "message_timer" (Just mt) (cnvMessageTimer <$> cnv) - assertEqual "self" (Just s) (memId <$> _self) - assertEqual "others" (Just . Set.fromList $ us) (Set.fromList . map omQualifiedId . toList <$> others) - assertEqual "creator is always and admin" (Just roleNameWireAdmin) (memConvRoleName <$> _self) - assertBool "others role" (all (== role) $ maybe (error "Cannot be null") (map omConvRoleName . toList) others) - assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef =<< _self)) - assertBool "otr archived not false" (Just False == (memOtrArchived <$> _self)) - assertBool "otr archived ref not empty" (isNothing (memOtrArchivedRef =<< _self)) + assertEqual "id" cId (qUnqualified (cnvQualifiedId cnv)) + assertEqual "name" n (cnvName cnv) + assertEqual "type" t (cnvType cnv) + assertEqual "creator" c (cnvCreator cnv) + assertEqual "message_timer" mt (cnvMessageTimer cnv) + assertEqual "self" s (memId _self) + assertEqual "others" (Set.fromList $ us) (Set.fromList . map omQualifiedId . toList $ others) + assertEqual "creator is always and admin" roleNameWireAdmin (memConvRoleName _self) + assertBool "others role" (all ((== role) . omConvRoleName) (toList others)) + assertBool "otr muted ref not empty" (isNothing (memOtrMutedRef _self)) + assertBool "otr archived not false" (not (memOtrArchived _self)) + assertBool "otr archived ref not empty" (isNothing (memOtrArchivedRef _self)) case t of - SelfConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - ConnectConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) - One2OneConv -> assertEqual "access" (Just privateAccess) (cnvAccess <$> cnv) + SelfConv -> assertEqual "access" privateAccess (cnvAccess cnv) + ConnectConv -> assertEqual "access" privateAccess (cnvAccess cnv) + One2OneConv -> assertEqual "access" privateAccess (cnvAccess cnv) _ -> pure () - pure cId + pure (cnvQualifiedId cnv) wsAssertOtr :: HasCallStack => diff --git a/services/galley/test/integration/Main.hs b/services/galley/test/integration/Main.hs index 6907d1ab45..6410cc9fb8 100644 --- a/services/galley/test/integration/Main.hs +++ b/services/galley/test/integration/Main.hs @@ -88,7 +88,7 @@ main = withOpenSSL $ runTests go "galley" [ testCase "sitemap" $ assertEqual - "inconcistent sitemap" + "inconsistent sitemap" mempty (pathsConsistencyCheck . treeToPaths . compile $ Galley.API.sitemap), API.tests setup