Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(remove-mls-data-on-device-being-removed) #WPB-12155 #33

Merged
merged 2 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ This is Legal Hold Service for Wire.
## Environment variables
- SERVICE_TOKEN: <mandatory>. Must be set to some random value (at least 16 alphanumeric chars)
- WIRE_API_HOST: <optional>. Your Wire Backend host. Default: https://prod-nginz-https.wire.com
- CORE_CRYPTO_PASSWORD: <mandatory>. Your MLS folder password
- DB_DRIVER: <optional>. Default: org.postgresql.Driver
- DB_URL: <optional>. Default: jdbc:postgresql://localhost/legalhold
- DB_USER: <optional>
Expand All @@ -21,6 +22,7 @@ docker run \
-e DB_USER='admin' \
-e DB_PASSWORD='s3cret' \
-e SERVICE_TOKEN='secr3t' \
-e CORE_CRYPTO_PASSWORD='secr3t' \
-p 80:8080 \
--name secure-hold --rm quay.io/wire/legalhold:1.0.4
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.wire.bots.cryptobox.CryptoException;
import com.wire.bots.hold.DAO.AccessDAO;
import com.wire.bots.hold.model.database.LHAccess;
import com.wire.bots.hold.model.dto.InitializedDeviceDTO;
import com.wire.bots.hold.utils.CryptoDatabaseFactory;

