From a7b2b2863503c8f5ca1580a3246b0719e44200f0 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 9 Dec 2021 11:46:06 +0000 Subject: [PATCH 1/9] check feature status when joining with a guest link --- .../wire-api/src/Wire/API/ErrorDescription.hs | 2 + .../src/Wire/API/Routes/Public/Galley.hs | 1 + services/galley/src/Galley/API/Error.hs | 5 +++ services/galley/src/Galley/API/Query.hs | 41 ++++++++++++++----- .../galley/src/Galley/API/Teams/Features.hs | 1 + 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/libs/wire-api/src/Wire/API/ErrorDescription.hs b/libs/wire-api/src/Wire/API/ErrorDescription.hs index f02a37cc0f..1cb53ce4a3 100644 --- a/libs/wire-api/src/Wire/API/ErrorDescription.hs +++ b/libs/wire-api/src/Wire/API/ErrorDescription.hs @@ -261,6 +261,8 @@ type HandleNotFound = ErrorDescription 404 "not-found" "Handle not found" type TooManyClients = ErrorDescription 403 "too-many-clients" "Too many clients" +type GuestLinksDisabled = ErrorDescription 409 "guest-links-disabled" "The guest link feature is disabled and all guest links have been revoked." + type MissingAuth = ErrorDescription 403 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 158fddcca3..30e402c059 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley.hs @@ -195,6 +195,7 @@ data Api routes = Api :> CanThrow CodeNotFound :> CanThrow ConvNotFound :> CanThrow ConvAccessDenied + :> CanThrow GuestLinksDisabled :> ZLocalUser :> "conversations" :> "join" diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs index a4569e788c..2e5a8a9afd 100644 --- a/services/galley/src/Galley/API/Error.hs +++ b/services/galley/src/Galley/API/Error.hs @@ -122,6 +122,7 @@ data ConversationError | ConvMemberNotFound | NoBindingTeamMembers | NoManagedTeamConv + | GuestLinksDisabled instance APIError ConversationError where toWai ConvAccessDenied = errorDescriptionTypeToWai @ConvAccessDenied @@ -130,6 +131,7 @@ instance APIError ConversationError where toWai ConvMemberNotFound = errorDescriptionTypeToWai @ConvMemberNotFound toWai NoBindingTeamMembers = noBindingTeamMembers toWai NoManagedTeamConv = noManagedTeamConv + toWai GuestLinksDisabled = guestLinksDisabled data TeamError = NoBindingTeam @@ -396,6 +398,9 @@ teamMemberNotFound = mkError status404 "no-team-member" "team member not found" noManagedTeamConv :: Error noManagedTeamConv = mkError status400 "no-managed-team-conv" "Managed team conversations have been deprecated." +guestLinksDisabled :: Error +guestLinksDisabled = mkError status409 "guest-links-disabled" "The guest link feature is disabled and all guest links have been revoked." + userBindingExists :: Error userBindingExists = mkError status403 "binding-exists" "User already bound to a different team." diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index b2c678d837..0dedd43d2d 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -34,6 +34,7 @@ module Galley.API.Query where import qualified Cassandra as C +import Control.Lens import qualified Data.ByteString.Lazy as LBS import Data.Code import Data.CommaSeparatedList @@ -54,9 +55,12 @@ import qualified Galley.Effects.ConversationStore as E import qualified Galley.Effects.FederatorAccess as E import qualified Galley.Effects.ListItems as E import qualified Galley.Effects.MemberStore as E +import qualified Galley.Effects.TeamFeatureStore as TeamFeatures +import Galley.Options import Galley.Types import Galley.Types.Conversations.Members import Galley.Types.Conversations.Roles +import Galley.Types.Teams import Imports import Network.HTTP.Types import Network.Wai @@ -77,6 +81,7 @@ import qualified Wire.API.Federation.API.Galley as F import Wire.API.Federation.Error import qualified Wire.API.Provider.Bot as Public import qualified Wire.API.Routes.MultiTablePaging as Public +import Wire.API.Team.Feature as Public getBotConversationH :: Members '[ConversationStore, Error ConversationError, Input (Local ())] r => @@ -490,16 +495,17 @@ getConversationMeta cnv = do pure Nothing getConversationByReusableCode :: - Members - '[ BrigAccess, - CodeStore, - ConversationStore, - Error CodeError, - Error ConversationError, - Error NotATeamMember, - TeamStore - ] - r => + forall r. + ( Member BrigAccess r, + Member CodeStore r, + Member ConversationStore r, + Member (Error CodeError) r, + Member (Error ConversationError) r, + Member (Error NotATeamMember) r, + Member TeamStore r, + Member TeamFeatureStore r, + Member (Input Opts) r + ) => Local UserId -> Key -> Value -> @@ -507,7 +513,9 @@ getConversationByReusableCode :: getConversationByReusableCode lusr key value = do c <- verifyReusableCode (ConversationCode key value Nothing) conv <- ensureConversationAccess (tUnqualified lusr) (Data.codeConversation c) CodeAccess - pure $ coverView conv + getFeatureStatus conv >>= \case + TeamFeatureEnabled -> pure $ coverView conv + TeamFeatureDisabled -> throw GuestLinksDisabled where coverView :: Data.Conversation -> ConversationCoverView coverView conv = @@ -515,3 +523,14 @@ getConversationByReusableCode lusr key value = do { cnvCoverConvId = Data.convId conv, cnvCoverName = Data.convName conv } + + getDefaultFeatureStatus :: Sem r TeamFeatureStatusValue + getDefaultFeatureStatus = do + status <- input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults) + pure $ tfwoapsStatus status + + getFeatureStatus :: Data.Conversation -> Sem r TeamFeatureStatusValue + getFeatureStatus conv = do + defaultStatus <- getDefaultFeatureStatus + maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` Data.convTeam conv + pure $ maybe defaultStatus tfwoStatus maybeFeatureStatus diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index 63ef91cd19..ff250f4d21 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -17,6 +17,7 @@ module Galley.API.Teams.Features ( getFeatureStatus, + getFeatureStatusNoConfig, setFeatureStatus, getFeatureConfig, getAllFeatureConfigs, From 0439cefc4c3be631ef9351f9656f4f9b929616a9 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 9 Dec 2021 12:08:45 +0000 Subject: [PATCH 2/9] changelog --- changelog.d/2-features/pr-1976 | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/2-features/pr-1976 diff --git a/changelog.d/2-features/pr-1976 b/changelog.d/2-features/pr-1976 new file mode 100644 index 0000000000..e67dd816a0 --- /dev/null +++ b/changelog.d/2-features/pr-1976 @@ -0,0 +1 @@ +If the guest links team feature is disabled guest links will be revoked. From 085e912504c82e302e88e839c28c78639921ed0a Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Thu, 9 Dec 2021 17:15:15 +0100 Subject: [PATCH 3/9] clean up Co-authored-by: fisx --- services/galley/src/Galley/API/Query.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 0dedd43d2d..ace30d6eec 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -525,9 +525,8 @@ getConversationByReusableCode lusr key value = do } getDefaultFeatureStatus :: Sem r TeamFeatureStatusValue - getDefaultFeatureStatus = do - status <- input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults) - pure $ tfwoapsStatus status + getDefaultFeatureStatus = + input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults . to tfwoapsStatus) getFeatureStatus :: Data.Conversation -> Sem r TeamFeatureStatusValue getFeatureStatus conv = do From 8d4fdc1519cb779e796b773887cf3ad87c791e1b Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Fri, 10 Dec 2021 12:50:58 +0000 Subject: [PATCH 4/9] test for revoking guest links --- services/galley/src/Galley/API/Query.hs | 3 ++- services/galley/test/integration/API.hs | 25 ++++++++++++++++++++ services/galley/test/integration/API/Util.hs | 8 ++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index ace30d6eec..02f9567354 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -45,6 +45,7 @@ import Data.Proxy import Data.Qualified import Data.Range import qualified Data.Set as Set +import Debug.Trace import Galley.API.Error import qualified Galley.API.Mapping as Mapping import Galley.API.Util @@ -531,5 +532,5 @@ getConversationByReusableCode lusr key value = do getFeatureStatus :: Data.Conversation -> Sem r TeamFeatureStatusValue getFeatureStatus conv = do defaultStatus <- getDefaultFeatureStatus - maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` Data.convTeam conv + maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` (traceShowId $ Data.convTeam conv) pure $ maybe defaultStatus tfwoStatus maybeFeatureStatus diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 13d41e2b80..8778eef1fe 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -33,6 +33,8 @@ import qualified API.Teams.Feature as TeamFeature import qualified API.Teams.LegalHold as Teams.LegalHold import qualified API.Teams.LegalHold.DisabledByDefault import API.Util +import qualified API.Util as Util +import API.Util.TeamFeature as TeamFeatures import Bilge hiding (timeout) import Bilge.Assert import Brig.Types @@ -93,6 +95,7 @@ import Wire.API.Federation.API.Galley import qualified Wire.API.Federation.API.Galley as F import qualified Wire.API.Message as Message import Wire.API.Routes.MultiTablePaging +import qualified Wire.API.Team.Feature as Public import Wire.API.User.Client import Wire.API.UserMap (UserMap (..)) @@ -218,6 +221,7 @@ tests s = test s "convert code to team-access conversation" postConvertTeamConv, test s "local and remote guests are removed when access changes" testAccessUpdateGuestRemoved, test s "cannot join private conversation" postJoinConvFail, + test s "join conversation when guest links are disabled" testJoinConvGuestLinksDisabled, test s "remove user with only local convs" removeUserNoFederation, test s "remove user with local and remote convs" removeUser, test s "iUpsertOne2OneConversation" testAllOne2OneConversationRequests, @@ -1236,6 +1240,27 @@ testJoinCodeConv = do getJoinCodeConv eve (conversationKey cCode) (conversationCode cCode) !!! do const 403 === statusCode +testJoinConvGuestLinksDisabled :: TestM () +testJoinConvGuestLinksDisabled = do + galley <- view tsGalley + let convName = "testConversation" + (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 + userNotInTeam <- randomUser + convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just ActivatedAccessRole) Nothing + cCode <- decodeConvCodeEvent <$> postConvCode owner convId + + getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const 200 === statusCode + + let tfStatus = Public.TeamFeatureStatusNoConfig Public.TeamFeatureDisabled + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus !!! do + const 200 === statusCode + + -- TODO(leif): this assertion fails because the team is not found in the server implementation + getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + const 409 === statusCode + postJoinCodeConvOk :: TestM () postJoinCodeConvOk = do c <- view tsCannon diff --git a/services/galley/test/integration/API/Util.hs b/services/galley/test/integration/API/Util.hs index b1cb862d55..31af5c651a 100644 --- a/services/galley/test/integration/API/Util.hs +++ b/services/galley/test/integration/API/Util.hs @@ -1738,7 +1738,10 @@ randomTeamCreator :: HasCallStack => TestM UserId randomTeamCreator = qUnqualified <$> randomUser' True True True randomUser' :: HasCallStack => Bool -> Bool -> Bool -> TestM (Qualified UserId) -randomUser' isCreator hasPassword hasEmail = do +randomUser' isCreator hasPassword hasEmail = userQualifiedId . selfUser <$> randomUserProfile' isCreator hasPassword hasEmail + +randomUserProfile' :: HasCallStack => Bool -> Bool -> Bool -> TestM SelfProfile +randomUserProfile' isCreator hasPassword hasEmail = do b <- view tsBrig e <- liftIO randomEmail let p = @@ -1747,8 +1750,7 @@ randomUser' isCreator hasPassword hasEmail = do <> ["password" .= defPassword | hasPassword] <> ["email" .= fromEmail e | hasEmail] <> ["team" .= Team.BindingNewTeam (Team.newNewTeam (unsafeRange "teamName") (unsafeRange "defaultIcon")) | isCreator] - selfProfile <- responseJsonUnsafe <$> (post (b . path "/i/users" . json p) (post (b . path "/i/users" . json p) TestM UserId ephemeralUser = do From b98993b5aa6f4ab6d39cfc2dbe59eb2377401259 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Fri, 17 Dec 2021 11:37:50 +0100 Subject: [PATCH 5/9] Remove Debug.Trace. --- services/galley/src/Galley/API/Query.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 02f9567354..1f4e02d437 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -45,7 +45,6 @@ import Data.Proxy import Data.Qualified import Data.Range import qualified Data.Set as Set -import Debug.Trace import Galley.API.Error import qualified Galley.API.Mapping as Mapping import Galley.API.Util @@ -532,5 +531,5 @@ getConversationByReusableCode lusr key value = do getFeatureStatus :: Data.Conversation -> Sem r TeamFeatureStatusValue getFeatureStatus conv = do defaultStatus <- getDefaultFeatureStatus - maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` (traceShowId $ Data.convTeam conv) + maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` (Data.convTeam conv) pure $ maybe defaultStatus tfwoStatus maybeFeatureStatus From af56f98bc83fbf37667c79f8b3722de82154254e Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Fri, 17 Dec 2021 13:06:17 +0100 Subject: [PATCH 6/9] Extend test coverage (a little). --- services/galley/test/integration/API.hs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 8778eef1fe..6db6334973 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -1249,10 +1249,12 @@ testJoinConvGuestLinksDisabled = do convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just ActivatedAccessRole) Nothing cCode <- decodeConvCodeEvent <$> postConvCode owner convId + -- works by default getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither const 200 === statusCode + -- fails if disabled let tfStatus = Public.TeamFeatureStatusNoConfig Public.TeamFeatureDisabled TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus !!! do const 200 === statusCode @@ -1261,6 +1263,15 @@ testJoinConvGuestLinksDisabled = do getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do const 409 === statusCode + -- after re-enabeling, the old link is still valid + let tfStatus' = Public.TeamFeatureStatusNoConfig Public.TeamFeatureEnabled + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus' !!! do + const 200 === statusCode + + getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const 200 === statusCode + postJoinCodeConvOk :: TestM () postJoinCodeConvOk = do c <- view tsCannon From 819a1d7504fe74a5fb6586b89aa6a46e3cf8adf7 Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Fri, 17 Dec 2021 13:11:03 +0100 Subject: [PATCH 7/9] Fixup --- services/galley/test/integration/API.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 6db6334973..6805eae16b 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -1246,7 +1246,7 @@ testJoinConvGuestLinksDisabled = do let convName = "testConversation" (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 userNotInTeam <- randomUser - convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just ActivatedAccessRole) Nothing + convId <- decodeConvId <$> postTeamConv teamId owner [] (Just convName) [CodeAccess] (Just ActivatedAccessRole) Nothing cCode <- decodeConvCodeEvent <$> postConvCode owner convId -- works by default @@ -1259,7 +1259,6 @@ testJoinConvGuestLinksDisabled = do TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus !!! do const 200 === statusCode - -- TODO(leif): this assertion fails because the team is not found in the server implementation getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do const 409 === statusCode From c93de6134fcb23c8768487e308530b2e6cd1e11c Mon Sep 17 00:00:00 2001 From: Matthias Fischmann Date: Fri, 17 Dec 2021 13:49:49 +0100 Subject: [PATCH 8/9] hi ci From 0b34bfb0ed23ee168cfea753ca4b1547e6cbd0e2 Mon Sep 17 00:00:00 2001 From: Leif Battermann Date: Fri, 17 Dec 2021 13:41:19 +0000 Subject: [PATCH 9/9] check non-team conversations --- services/galley/src/Galley/API/Query.hs | 2 +- services/galley/test/integration/API.hs | 32 +++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index 1f4e02d437..ace30d6eec 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -531,5 +531,5 @@ getConversationByReusableCode lusr key value = do getFeatureStatus :: Data.Conversation -> Sem r TeamFeatureStatusValue getFeatureStatus conv = do defaultStatus <- getDefaultFeatureStatus - maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` (Data.convTeam conv) + maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` Data.convTeam conv pure $ maybe defaultStatus tfwoStatus maybeFeatureStatus diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 6805eae16b..3d2706dd4e 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -221,7 +221,8 @@ tests s = test s "convert code to team-access conversation" postConvertTeamConv, test s "local and remote guests are removed when access changes" testAccessUpdateGuestRemoved, test s "cannot join private conversation" postJoinConvFail, - test s "join conversation when guest links are disabled" testJoinConvGuestLinksDisabled, + test s "revoke guest links for team conversation" testJoinTeamConvGuestLinksDisabled, + test s "revoke guest links for non-team conversation" testJoinNonTeamConvGuestLinksDisabled, test s "remove user with only local convs" removeUserNoFederation, test s "remove user with local and remote convs" removeUser, test s "iUpsertOne2OneConversation" testAllOne2OneConversationRequests, @@ -1240,8 +1241,8 @@ testJoinCodeConv = do getJoinCodeConv eve (conversationKey cCode) (conversationCode cCode) !!! do const 403 === statusCode -testJoinConvGuestLinksDisabled :: TestM () -testJoinConvGuestLinksDisabled = do +testJoinTeamConvGuestLinksDisabled :: TestM () +testJoinTeamConvGuestLinksDisabled = do galley <- view tsGalley let convName = "testConversation" (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 @@ -1262,7 +1263,7 @@ testJoinConvGuestLinksDisabled = do getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do const 409 === statusCode - -- after re-enabeling, the old link is still valid + -- after re-enabling, the old link is still valid let tfStatus' = Public.TeamFeatureStatusNoConfig Public.TeamFeatureEnabled TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus' !!! do const 200 === statusCode @@ -1271,6 +1272,29 @@ testJoinConvGuestLinksDisabled = do const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither const 200 === statusCode +testJoinNonTeamConvGuestLinksDisabled :: TestM () +testJoinNonTeamConvGuestLinksDisabled = do + galley <- view tsGalley + let convName = "testConversation" + (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 + userNotInTeam <- randomUser + convId <- decodeConvId <$> postConv owner [] (Just convName) [CodeAccess] (Just ActivatedAccessRole) Nothing + cCode <- decodeConvCodeEvent <$> postConvCode owner convId + + -- works by default + getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const 200 === statusCode + + -- for non-team conversations it still works if status is disabled for the team but not server wide + let tfStatus = Public.TeamFeatureStatusNoConfig Public.TeamFeatureDisabled + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId tfStatus !!! do + const 200 === statusCode + + getJoinCodeConv userNotInTeam (conversationKey cCode) (conversationCode cCode) !!! do + const (Right (ConversationCoverView convId (Just convName))) === responseJsonEither + const 200 === statusCode + postJoinCodeConvOk :: TestM () postJoinCodeConvOk = do c <- view tsCannon