diff --git a/changelog.d/2-features/pr-1980 b/changelog.d/2-features/pr-1980 new file mode 100644 index 0000000000..1eafba32ca --- /dev/null +++ b/changelog.d/2-features/pr-1980 @@ -0,0 +1 @@ +Revoke guest links if feature is disabled. If the guest links team feature is disabled `get /conversations/join`, `post /conversations/:cnv/code`, and `get /conversations/:cnv/code` will return an error. diff --git a/services/galley/src/Galley/API/Query.hs b/services/galley/src/Galley/API/Query.hs index ace30d6eec..736e401397 100644 --- a/services/galley/src/Galley/API/Query.hs +++ b/services/galley/src/Galley/API/Query.hs @@ -30,6 +30,7 @@ module Galley.API.Query internalGetMemberH, getConversationMetaH, getConversationByReusableCode, + ensureGuestLinksEnabled, ) where @@ -513,9 +514,8 @@ getConversationByReusableCode :: getConversationByReusableCode lusr key value = do c <- verifyReusableCode (ConversationCode key value Nothing) conv <- ensureConversationAccess (tUnqualified lusr) (Data.codeConversation c) CodeAccess - getFeatureStatus conv >>= \case - TeamFeatureEnabled -> pure $ coverView conv - TeamFeatureDisabled -> throw GuestLinksDisabled + ensureGuestLinksEnabled conv + pure $ coverView conv where coverView :: Data.Conversation -> ConversationCoverView coverView conv = @@ -524,12 +524,23 @@ getConversationByReusableCode lusr key value = do cnvCoverName = Data.convName conv } +-- FUTUREWORK(leif): refactor and make it consistent for all team features +ensureGuestLinksEnabled :: + forall r. + ( Member (Error ConversationError) r, + Member TeamFeatureStore r, + Member (Input Opts) r + ) => + Data.Conversation -> + Sem r () +ensureGuestLinksEnabled conv = do + defaultStatus <- getDefaultFeatureStatus + maybeFeatureStatus <- join <$> TeamFeatures.getFeatureStatusNoConfig @'TeamFeatureGuestLinks `traverse` Data.convTeam conv + case maybe defaultStatus tfwoStatus maybeFeatureStatus of + TeamFeatureEnabled -> pure () + TeamFeatureDisabled -> throw GuestLinksDisabled + where getDefaultFeatureStatus :: Sem r TeamFeatureStatusValue - getDefaultFeatureStatus = - input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults . to tfwoapsStatus) - - 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 + getDefaultFeatureStatus = do + status <- input <&> view (optSettings . setFeatureFlags . flagConversationGuestLinks . unDefaults) + pure $ tfwoapsStatus status diff --git a/services/galley/src/Galley/API/Update.hs b/services/galley/src/Galley/API/Update.hs index 26af1fabcb..aec91abde6 100644 --- a/services/galley/src/Galley/API/Update.hs +++ b/services/galley/src/Galley/API/Update.hs @@ -82,6 +82,7 @@ import Galley.API.Error import Galley.API.LegalHold.Conflicts import Galley.API.Mapping import Galley.API.Message +import qualified Galley.API.Query as Query import Galley.API.Util import qualified Galley.Data.Conversation as Data import Galley.Data.Services as Data @@ -522,16 +523,17 @@ getUpdateResult :: Sem (Error NoChanges ': r) a -> Sem r (UpdateResult a) getUpdateResult = fmap (either (const Unchanged) Updated) . runError addCodeH :: - Members - '[ CodeStore, - ConversationStore, - Error ConversationError, - ExternalAccess, - GundeckAccess, - Input (Local ()), - Input UTCTime - ] - r => + forall r. + ( Member CodeStore r, + Member ConversationStore r, + Member (Error ConversationError) r, + Member ExternalAccess r, + Member GundeckAccess r, + Member (Input (Local ())) r, + Member (Input UTCTime) r, + Member (Input Opts) r, + Member TeamFeatureStore r + ) => UserId ::: ConnId ::: ConvId -> Sem r Response addCodeH (usr ::: zcon ::: cnv) = do @@ -547,21 +549,22 @@ data AddCodeResult addCode :: forall r. - Members - '[ CodeStore, - ConversationStore, - Error ConversationError, - ExternalAccess, - GundeckAccess, - Input UTCTime - ] - r => + ( Member CodeStore r, + Member ConversationStore r, + Member (Error ConversationError) r, + Member ExternalAccess r, + Member GundeckAccess r, + Member (Input UTCTime) r, + Member (Input Opts) r, + Member TeamFeatureStore r + ) => Local UserId -> ConnId -> Local ConvId -> Sem r AddCodeResult addCode lusr zcon lcnv = do conv <- E.getConversation (tUnqualified lcnv) >>= note ConvNotFound + Query.ensureGuestLinksEnabled conv ensureConvMember (Data.convLocalMembers conv) (tUnqualified lusr) ensureAccess conv CodeAccess let (bots, users) = localBotsAndUsers $ Data.convLocalMembers conv @@ -582,8 +585,7 @@ addCode lusr zcon lcnv = do where createCode :: Code -> Sem r ConversationCode createCode code = do - urlPrefix <- E.getConversationCodeURI - return $ mkConversationCode (codeKey code) (codeValue code) urlPrefix + mkConversationCode (codeKey code) (codeValue code) <$> E.getConversationCodeURI rmCodeH :: Members @@ -631,32 +633,35 @@ rmCode lusr zcon lcnv = do pure event getCodeH :: - Members - '[ CodeStore, - ConversationStore, - Error CodeError, - Error ConversationError - ] - r => + forall r. + ( Member CodeStore r, + Member ConversationStore r, + Member (Error CodeError) r, + Member (Error ConversationError) r, + Member (Input Opts) r, + Member TeamFeatureStore r + ) => UserId ::: ConvId -> Sem r Response getCodeH (usr ::: cnv) = setStatus status200 . json <$> getCode usr cnv getCode :: - Members - '[ CodeStore, - ConversationStore, - Error CodeError, - Error ConversationError - ] - r => + forall r. + ( Member CodeStore r, + Member ConversationStore r, + Member (Error CodeError) r, + Member (Error ConversationError) r, + Member (Input Opts) r, + Member TeamFeatureStore r + ) => UserId -> ConvId -> Sem r Public.ConversationCode getCode usr cnv = do conv <- E.getConversation cnv >>= note ConvNotFound + Query.ensureGuestLinksEnabled conv ensureAccess conv CodeAccess ensureConvMember (Data.convLocalMembers conv) usr key <- E.makeKey cnv @@ -665,8 +670,7 @@ getCode usr cnv = do returnCode :: Member CodeStore r => Code -> Sem r Public.ConversationCode returnCode c = do - urlPrefix <- E.getConversationCodeURI - pure $ Public.mkConversationCode (codeKey c) (codeValue c) urlPrefix + Public.mkConversationCode (codeKey c) (codeValue c) <$> E.getConversationCodeURI checkReusableCodeH :: Members '[CodeStore, Error CodeError, WaiRoutes] r => diff --git a/services/galley/test/integration/API.hs b/services/galley/test/integration/API.hs index 3d2706dd4e..5a29641a60 100644 --- a/services/galley/test/integration/API.hs +++ b/services/galley/test/integration/API.hs @@ -223,6 +223,8 @@ tests s = test s "cannot join private conversation" postJoinConvFail, test s "revoke guest links for team conversation" testJoinTeamConvGuestLinksDisabled, test s "revoke guest links for non-team conversation" testJoinNonTeamConvGuestLinksDisabled, + test s "get code rejected if guest links disabled" testGetCodeRejectedIfGuestLinksDisabled, + test s "post code rejected if guest links disabled" testPostCodeRejectedIfGuestLinksDisabled, test s "remove user with only local convs" removeUserNoFederation, test s "remove user with local and remote convs" removeUser, test s "iUpsertOne2OneConversation" testAllOne2OneConversationRequests, @@ -1241,6 +1243,42 @@ testJoinCodeConv = do getJoinCodeConv eve (conversationKey cCode) (conversationCode cCode) !!! do const 403 === statusCode +testGetCodeRejectedIfGuestLinksDisabled :: TestM () +testGetCodeRejectedIfGuestLinksDisabled = do + galley <- view tsGalley + (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 + let createConvWithGuestLink = do + convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just ActivatedAccessRole) Nothing + void $ decodeConvCodeEvent <$> postConvCode owner convId + pure convId + convId <- createConvWithGuestLink + let checkGetCode expectedStatus = getConvCode owner convId !!! statusCode === const expectedStatus + let setStatus tfStatus = + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId (Public.TeamFeatureStatusNoConfig tfStatus) !!! do + const 200 === statusCode + + checkGetCode 200 + setStatus Public.TeamFeatureDisabled + checkGetCode 409 + setStatus Public.TeamFeatureEnabled + checkGetCode 200 + +testPostCodeRejectedIfGuestLinksDisabled :: TestM () +testPostCodeRejectedIfGuestLinksDisabled = do + galley <- view tsGalley + (owner, teamId, []) <- Util.createBindingTeamWithNMembers 0 + convId <- decodeConvId <$> postTeamConv teamId owner [] (Just "testConversation") [CodeAccess] (Just ActivatedAccessRole) Nothing + let checkPostCode expectedStatus = postConvCode owner convId !!! statusCode === const expectedStatus + let setStatus tfStatus = + TeamFeatures.putTeamFeatureFlagWithGalley @'Public.TeamFeatureGuestLinks galley owner teamId (Public.TeamFeatureStatusNoConfig tfStatus) !!! do + const 200 === statusCode + + checkPostCode 201 + setStatus Public.TeamFeatureDisabled + checkPostCode 409 + setStatus Public.TeamFeatureEnabled + checkPostCode 200 + testJoinTeamConvGuestLinksDisabled :: TestM () testJoinTeamConvGuestLinksDisabled = do galley <- view tsGalley