Skip to content

Commit

Permalink
feat(remove-mls-data-on-device-being-removed) #WPB-12155
Browse files Browse the repository at this point in the history
* Update README.md to include CORE_CRYPTO_PASSWORD
* Add call to CoreCrypto wipe() function in removeDevice
* Add tests for wipe mls data on device removed
  • Loading branch information
alexandreferris committed Nov 27, 2024
1 parent 076ddf4 commit bb54d84
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 6 deletions.
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();
}
}
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
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.mls.CryptoMlsClient;
import io.dropwizard.testing.ConfigOverride;
import io.dropwizard.testing.DropwizardTestSupport;
import org.junit.*;
Expand All @@ -18,6 +20,9 @@
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.UUID;

import static com.github.tomakehurst.wiremock.client.WireMock.*;
Expand All @@ -33,19 +38,16 @@ 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 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 @@ -69,9 +71,8 @@ public void before() {
stubFor(get(urlEqualTo("/api-version"))
.willReturn(okJson(apiVersionV6)));

cryptoFactory = mock(CryptoDatabaseFactory.class);
CryptoDatabaseFactory cryptoFactory = mock(CryptoDatabaseFactory.class);
accessDAO = mock(AccessDAO.class);
coreCryptoPassword = "secr3t";

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

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

// when
try {
deviceManagementService.removeDevice(userId, teamId);
} catch (Exception exception) {
assert exception instanceof NullPointerException;
assert exception.getMessage().equals("Cannot invoke \"com.wire.xenon.crypto.Crypto.purge()\" because \"crypto\" is null");
}

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

@Test
public void givenKnownUser_whenRemovingDeviceAndMlsIsDisabled_thenNoWipeIsCalled() {
// 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
try {
deviceManagementService.removeDevice(userId, teamId);
} catch (Exception exception) {
assert exception instanceof NullPointerException;
assert exception.getMessage().equals("Cannot invoke \"com.wire.xenon.crypto.Crypto.purge()\" because \"crypto\" is null");
}

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

@Test
public void givenKnownUser_whenRemovingDeviceAndMlsIsEnabled_thenWipeIsCalled() {
// 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
try {
deviceManagementService.removeDevice(userId, teamId);
} catch (Exception exception) {
assert exception instanceof NullPointerException;
assert exception.getMessage().equals("Cannot invoke \"com.wire.xenon.crypto.Crypto.purge()\" because \"crypto\" is null");
}

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

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

0 comments on commit bb54d84

Please sign in to comment.