From e25b51aa3cb568aabde468e0754da43ce0980041 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 28 May 2021 15:18:31 +0200 Subject: [PATCH 1/9] ensureNotTooLargeForLegalHold: add exception code golf --- services/galley/src/Galley/API/Teams.hs | 41 +++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index aaae062fb75..683d33f48c9 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -53,6 +53,7 @@ module Galley.API.Teams canUserJoinTeamH, internalDeleteBindingTeamWithOneMemberH, internalDeleteBindingTeamWithOneMember, + ensureNotTooLargeForLegalHold, ) where @@ -82,6 +83,7 @@ import Galley.API.LegalHold import qualified Galley.API.Teams.Notifications as APITeamQueue import Galley.API.Util import Galley.App +import Galley.Data (teamMembersForFanout) import qualified Galley.Data as Data import qualified Galley.Data.SearchVisibility as SearchVisibilityData import Galley.Data.Services (BotMember) @@ -94,6 +96,7 @@ import qualified Galley.Intra.Spar as Spar import qualified Galley.Intra.Team as BrigTeam import Galley.Intra.User import Galley.Options +import qualified Galley.Options as Opts import qualified Galley.Queue as Q import Galley.Types (UserIdList (UserIdList)) import qualified Galley.Types as Conv @@ -574,7 +577,7 @@ addTeamMember zusr zcon tid nmem = do ensureConnectedToLocals zusr [uid] memList <- Data.teamMembersForFanout tid -- FUTUREWORK: We cannot enable legalhold on large teams right now - ensureNotTooLargeForLegalHold tid memList + ensureNotTooLargeForLegalHold tid (Just memList) void $ addTeamMemberInternal tid (Just zusr) (Just zcon) nmem memList -- This function is "unchecked" because there is no need to check for user binding (invite only). @@ -588,7 +591,7 @@ uncheckedAddTeamMember :: TeamId -> NewTeamMember -> Galley () uncheckedAddTeamMember tid nmem = do mems <- Data.teamMembersForFanout tid -- FUTUREWORK: We cannot enable legalhold on large teams right now - ensureNotTooLargeForLegalHold tid mems + ensureNotTooLargeForLegalHold tid (Just mems) (TeamSize sizeBeforeAdd) <- addTeamMemberInternal tid Nothing Nothing nmem mems billingUserIds <- Journal.getBillingUserIds tid $ Just $ newTeamMemberList ((nmem ^. ntmNewTeamMember) : mems ^. teamMembers) (mems ^. teamMemberListType) Journal.teamUpdate tid (sizeBeforeAdd + 1) billingUserIds @@ -856,15 +859,29 @@ ensureNotTooLarge tid = do throwM tooManyTeamMembers return $ TeamSize size --- FUTUREWORK: Large teams cannot have legalhold enabled, needs rethinking --- due to the expensive operation of removing settings -ensureNotTooLargeForLegalHold :: TeamId -> TeamMemberList -> Galley () -ensureNotTooLargeForLegalHold tid mems = do - limit <- fromIntegral . fromRange <$> fanoutLimit - when (length (mems ^. teamMembers) >= limit) $ do - lhEnabled <- isLegalHoldEnabledForTeam tid - when lhEnabled $ - throwM tooManyTeamMembersOnTeamWithLegalhold +-- | Ensure that a team doesn't exceed the member count limit for the LegalHold +-- feature. A team with more members than the fanout limit is too large, because +-- the fanout limit would prevent turning LegalHold feature _off_ again (for +-- details see 'Galley.API.LegalHold.removeSettings'). +-- +-- If LegalHold is configured for whitelisted teams only we consider the team +-- size unlimited, because we make the assumption that these teams won't turn +-- LegalHold off after activation. +-- FUTUREWORK: Find a way around the fanout limit. +ensureNotTooLargeForLegalHold :: TeamId -> Maybe TeamMemberList -> Galley () +ensureNotTooLargeForLegalHold tid mMems = do + let assertTeamBelowFanoutLimit :: Galley () + assertTeamBelowFanoutLimit = do + mems <- maybe (teamMembersForFanout tid) pure mMems + limit <- fromIntegral . fromRange <$> fanoutLimit + when (length (mems ^. teamMembers) >= limit) $ do + throwM tooManyTeamMembersOnTeamWithLegalhold + + whenM (isLegalHoldEnabledForTeam tid) $ + view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case + FeatureLegalHoldDisabledPermanently -> assertTeamBelowFanoutLimit -- impossible case + FeatureLegalHoldDisabledByDefault -> assertTeamBelowFanoutLimit + FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> pure () addTeamMemberInternal :: TeamId -> Maybe UserId -> Maybe ConnId -> NewTeamMember -> TeamMemberList -> Galley TeamSize addTeamMemberInternal tid origin originConn (view ntmNewTeamMember -> new) memList = do @@ -956,7 +973,7 @@ canUserJoinTeamH tid = canUserJoinTeam tid >> pure empty canUserJoinTeam :: TeamId -> Galley () canUserJoinTeam tid = do lhEnabled <- isLegalHoldEnabledForTeam tid - when (lhEnabled) $ + when lhEnabled $ checkTeamSize where checkTeamSize = do From 967657515f874668d5e2e7bd683a7c55481f4445 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 28 May 2021 15:29:52 +0200 Subject: [PATCH 2/9] removeSettings: add error if whitelisting teams up --- services/galley/src/Galley/API/Error.hs | 3 +++ services/galley/src/Galley/API/LegalHold.hs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs index 1a985290986..80a2a1c3ed7 100644 --- a/services/galley/src/Galley/API/Error.hs +++ b/services/galley/src/Galley/API/Error.hs @@ -206,6 +206,9 @@ legalHoldFeatureFlagNotEnabled = Error status403 "legalhold-not-enabled" "legal legalHoldNotEnabled :: Error legalHoldNotEnabled = Error status403 "legalhold-not-enabled" "legal hold is not enabled for this team" +legalHoldDisableUnimplemented :: Error +legalHoldDisableUnimplemented = Error status403 "legalhold-disable-unimplemented" "legal hold cannot be disabled for whitelisted teams" + userLegalHoldAlreadyEnabled :: Error userLegalHoldAlreadyEnabled = Error status409 "legalhold-already-enabled" "legal hold is already enabled for this user" diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs index 9feaafed524..1cf6137c900 100644 --- a/services/galley/src/Galley/API/LegalHold.hs +++ b/services/galley/src/Galley/API/LegalHold.hs @@ -133,6 +133,7 @@ removeSettingsH (zusr ::: tid ::: req ::: _) = do removeSettings :: UserId -> TeamId -> Public.RemoveLegalHoldSettingsRequest -> Galley () removeSettings zusr tid (Public.RemoveLegalHoldSettingsRequest mPassword) = do + assertNotWhitelisting assertLegalHoldEnabledForTeam tid zusrMembership <- Data.teamMember tid zusr -- let zothers = map (view userId) membs @@ -142,6 +143,14 @@ removeSettings zusr tid (Public.RemoveLegalHoldSettingsRequest mPassword) = do void $ permissionCheck ChangeLegalHoldTeamSettings zusrMembership ensureReAuthorised zusr mPassword removeSettings' tid + where + assertNotWhitelisting :: Galley () + assertNotWhitelisting = do + view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case + FeatureLegalHoldDisabledPermanently -> pure () + FeatureLegalHoldDisabledByDefault -> pure () + FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> do + throwM legalHoldDisableUnimplemented -- | Remove legal hold settings from team; also disabling for all users and removing LH devices removeSettings' :: From f89897fe6ce7417e3b652fbce193e80df3f21987 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 28 May 2021 15:44:43 +0200 Subject: [PATCH 3/9] setLegalholdStatusInternal: Use same guard --- services/galley/src/Galley/API/Teams/Features.hs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index db98f29cfc0..27ae9bd43f5 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -35,22 +35,20 @@ module Galley.API.Teams.Features ) where -import Brig.Types.Team (TeamSize (..)) import Control.Lens import Control.Monad.Catch import qualified Data.Aeson as Aeson import Data.ByteString.Conversion hiding (fromList) import Data.Id -import Data.Range as Range import Data.String.Conversions (cs) import Galley.API.Error as Galley import Galley.API.LegalHold +import Galley.API.Teams (ensureNotTooLargeForLegalHold) import Galley.API.Util import Galley.App import qualified Galley.Data as Data import qualified Galley.Data.SearchVisibility as SearchVisibilityData import qualified Galley.Data.TeamFeatures as TeamFeatures -import qualified Galley.Intra.Team as BrigTeam import Galley.Options import Galley.Types.Teams hiding (newTeam) import Imports @@ -204,15 +202,9 @@ setLegalholdStatusInternal tid status@(Public.tfwoStatus -> statusValue) = do throwM legalHoldWhitelistedOnly case statusValue of Public.TeamFeatureDisabled -> removeSettings' tid - -- FUTUREWORK: We cannot enable legalhold on large teams right now - Public.TeamFeatureEnabled -> checkTeamSize + Public.TeamFeatureEnabled -> do + ensureNotTooLargeForLegalHold tid Nothing TeamFeatures.setFeatureStatusNoConfig @'Public.TeamFeatureLegalHold tid status - where - checkTeamSize = do - (TeamSize size) <- BrigTeam.getSize tid - limit <- fromIntegral . fromRange <$> fanoutLimit - when (size > limit) $ do - throwM cannotEnableLegalHoldServiceLargeTeam getAppLockInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureAppLock) getAppLockInternal tid = do From 6b23e7a0f5e79fec01f556028c40c81793350a67 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 28 May 2021 15:57:03 +0200 Subject: [PATCH 4/9] canUserJoinTeam: use same guard --- services/galley/src/Galley/API/Teams.hs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 683d33f48c9..4e572ca4fa1 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -973,15 +973,8 @@ canUserJoinTeamH tid = canUserJoinTeam tid >> pure empty canUserJoinTeam :: TeamId -> Galley () canUserJoinTeam tid = do lhEnabled <- isLegalHoldEnabledForTeam tid - when lhEnabled $ - checkTeamSize - where - checkTeamSize = do - (TeamSize size) <- BrigTeam.getSize tid - limit <- fromIntegral . fromRange <$> fanoutLimit - -- Teams larger than fanout limit cannot use legalhold - when (size >= limit) $ do - throwM tooManyTeamMembersOnTeamWithLegalhold + when lhEnabled $ do + ensureNotTooLargeForLegalHold tid Nothing getTeamSearchVisibilityAvailableInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureSearchVisibility) getTeamSearchVisibilityAvailableInternal tid = do From fdf9e66947bb8e6468588d85c264fc5aab5f4612 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 28 May 2021 19:04:29 +0200 Subject: [PATCH 5/9] factor out separate check for activation hook --- services/galley/src/Galley/API/Teams.hs | 42 +++++++++++-------- .../galley/src/Galley/API/Teams/Features.hs | 4 +- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 4e572ca4fa1..ef11f7ea3cd 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -54,6 +54,7 @@ module Galley.API.Teams internalDeleteBindingTeamWithOneMemberH, internalDeleteBindingTeamWithOneMember, ensureNotTooLargeForLegalHold, + ensureNotTooLargeToActivateLegalHold, ) where @@ -577,7 +578,7 @@ addTeamMember zusr zcon tid nmem = do ensureConnectedToLocals zusr [uid] memList <- Data.teamMembersForFanout tid -- FUTUREWORK: We cannot enable legalhold on large teams right now - ensureNotTooLargeForLegalHold tid (Just memList) + ensureNotTooLargeForLegalHold tid (length (memList ^. teamMembers) + 1) void $ addTeamMemberInternal tid (Just zusr) (Just zcon) nmem memList -- This function is "unchecked" because there is no need to check for user binding (invite only). @@ -591,7 +592,7 @@ uncheckedAddTeamMember :: TeamId -> NewTeamMember -> Galley () uncheckedAddTeamMember tid nmem = do mems <- Data.teamMembersForFanout tid -- FUTUREWORK: We cannot enable legalhold on large teams right now - ensureNotTooLargeForLegalHold tid (Just mems) + ensureNotTooLargeForLegalHold tid (length (mems ^. teamMembers) + 1) (TeamSize sizeBeforeAdd) <- addTeamMemberInternal tid Nothing Nothing nmem mems billingUserIds <- Journal.getBillingUserIds tid $ Just $ newTeamMemberList ((nmem ^. ntmNewTeamMember) : mems ^. teamMembers) (mems ^. teamMemberListType) Journal.teamUpdate tid (sizeBeforeAdd + 1) billingUserIds @@ -868,20 +869,26 @@ ensureNotTooLarge tid = do -- size unlimited, because we make the assumption that these teams won't turn -- LegalHold off after activation. -- FUTUREWORK: Find a way around the fanout limit. -ensureNotTooLargeForLegalHold :: TeamId -> Maybe TeamMemberList -> Galley () -ensureNotTooLargeForLegalHold tid mMems = do - let assertTeamBelowFanoutLimit :: Galley () - assertTeamBelowFanoutLimit = do - mems <- maybe (teamMembersForFanout tid) pure mMems - limit <- fromIntegral . fromRange <$> fanoutLimit - when (length (mems ^. teamMembers) >= limit) $ do - throwM tooManyTeamMembersOnTeamWithLegalhold - - whenM (isLegalHoldEnabledForTeam tid) $ - view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case - FeatureLegalHoldDisabledPermanently -> assertTeamBelowFanoutLimit -- impossible case - FeatureLegalHoldDisabledByDefault -> assertTeamBelowFanoutLimit - FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> pure () +ensureNotTooLargeForLegalHold :: TeamId -> Int -> Galley () +ensureNotTooLargeForLegalHold tid nMembersFanout = do + whenM (isLegalHoldEnabledForTeam tid) $ do + unlessM (teamSizeNotTooLargeForLegalhold nMembersFanout) $ do + throwM tooManyTeamMembersOnTeamWithLegalhold + +ensureNotTooLargeToActivateLegalHold :: TeamId -> Galley () +ensureNotTooLargeToActivateLegalHold tid = do + mems <- teamMembersForFanout tid + unlessM (teamSizeNotTooLargeForLegalhold (length (mems ^. teamMembers))) $ do + throwM cannotEnableLegalHoldServiceLargeTeam + +teamSizeNotTooLargeForLegalhold :: Int -> Galley Bool +teamSizeNotTooLargeForLegalhold nMembersFanout = do + limit <- fromIntegral . fromRange <$> fanoutLimit + let withinLimit = nMembersFanout <= limit + view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case + FeatureLegalHoldDisabledPermanently -> pure withinLimit + FeatureLegalHoldDisabledByDefault -> pure withinLimit + FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> pure True -- no limit, see 'ensureNotTooLargeForLegalHold' addTeamMemberInternal :: TeamId -> Maybe UserId -> Maybe ConnId -> NewTeamMember -> TeamMemberList -> Galley TeamSize addTeamMemberInternal tid origin originConn (view ntmNewTeamMember -> new) memList = do @@ -974,7 +981,8 @@ canUserJoinTeam :: TeamId -> Galley () canUserJoinTeam tid = do lhEnabled <- isLegalHoldEnabledForTeam tid when lhEnabled $ do - ensureNotTooLargeForLegalHold tid Nothing + mems <- Data.teamMembersForFanout tid + ensureNotTooLargeForLegalHold tid (length (mems ^. teamMembers) + 1) getTeamSearchVisibilityAvailableInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureSearchVisibility) getTeamSearchVisibilityAvailableInternal tid = do diff --git a/services/galley/src/Galley/API/Teams/Features.hs b/services/galley/src/Galley/API/Teams/Features.hs index 27ae9bd43f5..4b88340ed83 100644 --- a/services/galley/src/Galley/API/Teams/Features.hs +++ b/services/galley/src/Galley/API/Teams/Features.hs @@ -43,7 +43,7 @@ import Data.Id import Data.String.Conversions (cs) import Galley.API.Error as Galley import Galley.API.LegalHold -import Galley.API.Teams (ensureNotTooLargeForLegalHold) +import Galley.API.Teams (ensureNotTooLargeToActivateLegalHold) import Galley.API.Util import Galley.App import qualified Galley.Data as Data @@ -203,7 +203,7 @@ setLegalholdStatusInternal tid status@(Public.tfwoStatus -> statusValue) = do case statusValue of Public.TeamFeatureDisabled -> removeSettings' tid Public.TeamFeatureEnabled -> do - ensureNotTooLargeForLegalHold tid Nothing + ensureNotTooLargeToActivateLegalHold tid TeamFeatures.setFeatureStatusNoConfig @'Public.TeamFeatureLegalHold tid status getAppLockInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureAppLock) From 1dd0f941166a52f3a69e910202a4a5bd8de649c9 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Fri, 28 May 2021 20:23:34 +0200 Subject: [PATCH 6/9] add test wip --- services/galley/src/Galley/API/Teams.hs | 1 + .../test/integration/API/Teams/LegalHold.hs | 38 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index ef11f7ea3cd..2a5456f1c3b 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -885,6 +885,7 @@ teamSizeNotTooLargeForLegalhold :: Int -> Galley Bool teamSizeNotTooLargeForLegalhold nMembersFanout = do limit <- fromIntegral . fromRange <$> fanoutLimit let withinLimit = nMembersFanout <= limit + Log.info $ Log.msg (show (nMembersFanout, limit)) view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case FeatureLegalHoldDisabledPermanently -> pure withinLimit FeatureLegalHoldDisabledByDefault -> pure withinLimit diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs index 4cbe3e07eb6..4f56845f2e7 100644 --- a/services/galley/test/integration/API/Teams/LegalHold.hs +++ b/services/galley/test/integration/API/Teams/LegalHold.hs @@ -128,11 +128,12 @@ tests s = test s "GET /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testGetLegalHoldTeamSettings), test s "DELETE /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testRemoveLegalHoldFromTeam), test s "GET, PUT [/i]?/teams/{tid}/legalhold" (onlyIfLhEnabled testEnablePerTeam), - test s "GET, PUT [/i]?/teams/{tid}/legalhold - too large" (onlyIfLhEnabled testEnablePerTeamTooLarge), + test s "XXXXXX GET, PUT [/i]?/teams/{tid}/legalhold - too large" (onlyIfLhEnabled testEnablePerTeamTooLarge), -- behavior of existing end-points test s "POST /clients" (onlyIfLhEnabled testCannotCreateLegalHoldDeviceOldAPI), test s "GET /teams/{tid}/members" (onlyIfLhEnabled testGetTeamMembersIncludesLHStatus), - test s "POST /register - cannot add team members with LH - too large" (onlyIfLhEnabled testAddTeamUserTooLargeWithLegalhold), + test s "POST /register - cannot add team members in large teams with LH" (onlyIfLhEnabled (testAddTeamUserTooLargeWithLegalhold False)), + test s "POST /register - can add team members in large teams with whitelisting feature" (onlyIfLhEnabled (testAddTeamUserTooLargeWithLegalhold True)), test s "GET legalhold status in user profile" testGetLegalholdStatus, {- TODO: conversations/{cnv}/otr/messages - possibly show the legal hold device (if missing) as a different device type (or show that on device level, depending on how client teams prefer) @@ -642,7 +643,9 @@ testEnablePerTeamTooLarge :: TestM () testEnablePerTeamTooLarge = do o <- view tsGConf let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o - (tid, _owner, _others) <- createBindingTeamWithMembers (fanoutLimit + 1) + -- TODO: it is impossible in this test to create teams bigger than the fanout limit. + -- Change the +1 to anything else and look at the logs + (tid, _owner, _others) <- createBindingTeamWithMembers (fanoutLimit + 5) status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid TestM () +testAddTeamUserTooLargeWithLegalhold withWhitelist = do o <- view tsGConf let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o (tid, owner, _others) <- createBindingTeamWithMembers fanoutLimit - status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid do + addUserToTeam' owner tid !!! do + const 200 === statusCode + else do + status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid Date: Sat, 29 May 2021 14:08:50 +0200 Subject: [PATCH 7/9] fix bug in guards --- services/galley/src/Galley/API/Error.hs | 2 +- services/galley/src/Galley/API/Teams.hs | 32 ++++++++++++------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/services/galley/src/Galley/API/Error.hs b/services/galley/src/Galley/API/Error.hs index 80a2a1c3ed7..edf257e10c1 100644 --- a/services/galley/src/Galley/API/Error.hs +++ b/services/galley/src/Galley/API/Error.hs @@ -153,7 +153,7 @@ tooManyTeamMembers :: Error tooManyTeamMembers = Error status403 "too-many-team-members" "Maximum number of members per team reached" tooManyTeamMembersOnTeamWithLegalhold :: Error -tooManyTeamMembersOnTeamWithLegalhold = Error status403 "too-many-members-for-legalhold" "cannot add more members to team legalhold service is enabled." +tooManyTeamMembersOnTeamWithLegalhold = Error status403 "too-many-members-for-legalhold" "cannot add more members to team when legalhold service is enabled." teamMemberNotFound :: Error teamMemberNotFound = Error status404 "no-team-member" "team member not found" diff --git a/services/galley/src/Galley/API/Teams.hs b/services/galley/src/Galley/API/Teams.hs index 2a5456f1c3b..6cb5cae8e64 100644 --- a/services/galley/src/Galley/API/Teams.hs +++ b/services/galley/src/Galley/API/Teams.hs @@ -84,7 +84,6 @@ import Galley.API.LegalHold import qualified Galley.API.Teams.Notifications as APITeamQueue import Galley.API.Util import Galley.App -import Galley.Data (teamMembersForFanout) import qualified Galley.Data as Data import qualified Galley.Data.SearchVisibility as SearchVisibilityData import Galley.Data.Services (BotMember) @@ -576,9 +575,9 @@ addTeamMember zusr zcon tid nmem = do ensureNonBindingTeam tid ensureUnboundUsers [uid] ensureConnectedToLocals zusr [uid] + (TeamSize sizeBeforeJoin) <- BrigTeam.getSize tid + ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1) memList <- Data.teamMembersForFanout tid - -- FUTUREWORK: We cannot enable legalhold on large teams right now - ensureNotTooLargeForLegalHold tid (length (memList ^. teamMembers) + 1) void $ addTeamMemberInternal tid (Just zusr) (Just zcon) nmem memList -- This function is "unchecked" because there is no need to check for user binding (invite only). @@ -591,8 +590,8 @@ uncheckedAddTeamMemberH (tid ::: req ::: _) = do uncheckedAddTeamMember :: TeamId -> NewTeamMember -> Galley () uncheckedAddTeamMember tid nmem = do mems <- Data.teamMembersForFanout tid - -- FUTUREWORK: We cannot enable legalhold on large teams right now - ensureNotTooLargeForLegalHold tid (length (mems ^. teamMembers) + 1) + (TeamSize sizeBeforeJoin) <- BrigTeam.getSize tid + ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1) (TeamSize sizeBeforeAdd) <- addTeamMemberInternal tid Nothing Nothing nmem mems billingUserIds <- Journal.getBillingUserIds tid $ Just $ newTeamMemberList ((nmem ^. ntmNewTeamMember) : mems ^. teamMembers) (mems ^. teamMemberListType) Journal.teamUpdate tid (sizeBeforeAdd + 1) billingUserIds @@ -870,26 +869,27 @@ ensureNotTooLarge tid = do -- LegalHold off after activation. -- FUTUREWORK: Find a way around the fanout limit. ensureNotTooLargeForLegalHold :: TeamId -> Int -> Galley () -ensureNotTooLargeForLegalHold tid nMembersFanout = do +ensureNotTooLargeForLegalHold tid teamSize = do whenM (isLegalHoldEnabledForTeam tid) $ do - unlessM (teamSizeNotTooLargeForLegalhold nMembersFanout) $ do + unlessM (teamSizeBelowLimit teamSize) $ do throwM tooManyTeamMembersOnTeamWithLegalhold ensureNotTooLargeToActivateLegalHold :: TeamId -> Galley () ensureNotTooLargeToActivateLegalHold tid = do - mems <- teamMembersForFanout tid - unlessM (teamSizeNotTooLargeForLegalhold (length (mems ^. teamMembers))) $ do + (TeamSize teamSize) <- BrigTeam.getSize tid + unlessM (teamSizeBelowLimit (fromIntegral teamSize)) $ do throwM cannotEnableLegalHoldServiceLargeTeam -teamSizeNotTooLargeForLegalhold :: Int -> Galley Bool -teamSizeNotTooLargeForLegalhold nMembersFanout = do +teamSizeBelowLimit :: Int -> Galley Bool +teamSizeBelowLimit teamSize = do limit <- fromIntegral . fromRange <$> fanoutLimit - let withinLimit = nMembersFanout <= limit - Log.info $ Log.msg (show (nMembersFanout, limit)) + let withinLimit = teamSize <= limit view (options . Opts.optSettings . Opts.setFeatureFlags . flagLegalHold) >>= \case FeatureLegalHoldDisabledPermanently -> pure withinLimit FeatureLegalHoldDisabledByDefault -> pure withinLimit - FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> pure True -- no limit, see 'ensureNotTooLargeForLegalHold' + FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> + -- unlimited, see docs of 'ensureNotTooLargeForLegalHold' + pure True addTeamMemberInternal :: TeamId -> Maybe UserId -> Maybe ConnId -> NewTeamMember -> TeamMemberList -> Galley TeamSize addTeamMemberInternal tid origin originConn (view ntmNewTeamMember -> new) memList = do @@ -982,8 +982,8 @@ canUserJoinTeam :: TeamId -> Galley () canUserJoinTeam tid = do lhEnabled <- isLegalHoldEnabledForTeam tid when lhEnabled $ do - mems <- Data.teamMembersForFanout tid - ensureNotTooLargeForLegalHold tid (length (mems ^. teamMembers) + 1) + (TeamSize sizeBeforeJoin) <- BrigTeam.getSize tid + ensureNotTooLargeForLegalHold tid (fromIntegral sizeBeforeJoin + 1) getTeamSearchVisibilityAvailableInternal :: TeamId -> Galley (Public.TeamFeatureStatus 'Public.TeamFeatureSearchVisibility) getTeamSearchVisibilityAvailableInternal tid = do From bd868c05102c3c6c0266654aa512ae6088eefb74 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Sat, 29 May 2021 15:10:54 +0200 Subject: [PATCH 8/9] Add test: no limit when whitelisting lh --- .../test/integration/API/Teams/LegalHold.hs | 92 +++++++++++++++---- 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs index 4f56845f2e7..6d4dd18f6aa 100644 --- a/services/galley/test/integration/API/Teams/LegalHold.hs +++ b/services/galley/test/integration/API/Teams/LegalHold.hs @@ -111,6 +111,26 @@ onlyIfLhEnabled action = do "`FeatureLegalHoldWhitelistTeamsAndImplicitConsent` requires setting up a mock galley \ \with `withLHWhitelist`; don't call `onlyIfLhEnabled` if you do that, that's redundant." +onlyIfLhWhitelisted :: TestM () -> TestM () +onlyIfLhWhitelisted action = do + featureLegalHold <- view (tsGConf . optSettings . setFeatureFlags . flagLegalHold) + case featureLegalHold of + FeatureLegalHoldDisabledPermanently -> + liftIO $ + hPutStrLn + stderr + "*** skipping test. This test only works if you manually adjust the server config files\ + \(the 'withLHWhitelist' trick does not work because it does not allow \ + \brig to talk to the dynamically spawned galley)." + FeatureLegalHoldDisabledByDefault -> + liftIO $ + hPutStrLn + stderr + "*** skipping test. This test only works if you manually adjust the server config files\ + \(the 'withLHWhitelist' trick does not work because it does not allow \ + \brig to talk to the dynamically spawned galley)." + FeatureLegalHoldWhitelistTeamsAndImplicitConsent -> action + tests :: IO TestSetup -> TestTree tests s = -- See also Client Tests in Brig; where behaviour around deleting/adding LH clients is tested @@ -128,12 +148,13 @@ tests s = test s "GET /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testGetLegalHoldTeamSettings), test s "DELETE /teams/{tid}/legalhold/settings" (onlyIfLhEnabled testRemoveLegalHoldFromTeam), test s "GET, PUT [/i]?/teams/{tid}/legalhold" (onlyIfLhEnabled testEnablePerTeam), - test s "XXXXXX GET, PUT [/i]?/teams/{tid}/legalhold - too large" (onlyIfLhEnabled testEnablePerTeamTooLarge), + test s "GET, PUT [/i]?/teams/{tid}/legalhold - too large" (onlyIfLhEnabled testEnablePerTeamTooLarge), -- behavior of existing end-points test s "POST /clients" (onlyIfLhEnabled testCannotCreateLegalHoldDeviceOldAPI), test s "GET /teams/{tid}/members" (onlyIfLhEnabled testGetTeamMembersIncludesLHStatus), - test s "POST /register - cannot add team members in large teams with LH" (onlyIfLhEnabled (testAddTeamUserTooLargeWithLegalhold False)), - test s "POST /register - can add team members in large teams with whitelisting feature" (onlyIfLhEnabled (testAddTeamUserTooLargeWithLegalhold True)), + test s "POST /register - cannot add team members above fanout limit" (onlyIfLhEnabled testAddTeamUserTooLargeWithLegalhold), + -- test s "POST /register - Enable this to create a test team for next test" (testAddTeamUserTooLargeWithLegalholdWhitelisted Nothing), + -- test s "POST /register - can add team members above fanout limit when whitelisting is enabled" (testAddTeamUserTooLargeWithLegalholdWhitelisted (Just (read "86bd1ba6-6c29-4d3b-af54-579c5e9b1fa3", read "310b550d-3832-47cc-b6dc-50d979879985"))), test s "GET legalhold status in user profile" testGetLegalholdStatus, {- TODO: conversations/{cnv}/otr/messages - possibly show the legal hold device (if missing) as a different device type (or show that on device level, depending on how client teams prefer) @@ -655,27 +676,58 @@ testEnablePerTeamTooLarge = do const 403 === statusCode const (Just "too-large-team-for-legalhold") === fmap Error.label . responseJsonMaybe -testAddTeamUserTooLargeWithLegalhold :: Bool -> TestM () -testAddTeamUserTooLargeWithLegalhold withWhitelist = do +testAddTeamUserTooLargeWithLegalhold :: TestM () +testAddTeamUserTooLargeWithLegalhold = do o <- view tsGConf let fanoutLimit = fromIntegral . fromRange $ Galley.currentFanoutLimit o (tid, owner, _others) <- createBindingTeamWithMembers fanoutLimit + status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid do - addUserToTeam' owner tid !!! do - const 200 === statusCode - else do - status :: Public.TeamFeatureStatus 'Public.TeamFeatureLegalHold <- responseJsonUnsafe <$> (getEnabled tid ] +-- + +-- - legalhold: disabled-by-default +-- + legalhold: whitelist-teams-and-implicit-consent +-- (c) run the second phase with the arguemnts returned in the first phase. +testAddTeamUserTooLargeWithLegalholdWhitelisted :: HasCallStack => Maybe (UserId, TeamId) -> TestM () +testAddTeamUserTooLargeWithLegalholdWhitelisted Nothing = do + o <- view tsGConf + (owner, tid) <- createBindingTeam + let fanoutLimit = fromIntegral @_ @Integer . fromRange $ Galley.currentFanoutLimit o + forM_ [2 .. fanoutLimit] $ \_n -> do + addUserToTeam' owner tid !!! do + const 201 === statusCode + error $ + ( "**** testAddTeamUserTooLargeWithLegalholdWhitelisted. Created (owner, tid): " <> show (owner, tid) + <> " Use these values to run the test." + ) +testAddTeamUserTooLargeWithLegalholdWhitelisted (Just (owner, tid)) = onlyIfLhWhitelisted $ do + replicateM_ 40 $ do + addUserToTeam' owner tid !!! do + const 201 === statusCode + ensureQueueEmpty testCannotCreateLegalHoldDeviceOldAPI :: TestM () testCannotCreateLegalHoldDeviceOldAPI = do From 6c19cd403eccf9fff8bc8ac4ab6b0f09b0dd7639 Mon Sep 17 00:00:00 2001 From: Stefan Matting Date: Sat, 29 May 2021 15:36:22 +0200 Subject: [PATCH 9/9] fix compilation --- services/galley/test/integration/API/Teams/LegalHold.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/galley/test/integration/API/Teams/LegalHold.hs b/services/galley/test/integration/API/Teams/LegalHold.hs index 6d4dd18f6aa..5bdd008ca52 100644 --- a/services/galley/test/integration/API/Teams/LegalHold.hs +++ b/services/galley/test/integration/API/Teams/LegalHold.hs @@ -1,7 +1,6 @@ {-# LANGUAGE DeriveAnyClass #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} {-# OPTIONS_GHC -Wno-orphans #-} - -- This file is part of the Wire Server implementation. -- -- Copyright (C) 2020 Wire Swiss GmbH @@ -18,6 +17,7 @@ -- -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . +{-# OPTIONS_GHC -Wno-unused-top-binds #-} module API.Teams.LegalHold ( tests,