diff --git a/changelog.d/2-features/block-lh-for-mls-users b/changelog.d/2-features/block-lh-for-mls-users new file mode 100644 index 00000000000..cc86b5c4512 --- /dev/null +++ b/changelog.d/2-features/block-lh-for-mls-users @@ -0,0 +1 @@ +Deny requests for a legalhold device for users who are part of any MLS conversations \ No newline at end of file diff --git a/integration/test/Test/LegalHold.hs b/integration/test/Test/LegalHold.hs index f359d54d2c3..4b70fd0d454 100644 --- a/integration/test/Test/LegalHold.hs +++ b/integration/test/Test/LegalHold.hs @@ -906,6 +906,24 @@ testLHDisableBeforeApproval = do >>= assertStatus 200 getBob'sStatus `shouldMatch` "disabled" +-- --------- +-- WPB-10783 +-- --------- +testBlockLHForMLSUsers :: (HasCallStack) => App () +testBlockLHForMLSUsers = do + -- scenario 1: + -- if charlie is in any MLS conversation, he cannot approve to be put under legalhold + (charlie, tid, []) <- createTeam OwnDomain 1 + [charlie1] <- traverse (createMLSClient def) [charlie] + void $ createNewGroup charlie1 + void $ createAddCommit charlie1 [charlie] >>= sendAndConsumeCommitBundle + + legalholdWhitelistTeam tid charlie >>= assertStatus 200 + withMockServer def lhMockApp \lhDomAndPort _chan -> do + postLegalHoldSettings tid charlie (mkLegalHoldSettings lhDomAndPort) >>= assertStatus 201 + requestLegalHoldDevice tid charlie charlie `bindResponse` do + assertLabel 409 "mls-legal-hold-not-allowed" + -- --------- -- WPB-10772 -- --------- @@ -913,8 +931,8 @@ testLHDisableBeforeApproval = do -- | scenario 2.1: -- charlie first is put under legalhold and after that wants to join an MLS conversation -- claiming a keypackage of charlie to add them to a conversation should not be possible -testLegalholdThenMLSThirdParty :: (HasCallStack) => App () -testLegalholdThenMLSThirdParty = do +testBlockClaimingKeyPackageForLHUsers :: (HasCallStack) => App () +testBlockClaimingKeyPackageForLHUsers = do (alice, tid, [charlie]) <- createTeam OwnDomain 2 [alice1, charlie1] <- traverse (createMLSClient def) [alice, charlie] _ <- uploadNewKeyPackage charlie1 @@ -937,8 +955,8 @@ testLegalholdThenMLSThirdParty = do -- since he doesn't need to claim his own keypackage to do so, this would succeed -- we need to check upon group creation if the user is under legalhold and reject -- the operation if they are -testLegalholdThenMLSSelf :: (HasCallStack) => App () -testLegalholdThenMLSSelf = do +testBlockCreateMLSConvForLHUsers :: (HasCallStack) => App () +testBlockCreateMLSConvForLHUsers = do (alice, tid, [charlie]) <- createTeam OwnDomain 2 [alice1, charlie1] <- traverse (createMLSClient def) [alice, charlie] _ <- uploadNewKeyPackage alice1 diff --git a/libs/wire-api/src/Wire/API/Routes/Public/Galley/LegalHold.hs b/libs/wire-api/src/Wire/API/Routes/Public/Galley/LegalHold.hs index a9d7ebe219d..f4506b7fcfb 100644 --- a/libs/wire-api/src/Wire/API/Routes/Public/Galley/LegalHold.hs +++ b/libs/wire-api/src/Wire/API/Routes/Public/Galley/LegalHold.hs @@ -133,6 +133,7 @@ type LegalHoldAPI = :> CanThrow 'LegalHoldServiceBadResponse :> CanThrow 'LegalHoldServiceNotRegistered :> CanThrow 'LegalHoldCouldNotBlockConnections + :> CanThrow 'MLSLegalholdIncompatible :> CanThrow 'UserLegalHoldIllegalOperation :> Description "This endpoint can lead to the following events being sent:\n\ diff --git a/services/galley/src/Galley/API/LegalHold.hs b/services/galley/src/Galley/API/LegalHold.hs index cd2227ff4e3..3c15d5c1e53 100644 --- a/services/galley/src/Galley/API/LegalHold.hs +++ b/services/galley/src/Galley/API/LegalHold.hs @@ -68,6 +68,7 @@ import Polysemy.Input import Polysemy.TinyLog qualified as P import System.Logger.Class qualified as Log import Wire.API.Conversation (ConvType (..)) +import Wire.API.Conversation.Protocol import Wire.API.Conversation.Role import Wire.API.Error import Wire.API.Error.Galley @@ -345,6 +346,7 @@ requestDevice :: Member (ErrorS 'LegalHoldNotEnabled) r, Member (ErrorS 'LegalHoldServiceBadResponse) r, Member (ErrorS 'LegalHoldServiceNotRegistered) r, + Member (ErrorS 'MLSLegalholdIncompatible) r, Member (ErrorS 'NotATeamMember) r, Member (ErrorS 'NoUserLegalHoldConsent) r, Member (ErrorS OperationDenied) r, @@ -392,6 +394,12 @@ requestDevice lzusr tid uid = do lhs@UserLegalHoldDisabled -> RequestDeviceSuccess <$ provisionLHDevice zusr luid lhs UserLegalHoldNoConsent -> throwS @'NoUserLegalHoldConsent where + disallowIfMLSUser :: Local UserId -> Sem r () + disallowIfMLSUser luid = do + void $ iterateConversations luid (toRange (Proxy @500)) $ \convs -> do + when (any (\c -> c.convProtocol /= ProtocolProteus) convs) $ do + throwS @'MLSLegalholdIncompatible + -- Wire's LH service that galley is usually calling here is idempotent in device creation, -- ie. it returns the existing device on multiple calls to `/init`, like here: -- https://github.com/wireapp/legalhold/blob/e0a241162b9dbc841f12fbc57c8a1e1093c7e83a/src/main/java/com/wire/bots/hold/resource/InitiateResource.java#L42 @@ -401,6 +409,7 @@ requestDevice lzusr tid uid = do -- device at (almost) the same time. provisionLHDevice :: UserId -> Local UserId -> UserLegalHoldStatus -> Sem r () provisionLHDevice zusr luid userLHStatus = do + disallowIfMLSUser luid (lastPrekey', prekeys) <- requestDeviceFromService luid -- We don't distinguish the last key here; brig will do so when the device is added LegalHoldData.insertPendingPrekeys (tUnqualified luid) (unpackLastPrekey lastPrekey' : prekeys)