diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java b/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java index d200bb806..7e54ca7de 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClient.java @@ -30,14 +30,19 @@ public class SecureStorageClient { private final ExternalServiceCredentialGenerator storageServiceCredentialGenerator; private final URI deleteUri; + private final URI getManifestUri; private final FaultTolerantHttpClient httpClient; @VisibleForTesting static final String DELETE_PATH = "/v1/storage"; + @VisibleForTesting + static final String GET_MANIFEST_PATH = "/v1/storage/manifest"; + public SecureStorageClient(final ExternalServiceCredentialGenerator storageServiceCredentialGenerator, final Executor executor, final SecureStorageServiceConfiguration configuration) throws CertificateException { this.storageServiceCredentialGenerator = storageServiceCredentialGenerator; this.deleteUri = URI.create(configuration.getUri()).resolve(DELETE_PATH); + this.getManifestUri = URI.create(configuration.getUri()).resolve(GET_MANIFEST_PATH); this.httpClient = FaultTolerantHttpClient.newBuilder() .withCircuitBreaker(configuration.getCircuitBreakerConfiguration()) .withRetry(configuration.getRetryConfiguration()) @@ -68,4 +73,24 @@ public class SecureStorageClient { throw new RuntimeException("Failed to delete storage service data: " + response.statusCode()); }); } + + public CompletableFuture hasStoredData(final UUID accountUuid) { + final ExternalServiceCredentials credentials = storageServiceCredentialGenerator.generateFor(accountUuid.toString()); + + final HttpRequest request = HttpRequest.newBuilder() + .uri(getManifestUri) + .GET() + .header("Authorization", "Basic " + Base64.encodeBytes((credentials.getUsername() + ":" + credentials.getPassword()).getBytes(StandardCharsets.UTF_8))) + .build(); + + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(response -> { + if (response.statusCode() >= 200 && response.statusCode() < 300) { + return true; + } else if (response.statusCode() == 404) { + return false; + } + + throw new RuntimeException("Failed to check for presence of manifest: " + response.statusCode()); + }); + } } diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java index 39306c27a..0bae3a950 100644 --- a/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java +++ b/service/src/test/java/org/whispersystems/textsecuregcm/securestorage/SecureStorageClientTest.java @@ -27,9 +27,12 @@ import java.util.concurrent.TimeUnit; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.delete; +import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -118,4 +121,46 @@ public class SecureStorageClientTest { assertThrows(RuntimeException.class, () -> secureStorageClient.deleteStoredData(accountUuid).join()); } + + @Test + public void hasManifest() { + final String username = RandomStringUtils.randomAlphabetic(16); + final String password = RandomStringUtils.randomAlphanumeric(32); + + when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + + wireMockRule.stubFor(get(urlEqualTo(SecureStorageClient.GET_MANIFEST_PATH)) + .withBasicAuth(username, password) + .willReturn(aResponse().withStatus(200))); + + assertTrue(secureStorageClient.hasStoredData(accountUuid).join()); + } + + @Test + public void hasManifesNotFound() { + final String username = RandomStringUtils.randomAlphabetic(16); + final String password = RandomStringUtils.randomAlphanumeric(32); + + when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + + wireMockRule.stubFor(get(urlEqualTo(SecureStorageClient.GET_MANIFEST_PATH)) + .withBasicAuth(username, password) + .willReturn(aResponse().withStatus(404))); + + assertFalse(secureStorageClient.hasStoredData(accountUuid).join()); + } + + @Test + public void hasManifestFailure() { + final String username = RandomStringUtils.randomAlphabetic(16); + final String password = RandomStringUtils.randomAlphanumeric(32); + + when(credentialGenerator.generateFor(accountUuid.toString())).thenReturn(new ExternalServiceCredentials(username, password)); + + wireMockRule.stubFor(get(urlEqualTo(SecureStorageClient.GET_MANIFEST_PATH)) + .withBasicAuth(username, password) + .willReturn(aResponse().withStatus(400))); + + assertThrows(RuntimeException.class, () -> secureStorageClient.hasStoredData(accountUuid).join()); + } }