Expand Down Expand Up @@ -164,6 +165,18 @@ public void confirmDevice(QualifiedId userId, UUID teamId, String clientId, Stri
* @throws CryptoException
*/
public void removeDevice(QualifiedId userId, UUID teamId) throws IOException, CryptoException {
// MLS
LHAccess userAccess = accessDAO.get(userId.id, userId.domain);
if (userAccess != null) {
API api = new API(client, null, userAccess.token);
if (api.isMlsEnabled()) {
try (CryptoMlsClient cryptoMlsClient = new CryptoMlsClient(userAccess.clientId, coreCryptoPassword)) {
cryptoMlsClient.wipe();
}
spoonman01 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Proteus
try (Crypto crypto = cf.create(userId)) {
crypto.purge();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package com.wire.bots.hold.service;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.wire.bots.cryptobox.CryptoException;
import com.wire.bots.hold.Config;
import com.wire.bots.hold.DAO.AccessDAO;
import com.wire.bots.hold.DAO.MetadataDAO;
import com.wire.bots.hold.Service;
import com.wire.bots.hold.model.database.LHAccess;
import com.wire.bots.hold.utils.Cache;
import com.wire.bots.hold.utils.CryptoDatabaseFactory;
import com.wire.bots.hold.utils.HttpTestUtils;
import com.wire.xenon.backend.models.QualifiedId;
import com.wire.xenon.crypto.Crypto;
import com.wire.xenon.crypto.mls.CryptoMlsClient;
import com.wire.xenon.models.otr.Missing;
import com.wire.xenon.models.otr.PreKey;
import com.wire.xenon.models.otr.PreKeys;
import com.wire.xenon.models.otr.Recipients;
import io.dropwizard.testing.ConfigOverride;
import io.dropwizard.testing.DropwizardTestSupport;
import org.junit.*;
Expand All @@ -18,6 +26,10 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.UUID;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
Expand All @@ -33,19 +45,17 @@ public class DeviceManagementServiceTest {
ConfigOverride.config("token", TOKEN),
ConfigOverride.config("apiHost", API_HOST));
private static Client client;

private static final WireMockServer wireMockServer = new WireMockServer(8090);

private CryptoDatabaseFactory cryptoFactory;
private AccessDAO accessDAO;
private String coreCryptoPassword;
private CryptoDatabaseFactory cryptoFactory;
private DeviceManagementService deviceManagementService;

// Consts
private static final QualifiedId userId = new QualifiedId(UUID.randomUUID(), MetadataDAO.FALLBACK_DOMAIN_KEY);
private static final UUID teamId = UUID.randomUUID();
private static final String clientId = UUID.randomUUID().toString();
private static final String refreshToken = UUID.randomUUID().toString();
private static final String coreCryptoPassword = "secr3t";

@BeforeClass
public static void beforeClass() throws Exception {
Expand All @@ -62,16 +72,16 @@ public static void afterClass() {
}

@Before
public void before() {
public void before() throws CryptoException {
wireMockServer.start();
configureFor("localhost", 8090);

stubFor(get(urlEqualTo("/api-version"))
.willReturn(okJson(apiVersionV6)));

cryptoFactory = mock(CryptoDatabaseFactory.class);
when(cryptoFactory.create(userId)).thenReturn(mockedCrypto);
accessDAO = mock(AccessDAO.class);
coreCryptoPassword = "secr3t";

deviceManagementService = new DeviceManagementService(
accessDAO,
Expand Down Expand Up @@ -458,6 +468,131 @@ public void givenEnabledMls_whenConfirmingDevice_thenDeviceIsSavedToDatabase() t
);
}

@Test
public void givenUnknownUser_whenRemovingDevice_thenNoMlsCallsAreMade() throws IOException, CryptoException {
// given
when(accessDAO.get(userId.id, userId.domain)).thenReturn(null);

// when
deviceManagementService.removeDevice(userId, teamId);

// then
verify(accessDAO, times(1)).get(userId.id, userId.domain);
}

@Test
public void givenKnownUser_whenRemovingDeviceAndMlsIsDisabled_thenNoWipeIsCalled() throws IOException, CryptoException {
// given
Path path = Paths.get("mls/" + clientId);
try (CryptoMlsClient cryptoMlsClient = new CryptoMlsClient(clientId, coreCryptoPassword)) {
assert cryptoMlsClient != null;
assert Files.exists(path);
}

LHAccess lhAccess = new LHAccess();
lhAccess.last = UUID.randomUUID();
lhAccess.userId = new QualifiedId(userId.id, userId.domain);
lhAccess.clientId = clientId;
lhAccess.token = refreshToken;
lhAccess.cookie = "cookie";
lhAccess.enabled = true;

when(accessDAO.get(userId.id, userId.domain)).thenReturn(lhAccess);
stubFor(get(urlEqualTo("/v6/feature-configs"))
.willReturn(okJson(disabledMlsFeatureConfigJsonResponse)));

// when
deviceManagementService.removeDevice(userId, teamId);

// then
verify(accessDAO, times(1)).get(userId.id, userId.domain);
assert Files.exists(path);
}

@Test
public void givenKnownUser_whenRemovingDeviceAndMlsIsEnabled_thenWipeIsCalled() throws IOException, CryptoException {
// given
stubFor(get(urlEqualTo("/v6/feature-configs"))
.willReturn(okJson(enabledMlsFeatureConfigJsonResponse)));
stubFor(get(urlEqualTo("/v6/mls/public-keys"))
.willReturn(okJson(mlsPublicKeysSuccessResponse)));

Path path = Paths.get("mls/" + clientId);
try (CryptoMlsClient cryptoMlsClient = new CryptoMlsClient(clientId, coreCryptoPassword)) {
assert cryptoMlsClient != null;
assert Files.exists(path);
}

LHAccess lhAccess = new LHAccess();
lhAccess.last = UUID.randomUUID();
lhAccess.userId = new QualifiedId(userId.id, userId.domain);
lhAccess.clientId = clientId;
lhAccess.token = refreshToken;
lhAccess.cookie = "cookie";
lhAccess.enabled = true;

when(accessDAO.get(userId.id, userId.domain)).thenReturn(lhAccess);

// when
deviceManagementService.removeDevice(userId, teamId);

// then
verify(accessDAO, times(1)).get(userId.id, userId.domain);
assert Files.notExists(path);
}

Crypto mockedCrypto = new Crypto() {
@Override
public byte[] getIdentity() throws CryptoException {
return new byte[0];
}

@Override
public byte[] getLocalFingerprint() throws CryptoException {
return new byte[0];
}

@Override
public PreKey newLastPreKey() throws CryptoException {
return null;
}

@Override
public ArrayList<PreKey> newPreKeys(int from, int count) throws CryptoException {
return null;
}

@Override
public Recipients encrypt(PreKeys preKeys, byte[] content) throws CryptoException {
return null;
}

@Override
public Recipients encrypt(Missing missing, byte[] content) throws CryptoException {
return null;
}

@Override
public String decrypt(QualifiedId userId, String clientId, String cypher) throws CryptoException {
return "";
}

@Override
public boolean isClosed() {
return false;
}

@Override
public void purge() throws IOException {

}

@Override
public void close() throws IOException {

}
};

private static final String enabledMlsFeatureConfigJsonResponse = """
{
"mls": {
Expand Down
Loading