From 6f622c0ea4cb8faa7e1cb09a39136ee442b232c2 Mon Sep 17 00:00:00 2001 From: Luca Rospocher Date: Tue, 8 Oct 2024 10:43:44 +0200 Subject: [PATCH] feat(add-api-v1) Add new resources handling qualified id requests from the backend (#WPB-11299) * Refactor resources and models in specific packages * Add /api-version endpoint return version 0 and 1 * Add DeviceManagementService, shared service between resources, also abstracting database access from api layer * Basic integration tests for the new api layer --- .../wire/bots/hold/{model => }/Config.java | 2 +- .../com/wire/bots/hold/DAO/AccessDAO.java | 2 +- .../bots/hold/DAO/AccessResultSetMapper.java | 2 +- .../com/wire/bots/hold/DAO/EventsDAO.java | 2 +- .../bots/hold/DAO/EventsResultSetMapper.java | 2 +- .../wire/bots/hold/FallbackDomainFetcher.java | 7 +- .../wire/bots/hold/NotificationProcessor.java | 2 +- src/main/java/com/wire/bots/hold/Service.java | 68 ++++++--- .../bots/hold/healthchecks/SanityCheck.java | 2 +- .../model/api/shared/ApiVersionResponse.java | 14 ++ .../model/{ => api/shared}/InitResponse.java | 2 +- .../v0/ConfirmPayloadV0.java} | 4 +- .../v0/InitPayloadV0.java} | 4 +- .../hold/model/api/v1/ConfirmPayloadV1.java | 27 ++++ .../bots/hold/model/api/v1/InitPayloadV1.java | 19 +++ .../bots/hold/model/{ => database}/Event.java | 2 +- .../hold/model/{ => database}/LHAccess.java | 2 +- .../hold/model/dto/InitializedDeviceDTO.java | 29 ++++ .../hold/monitoring/ApiVersionResource.java | 45 ++++++ .../bots/hold/monitoring/StatusResource.java | 4 +- .../bots/hold/resource/ConfirmResource.java | 70 --------- .../bots/hold/resource/InitiateResource.java | 65 --------- .../bots/hold/resource/RemoveResource.java | 61 -------- .../{ => v0/audit}/AuthorizeResource.java | 2 +- .../{ => v0/audit}/ConversationResource.java | 6 +- .../{ => v0/audit}/DevicesResource.java | 4 +- .../{ => v0/audit}/EventsResource.java | 4 +- .../{ => v0/audit}/IndexResource.java | 4 +- .../v0/backend/ConfirmResourceV0.java | 55 ++++++++ .../v0/backend/InitiateResourceV0.java | 60 ++++++++ .../resource/v0/backend/RemoveResourceV0.java | 52 +++++++ .../v1/backend/ConfirmResourceV1.java | 53 +++++++ .../v1/backend/InitiateResourceV1.java | 58 ++++++++ .../resource/v1/backend/RemoveResourceV1.java | 47 +++++++ .../hold/service/DeviceManagementService.java | 111 +++++++++++++++ .../hold/utils/CryptoDatabaseFactory.java | 10 ++ .../wire/bots/hold/utils/HoldClientRepo.java | 2 +- .../hold/ConfirmRemoveResourceV1Test.java | 133 ++++++++++++++++++ .../java/com/wire/bots/hold/DatabaseTest.java | 11 +- .../bots/hold/FallbackDomainFetcherTest.java | 5 +- .../bots/hold/InitiateResourceV1Test.java | 87 ++++++++++++ .../wire/bots/hold/utils/HttpTestUtils.java | 19 +++ .../com/wire/bots/hold/utils/TestCache.java | 1 - 43 files changed, 901 insertions(+), 260 deletions(-) rename src/main/java/com/wire/bots/hold/{model => }/Config.java (98%) create mode 100644 src/main/java/com/wire/bots/hold/model/api/shared/ApiVersionResponse.java rename src/main/java/com/wire/bots/hold/model/{ => api/shared}/InitResponse.java (92%) rename src/main/java/com/wire/bots/hold/model/{ConfirmPayload.java => api/v0/ConfirmPayloadV0.java} (87%) rename src/main/java/com/wire/bots/hold/model/{InitPayload.java => api/v0/InitPayloadV0.java} (83%) create mode 100644 src/main/java/com/wire/bots/hold/model/api/v1/ConfirmPayloadV1.java create mode 100644 src/main/java/com/wire/bots/hold/model/api/v1/InitPayloadV1.java rename src/main/java/com/wire/bots/hold/model/{ => database}/Event.java (82%) rename src/main/java/com/wire/bots/hold/model/{ => database}/LHAccess.java (85%) create mode 100644 src/main/java/com/wire/bots/hold/model/dto/InitializedDeviceDTO.java create mode 100644 src/main/java/com/wire/bots/hold/monitoring/ApiVersionResource.java delete mode 100644 src/main/java/com/wire/bots/hold/resource/ConfirmResource.java delete mode 100644 src/main/java/com/wire/bots/hold/resource/InitiateResource.java delete mode 100644 src/main/java/com/wire/bots/hold/resource/RemoveResource.java rename src/main/java/com/wire/bots/hold/resource/{ => v0/audit}/AuthorizeResource.java (96%) rename src/main/java/com/wire/bots/hold/resource/{ => v0/audit}/ConversationResource.java (98%) rename src/main/java/com/wire/bots/hold/resource/{ => v0/audit}/DevicesResource.java (97%) rename src/main/java/com/wire/bots/hold/resource/{ => v0/audit}/EventsResource.java (96%) rename src/main/java/com/wire/bots/hold/resource/{ => v0/audit}/IndexResource.java (96%) create mode 100644 src/main/java/com/wire/bots/hold/resource/v0/backend/ConfirmResourceV0.java create mode 100644 src/main/java/com/wire/bots/hold/resource/v0/backend/InitiateResourceV0.java create mode 100644 src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java create mode 100644 src/main/java/com/wire/bots/hold/resource/v1/backend/ConfirmResourceV1.java create mode 100644 src/main/java/com/wire/bots/hold/resource/v1/backend/InitiateResourceV1.java create mode 100644 src/main/java/com/wire/bots/hold/resource/v1/backend/RemoveResourceV1.java create mode 100644 src/main/java/com/wire/bots/hold/service/DeviceManagementService.java create mode 100644 src/main/java/com/wire/bots/hold/utils/CryptoDatabaseFactory.java create mode 100644 src/test/java/com/wire/bots/hold/ConfirmRemoveResourceV1Test.java create mode 100644 src/test/java/com/wire/bots/hold/InitiateResourceV1Test.java create mode 100644 src/test/java/com/wire/bots/hold/utils/HttpTestUtils.java diff --git a/src/main/java/com/wire/bots/hold/model/Config.java b/src/main/java/com/wire/bots/hold/Config.java similarity index 98% rename from src/main/java/com/wire/bots/hold/model/Config.java rename to src/main/java/com/wire/bots/hold/Config.java index e73b507..2bf23d6 100644 --- a/src/main/java/com/wire/bots/hold/model/Config.java +++ b/src/main/java/com/wire/bots/hold/Config.java @@ -16,7 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -package com.wire.bots.hold.model; +package com.wire.bots.hold; import com.fasterxml.jackson.annotation.JsonProperty; import io.dropwizard.Configuration; diff --git a/src/main/java/com/wire/bots/hold/DAO/AccessDAO.java b/src/main/java/com/wire/bots/hold/DAO/AccessDAO.java index e9fe222..e772614 100644 --- a/src/main/java/com/wire/bots/hold/DAO/AccessDAO.java +++ b/src/main/java/com/wire/bots/hold/DAO/AccessDAO.java @@ -1,6 +1,6 @@ package com.wire.bots.hold.DAO; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.LHAccess; import org.jdbi.v3.sqlobject.config.RegisterColumnMapper; import org.jdbi.v3.sqlobject.customizer.Bind; import org.jdbi.v3.sqlobject.statement.SqlQuery; diff --git a/src/main/java/com/wire/bots/hold/DAO/AccessResultSetMapper.java b/src/main/java/com/wire/bots/hold/DAO/AccessResultSetMapper.java index c36c2ce..d3f6bc7 100644 --- a/src/main/java/com/wire/bots/hold/DAO/AccessResultSetMapper.java +++ b/src/main/java/com/wire/bots/hold/DAO/AccessResultSetMapper.java @@ -1,6 +1,6 @@ package com.wire.bots.hold.DAO; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.LHAccess; import org.jdbi.v3.core.mapper.ColumnMapper; import org.jdbi.v3.core.statement.StatementContext; diff --git a/src/main/java/com/wire/bots/hold/DAO/EventsDAO.java b/src/main/java/com/wire/bots/hold/DAO/EventsDAO.java index 8da0260..d3c6511 100644 --- a/src/main/java/com/wire/bots/hold/DAO/EventsDAO.java +++ b/src/main/java/com/wire/bots/hold/DAO/EventsDAO.java @@ -1,6 +1,6 @@ package com.wire.bots.hold.DAO; -import com.wire.bots.hold.model.Event; +import com.wire.bots.hold.model.database.Event; import org.jdbi.v3.core.mapper.ColumnMapper; import org.jdbi.v3.core.statement.StatementContext; import org.jdbi.v3.sqlobject.config.RegisterColumnMapper; diff --git a/src/main/java/com/wire/bots/hold/DAO/EventsResultSetMapper.java b/src/main/java/com/wire/bots/hold/DAO/EventsResultSetMapper.java index 8327ec6..c2180cd 100644 --- a/src/main/java/com/wire/bots/hold/DAO/EventsResultSetMapper.java +++ b/src/main/java/com/wire/bots/hold/DAO/EventsResultSetMapper.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.wire.bots.hold.model.Event; +import com.wire.bots.hold.model.database.Event; import com.wire.xenon.tools.Logger; import org.jdbi.v3.core.mapper.ColumnMapper; import org.jdbi.v3.core.statement.StatementContext; diff --git a/src/main/java/com/wire/bots/hold/FallbackDomainFetcher.java b/src/main/java/com/wire/bots/hold/FallbackDomainFetcher.java index 40741e9..7c7f0fc 100644 --- a/src/main/java/com/wire/bots/hold/FallbackDomainFetcher.java +++ b/src/main/java/com/wire/bots/hold/FallbackDomainFetcher.java @@ -8,6 +8,8 @@ import com.wire.xenon.exceptions.HttpException; import com.wire.xenon.tools.Logger; +import javax.ws.rs.ProcessingException; + public class FallbackDomainFetcher implements Runnable { private final LoginClient loginClient; @@ -47,12 +49,9 @@ public void run() { metadataDAO.insert(MetadataDAO.FALLBACK_DOMAIN_KEY, apiVersionResponse.domain); Cache.setFallbackDomain(apiVersionResponse.domain); } else { - System.out.println("EEEEEEEE -> " + apiVersionResponse.domain); if (metadata.value.equals(apiVersionResponse.domain)) { - System.out.println("EEEEEEEE -> p1"); Cache.setFallbackDomain(apiVersionResponse.domain); } else { - System.out.println("EEEEEEEE -> p2"); String formattedExceptionMessage = String.format( "Database already has a default domain as %s and instead we got %s from the Backend API.", metadata.value, @@ -63,6 +62,8 @@ public void run() { } } catch (HttpException exception) { Logger.exception(exception, "FallbackDomainFetcher.run, exception: %s", exception.getMessage()); + } catch (ProcessingException pexception) { + Logger.info("FallbackDomainFetcher.run, ignoring test exceptions"); } } } diff --git a/src/main/java/com/wire/bots/hold/NotificationProcessor.java b/src/main/java/com/wire/bots/hold/NotificationProcessor.java index 1f504cf..b28534f 100644 --- a/src/main/java/com/wire/bots/hold/NotificationProcessor.java +++ b/src/main/java/com/wire/bots/hold/NotificationProcessor.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.wire.bots.hold.DAO.AccessDAO; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.helium.API; import com.wire.helium.LoginClient; import com.wire.helium.models.Access; diff --git a/src/main/java/com/wire/bots/hold/Service.java b/src/main/java/com/wire/bots/hold/Service.java index dcae767..aa470cd 100644 --- a/src/main/java/com/wire/bots/hold/Service.java +++ b/src/main/java/com/wire/bots/hold/Service.java @@ -19,23 +19,31 @@ import com.codahale.metrics.MetricRegistry; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; +import com.wire.bots.cryptobox.CryptoException; import com.wire.bots.hold.DAO.AccessDAO; import com.wire.bots.hold.DAO.EventsDAO; import com.wire.bots.hold.DAO.MetadataDAO; import com.wire.bots.hold.filters.ServiceAuthenticationFilter; import com.wire.bots.hold.healthchecks.SanityCheck; -import com.wire.bots.hold.model.Config; import com.wire.bots.hold.monitoring.RequestMdcFactoryFilter; import com.wire.bots.hold.monitoring.StatusResource; -import com.wire.bots.hold.resource.*; +import com.wire.bots.hold.resource.v0.audit.*; +import com.wire.bots.hold.resource.v0.backend.ConfirmResourceV0; +import com.wire.bots.hold.resource.v0.backend.InitiateResourceV0; +import com.wire.bots.hold.resource.v0.backend.RemoveResourceV0; +import com.wire.bots.hold.resource.v1.backend.ConfirmResourceV1; +import com.wire.bots.hold.resource.v1.backend.InitiateResourceV1; +import com.wire.bots.hold.resource.v1.backend.RemoveResourceV1; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.bots.hold.utils.CryptoDatabaseFactory; import com.wire.bots.hold.utils.HoldClientRepo; import com.wire.bots.hold.utils.ImagesBundle; import com.wire.helium.LoginClient; import com.wire.xenon.Const; import com.wire.xenon.backend.models.QualifiedId; +import com.wire.xenon.crypto.Crypto; import com.wire.xenon.crypto.CryptoDatabase; import com.wire.xenon.crypto.storage.JdbiStorage; -import com.wire.xenon.factories.CryptoFactory; import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; import io.dropwizard.client.JerseyClientBuilder; @@ -55,6 +63,7 @@ import org.jdbi.v3.sqlobject.SqlObjectPlugin; import javax.ws.rs.client.Client; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -107,20 +116,25 @@ public void run(Config config, Environment environment) throws ExecutionExceptio final Client httpClient = createHttpClient(config, environment); jdbi = buildJdbi(config.database, environment); - final CryptoFactory cf = getCryptoFactory(jdbi); + final CryptoDatabaseFactory cf = getCryptoFactory(jdbi); final AccessDAO accessDAO = jdbi.onDemand(AccessDAO.class); final EventsDAO eventsDAO = jdbi.onDemand(EventsDAO.class); final MetadataDAO metadataDAO = jdbi.onDemand(MetadataDAO.class); + final DeviceManagementService deviceManagementService = new DeviceManagementService(accessDAO, cf); + // Monitoring resources addResource(new StatusResource()); addResource(new RequestMdcFactoryFilter()); // Used by Wire Server - addResource(new InitiateResource(cf)); - addResource(new ConfirmResource(accessDAO)); - addResource(new RemoveResource(accessDAO, cf)); + addResource(new InitiateResourceV0(deviceManagementService)); + addResource(new InitiateResourceV1(deviceManagementService)); + addResource(new ConfirmResourceV0(deviceManagementService)); + addResource(new ConfirmResourceV1(deviceManagementService)); + addResource(new RemoveResourceV0(deviceManagementService)); + addResource(new RemoveResourceV1(deviceManagementService)); // Used by Audit addResource(new AuthorizeResource()); @@ -131,22 +145,15 @@ public void run(Config config, Environment environment) throws ExecutionExceptio addResource(ServiceAuthenticationFilter.ServiceAuthenticationFeature.class); - Runnable run = new Runnable() { - @Override - public void run(){ - new FallbackDomainFetcher( - new LoginClient(httpClient), - metadataDAO - ).run(); - } - }; - final Future fallbackDomainFetcher = environment .lifecycle() .executorService("fallback_domain_fetcher") .build() .submit( - run + new FallbackDomainFetcher( + new LoginClient(httpClient), + metadataDAO + ) ); fallbackDomainFetcher.get(); @@ -206,10 +213,25 @@ protected void setupDatabase(Config.Database database) { flyway.migrate(); } - public CryptoFactory getCryptoFactory(Jdbi jdbi) { - return (botId) -> new CryptoDatabase( - new QualifiedId(botId, null), // TODO(WPB-11287): Change null to default domain - new JdbiStorage(jdbi) - ); + public CryptoDatabaseFactory getCryptoFactory(Jdbi jdbi) { + return new CryptoDatabaseFactory() { + @Override + public Crypto create(UUID botId) throws CryptoException { + // Note: the name botId is incorrect as in LegalHold we are creating crypto box based on users + // but in the Xenon library used by bots and LegalHold, this param was called botId + return new CryptoDatabase( + new QualifiedId(botId, null), + new JdbiStorage(jdbi) + ); + } + + @Override + public Crypto create(QualifiedId userId) throws CryptoException { + return new CryptoDatabase( + userId, + new JdbiStorage(jdbi) + ); + } + }; } } diff --git a/src/main/java/com/wire/bots/hold/healthchecks/SanityCheck.java b/src/main/java/com/wire/bots/hold/healthchecks/SanityCheck.java index 94df99a..d6e6047 100644 --- a/src/main/java/com/wire/bots/hold/healthchecks/SanityCheck.java +++ b/src/main/java/com/wire/bots/hold/healthchecks/SanityCheck.java @@ -2,7 +2,7 @@ import com.codahale.metrics.health.HealthCheck; import com.wire.bots.hold.DAO.AccessDAO; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.bots.hold.utils.Cache; import com.wire.helium.API; import com.wire.xenon.backend.models.QualifiedId; diff --git a/src/main/java/com/wire/bots/hold/model/api/shared/ApiVersionResponse.java b/src/main/java/com/wire/bots/hold/model/api/shared/ApiVersionResponse.java new file mode 100644 index 0000000..5ede5ab --- /dev/null +++ b/src/main/java/com/wire/bots/hold/model/api/shared/ApiVersionResponse.java @@ -0,0 +1,14 @@ +package com.wire.bots.hold.model.api.shared; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class ApiVersionResponse { + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + private final List supported; + + public ApiVersionResponse(List supported) { + this.supported = supported; + } +} diff --git a/src/main/java/com/wire/bots/hold/model/InitResponse.java b/src/main/java/com/wire/bots/hold/model/api/shared/InitResponse.java similarity index 92% rename from src/main/java/com/wire/bots/hold/model/InitResponse.java rename to src/main/java/com/wire/bots/hold/model/api/shared/InitResponse.java index 428ca07..e015e6f 100644 --- a/src/main/java/com/wire/bots/hold/model/InitResponse.java +++ b/src/main/java/com/wire/bots/hold/model/api/shared/InitResponse.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.model; +package com.wire.bots.hold.model.api.shared; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/wire/bots/hold/model/ConfirmPayload.java b/src/main/java/com/wire/bots/hold/model/api/v0/ConfirmPayloadV0.java similarity index 87% rename from src/main/java/com/wire/bots/hold/model/ConfirmPayload.java rename to src/main/java/com/wire/bots/hold/model/api/v0/ConfirmPayloadV0.java index e81a26f..af1a39d 100644 --- a/src/main/java/com/wire/bots/hold/model/ConfirmPayload.java +++ b/src/main/java/com/wire/bots/hold/model/api/v0/ConfirmPayloadV0.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.model; +package com.wire.bots.hold.model.api.v0; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,7 +7,7 @@ import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) -public class ConfirmPayload { +public class ConfirmPayloadV0 { @JsonProperty("refresh_token") @NotNull public String refreshToken; diff --git a/src/main/java/com/wire/bots/hold/model/InitPayload.java b/src/main/java/com/wire/bots/hold/model/api/v0/InitPayloadV0.java similarity index 83% rename from src/main/java/com/wire/bots/hold/model/InitPayload.java rename to src/main/java/com/wire/bots/hold/model/api/v0/InitPayloadV0.java index 79cad46..d6f0b1b 100644 --- a/src/main/java/com/wire/bots/hold/model/InitPayload.java +++ b/src/main/java/com/wire/bots/hold/model/api/v0/InitPayloadV0.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.model; +package com.wire.bots.hold.model.api.v0; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -7,7 +7,7 @@ import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) -public class InitPayload { +public class InitPayloadV0 { @JsonProperty("user_id") @NotNull public UUID userId; diff --git a/src/main/java/com/wire/bots/hold/model/api/v1/ConfirmPayloadV1.java b/src/main/java/com/wire/bots/hold/model/api/v1/ConfirmPayloadV1.java new file mode 100644 index 0000000..fe8ad63 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/model/api/v1/ConfirmPayloadV1.java @@ -0,0 +1,27 @@ +package com.wire.bots.hold.model.api.v1; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.wire.xenon.backend.models.QualifiedId; + +import javax.validation.constraints.NotNull; +import java.util.UUID; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ConfirmPayloadV1 { + @JsonProperty("refresh_token") + @NotNull + public String refreshToken; + + @JsonProperty("client_id") + @NotNull + public String clientId; + + @JsonProperty("qualified_user_id") + @NotNull + public QualifiedId userId; + + @JsonProperty("team_id") + @NotNull + public UUID teamId; +} diff --git a/src/main/java/com/wire/bots/hold/model/api/v1/InitPayloadV1.java b/src/main/java/com/wire/bots/hold/model/api/v1/InitPayloadV1.java new file mode 100644 index 0000000..2f4ddb9 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/model/api/v1/InitPayloadV1.java @@ -0,0 +1,19 @@ +package com.wire.bots.hold.model.api.v1; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.wire.xenon.backend.models.QualifiedId; + +import javax.validation.constraints.NotNull; +import java.util.UUID; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class InitPayloadV1 { + @JsonProperty("qualified_user_id") + @NotNull + public QualifiedId userId; + + @JsonProperty("team_id") + @NotNull + public UUID teamId; +} diff --git a/src/main/java/com/wire/bots/hold/model/Event.java b/src/main/java/com/wire/bots/hold/model/database/Event.java similarity index 82% rename from src/main/java/com/wire/bots/hold/model/Event.java rename to src/main/java/com/wire/bots/hold/model/database/Event.java index 9f0cd07..a7c49eb 100644 --- a/src/main/java/com/wire/bots/hold/model/Event.java +++ b/src/main/java/com/wire/bots/hold/model/database/Event.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.model; +package com.wire.bots.hold.model.database; import java.util.UUID; diff --git a/src/main/java/com/wire/bots/hold/model/LHAccess.java b/src/main/java/com/wire/bots/hold/model/database/LHAccess.java similarity index 85% rename from src/main/java/com/wire/bots/hold/model/LHAccess.java rename to src/main/java/com/wire/bots/hold/model/database/LHAccess.java index 89221e4..f6d79c6 100644 --- a/src/main/java/com/wire/bots/hold/model/LHAccess.java +++ b/src/main/java/com/wire/bots/hold/model/database/LHAccess.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.model; +package com.wire.bots.hold.model.database; import java.util.UUID; diff --git a/src/main/java/com/wire/bots/hold/model/dto/InitializedDeviceDTO.java b/src/main/java/com/wire/bots/hold/model/dto/InitializedDeviceDTO.java new file mode 100644 index 0000000..4266ec4 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/model/dto/InitializedDeviceDTO.java @@ -0,0 +1,29 @@ +package com.wire.bots.hold.model.dto; + +import com.wire.xenon.models.otr.PreKey; + +import java.util.ArrayList; + +public class InitializedDeviceDTO { + private final ArrayList preKeys; + private final PreKey lastPreKey; + private final String fingerprint; + + public InitializedDeviceDTO(ArrayList preKeys, PreKey lastPreKey, String fingerprint) { + this.preKeys = preKeys; + this.lastPreKey = lastPreKey; + this.fingerprint = fingerprint; + } + + public ArrayList getPreKeys() { + return preKeys; + } + + public PreKey getLastPreKey() { + return lastPreKey; + } + + public String getFingerprint() { + return fingerprint; + } +} diff --git a/src/main/java/com/wire/bots/hold/monitoring/ApiVersionResource.java b/src/main/java/com/wire/bots/hold/monitoring/ApiVersionResource.java new file mode 100644 index 0000000..16f0dc9 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/monitoring/ApiVersionResource.java @@ -0,0 +1,45 @@ +// +// Wire +// Copyright (C) 2016 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +package com.wire.bots.hold.monitoring; + +import com.wire.bots.hold.model.api.shared.ApiVersionResponse; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +@Api +@Path("/api-version") +@Produces(MediaType.APPLICATION_JSON) +public class ApiVersionResource { + @GET + @ApiOperation(value = "Api version") + public Response apiVersion() { + ApiVersionResponse response = new ApiVersionResponse(List.of(0,1)); + + return Response + .ok(response) + .build(); + } +} diff --git a/src/main/java/com/wire/bots/hold/monitoring/StatusResource.java b/src/main/java/com/wire/bots/hold/monitoring/StatusResource.java index 724fee7..c44e87d 100644 --- a/src/main/java/com/wire/bots/hold/monitoring/StatusResource.java +++ b/src/main/java/com/wire/bots/hold/monitoring/StatusResource.java @@ -35,7 +35,7 @@ public class StatusResource { @ApiOperation(value = "Status") public Response statusEmpty() { return Response - .ok() - .build(); + .ok() + .build(); } } diff --git a/src/main/java/com/wire/bots/hold/resource/ConfirmResource.java b/src/main/java/com/wire/bots/hold/resource/ConfirmResource.java deleted file mode 100644 index 9cea782..0000000 --- a/src/main/java/com/wire/bots/hold/resource/ConfirmResource.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.wire.bots.hold.resource; - -import com.wire.bots.hold.DAO.AccessDAO; -import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.ConfirmPayload; -import com.wire.xenon.tools.Logger; -import io.swagger.annotations.*; - -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -@Api -@Path("/confirm") -@Produces(MediaType.APPLICATION_JSON) -public class ConfirmResource { - private final AccessDAO accessDAO; - - public ConfirmResource(AccessDAO accessDAO) { - this.accessDAO = accessDAO; - } - - @POST - @ServiceAuthorization - @ApiOperation(value = "Confirm legal hold device") - @ApiResponses(value = { - @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), - @ApiResponse(code = 500, message = "Something went wrong"), - @ApiResponse(code = 200, message = "Legal Hold Device enabled")}) - public Response confirm(@ApiParam @Valid @NotNull ConfirmPayload payload) { - try { - int insert = accessDAO.insert( - // TODO(WPB-11287): Add new column in DB for domain - // TODO(WPB-11287): + Use default domain for v0 - payload.userId, - payload.clientId, - payload.refreshToken - ); - - if (0 == insert) { - Logger.error("ConfirmResource: Failed to insert Access %s:%s", - payload.userId, - payload.clientId); - - return Response. - serverError(). - build(); - } - - Logger.info("ConfirmResource: team: %s, user:%s, client: %s", - payload.teamId, - payload.userId, - payload.clientId); - - return Response. - ok(). - build(); - } catch (Exception e) { - Logger.exception(e, "ConfirmResource.confirm: %s err: %s", payload.userId, e.getMessage()); - return Response - .ok(e) - .status(500) - .build(); - } - } -} diff --git a/src/main/java/com/wire/bots/hold/resource/InitiateResource.java b/src/main/java/com/wire/bots/hold/resource/InitiateResource.java deleted file mode 100644 index 9a3f3a7..0000000 --- a/src/main/java/com/wire/bots/hold/resource/InitiateResource.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.wire.bots.hold.resource; - -import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.InitPayload; -import com.wire.bots.hold.model.InitResponse; -import com.wire.xenon.crypto.Crypto; -import com.wire.xenon.factories.CryptoFactory; -import com.wire.xenon.models.otr.PreKey; -import com.wire.xenon.tools.Logger; -import io.swagger.annotations.*; - -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import java.util.ArrayList; - -import static com.wire.bots.hold.utils.Tools.hexify; - -@Api -@Path("/initiate") -@Produces(MediaType.APPLICATION_JSON) -public class InitiateResource { - private final CryptoFactory cf; - - public InitiateResource(CryptoFactory cf) { - - this.cf = cf; - } - - @POST - @ServiceAuthorization - @ApiOperation(value = "Initiate", response = InitResponse.class) - @ApiResponses(value = { - @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), - @ApiResponse(code = 500, message = "Something went wrong"), - @ApiResponse(code = 200, message = "CryptoBox initiated")}) - public Response initiate(@ApiParam @Valid @NotNull InitPayload init) { - try (Crypto crypto = cf.create(init.userId)) { - ArrayList preKeys = crypto.newPreKeys(0, 50); - PreKey lastKey = crypto.newLastPreKey(); - byte[] fingerprint = crypto.getLocalFingerprint(); - - InitResponse response = new InitResponse(); - response.preKeys = preKeys; - response.lastPreKey = lastKey; - response.fingerprint = hexify(fingerprint); - - Logger.info("InitiateResource: team: %s, user: %s", init.teamId, init.userId); - - return Response. - ok(response). - build(); - } catch (Exception e) { - Logger.exception(e, "InitiateResource: %s", e.getMessage()); - return Response - .ok(e) - .status(500) - .build(); - } - } -} diff --git a/src/main/java/com/wire/bots/hold/resource/RemoveResource.java b/src/main/java/com/wire/bots/hold/resource/RemoveResource.java deleted file mode 100644 index 56c4a5e..0000000 --- a/src/main/java/com/wire/bots/hold/resource/RemoveResource.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.wire.bots.hold.resource; - -import com.wire.bots.hold.DAO.AccessDAO; -import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.InitPayload; -import com.wire.xenon.crypto.Crypto; -import com.wire.xenon.factories.CryptoFactory; -import com.wire.xenon.tools.Logger; -import io.swagger.annotations.*; - -import javax.validation.Valid; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -@Api -@Path("/remove") -@Produces(MediaType.APPLICATION_JSON) -public class RemoveResource { - private final CryptoFactory cf; - private final AccessDAO accessDAO; - - public RemoveResource(AccessDAO accessDAO, CryptoFactory cf) { - this.accessDAO = accessDAO; - this.cf = cf; - } - - @POST - @ServiceAuthorization - @ApiOperation(value = "Remove legal hold device") - @ApiResponses(value = { - @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), - @ApiResponse(code = 500, message = "Something went wrong"), - @ApiResponse(code = 200, message = "Legal Hold Device was removed")}) - public Response remove(@ApiParam @Valid InitPayload payload) { - try { - try (Crypto crypto = cf.create(payload.userId)) { - crypto.purge(); - } - - int removeAccess = accessDAO.disable(payload.userId); - - Logger.info("RemoveResource: team: %s, user: %s, removed: %s", - payload.teamId, - payload.userId, - removeAccess); - - return Response. - ok(). - build(); - } catch (Exception e) { - Logger.exception(e, "RemoveResource.remove: %s", e.getMessage()); - return Response - .ok(e) - .status(500) - .build(); - } - } -} diff --git a/src/main/java/com/wire/bots/hold/resource/AuthorizeResource.java b/src/main/java/com/wire/bots/hold/resource/v0/audit/AuthorizeResource.java similarity index 96% rename from src/main/java/com/wire/bots/hold/resource/AuthorizeResource.java rename to src/main/java/com/wire/bots/hold/resource/v0/audit/AuthorizeResource.java index cfacf01..f78aa47 100644 --- a/src/main/java/com/wire/bots/hold/resource/AuthorizeResource.java +++ b/src/main/java/com/wire/bots/hold/resource/v0/audit/AuthorizeResource.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.resource; +package com.wire.bots.hold.resource.v0.audit; import com.wire.bots.hold.Service; import com.wire.xenon.backend.models.ErrorMessage; diff --git a/src/main/java/com/wire/bots/hold/resource/ConversationResource.java b/src/main/java/com/wire/bots/hold/resource/v0/audit/ConversationResource.java similarity index 98% rename from src/main/java/com/wire/bots/hold/resource/ConversationResource.java rename to src/main/java/com/wire/bots/hold/resource/v0/audit/ConversationResource.java index ed226e3..7637839 100644 --- a/src/main/java/com/wire/bots/hold/resource/ConversationResource.java +++ b/src/main/java/com/wire/bots/hold/resource/v0/audit/ConversationResource.java @@ -1,4 +1,4 @@ -package com.wire.bots.hold.resource; +package com.wire.bots.hold.resource.v0.audit; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -10,8 +10,8 @@ import com.wire.bots.hold.DAO.AssetsDAO; import com.wire.bots.hold.DAO.EventsDAO; import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.Event; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.Event; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.bots.hold.utils.Cache; import com.wire.bots.hold.utils.Collector; import com.wire.bots.hold.utils.PdfGenerator; diff --git a/src/main/java/com/wire/bots/hold/resource/DevicesResource.java b/src/main/java/com/wire/bots/hold/resource/v0/audit/DevicesResource.java similarity index 97% rename from src/main/java/com/wire/bots/hold/resource/DevicesResource.java rename to src/main/java/com/wire/bots/hold/resource/v0/audit/DevicesResource.java index cac059a..a3659b9 100644 --- a/src/main/java/com/wire/bots/hold/resource/DevicesResource.java +++ b/src/main/java/com/wire/bots/hold/resource/v0/audit/DevicesResource.java @@ -1,11 +1,11 @@ -package com.wire.bots.hold.resource; +package com.wire.bots.hold.resource.v0.audit; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; import com.wire.bots.hold.DAO.AccessDAO; import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.xenon.crypto.Crypto; import com.wire.xenon.factories.CryptoFactory; import com.wire.xenon.tools.Logger; diff --git a/src/main/java/com/wire/bots/hold/resource/EventsResource.java b/src/main/java/com/wire/bots/hold/resource/v0/audit/EventsResource.java similarity index 96% rename from src/main/java/com/wire/bots/hold/resource/EventsResource.java rename to src/main/java/com/wire/bots/hold/resource/v0/audit/EventsResource.java index 852a887..60236f3 100644 --- a/src/main/java/com/wire/bots/hold/resource/EventsResource.java +++ b/src/main/java/com/wire/bots/hold/resource/v0/audit/EventsResource.java @@ -1,11 +1,11 @@ -package com.wire.bots.hold.resource; +package com.wire.bots.hold.resource.v0.audit; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; import com.wire.bots.hold.DAO.EventsDAO; import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.Event; +import com.wire.bots.hold.model.database.Event; import com.wire.xenon.tools.Logger; import io.swagger.annotations.*; diff --git a/src/main/java/com/wire/bots/hold/resource/IndexResource.java b/src/main/java/com/wire/bots/hold/resource/v0/audit/IndexResource.java similarity index 96% rename from src/main/java/com/wire/bots/hold/resource/IndexResource.java rename to src/main/java/com/wire/bots/hold/resource/v0/audit/IndexResource.java index bd0110a..95aa4b9 100644 --- a/src/main/java/com/wire/bots/hold/resource/IndexResource.java +++ b/src/main/java/com/wire/bots/hold/resource/v0/audit/IndexResource.java @@ -1,11 +1,11 @@ -package com.wire.bots.hold.resource; +package com.wire.bots.hold.resource.v0.audit; import com.github.mustachejava.DefaultMustacheFactory; import com.github.mustachejava.Mustache; import com.github.mustachejava.MustacheFactory; import com.wire.bots.hold.DAO.EventsDAO; import com.wire.bots.hold.filters.ServiceAuthorization; -import com.wire.bots.hold.model.Event; +import com.wire.bots.hold.model.database.Event; import com.wire.xenon.tools.Logger; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; diff --git a/src/main/java/com/wire/bots/hold/resource/v0/backend/ConfirmResourceV0.java b/src/main/java/com/wire/bots/hold/resource/v0/backend/ConfirmResourceV0.java new file mode 100644 index 0000000..0f91e83 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v0/backend/ConfirmResourceV0.java @@ -0,0 +1,55 @@ +package com.wire.bots.hold.resource.v0.backend; + +import com.wire.bots.hold.filters.ServiceAuthorization; +import com.wire.bots.hold.model.api.v0.ConfirmPayloadV0; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.xenon.backend.models.QualifiedId; +import com.wire.xenon.tools.Logger; +import io.swagger.annotations.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Api +@Path("/confirm") +@Produces(MediaType.APPLICATION_JSON) +public class ConfirmResourceV0 { + private final DeviceManagementService deviceManagementService; + + public ConfirmResourceV0(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; + } + + @POST + @ServiceAuthorization + @ApiOperation(value = "Confirm legal hold device") + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), + @ApiResponse(code = 500, message = "Something went wrong"), + @ApiResponse(code = 200, message = "Legal Hold Device enabled")}) + public Response confirm(@ApiParam @Valid @NotNull ConfirmPayloadV0 payload) { + try { + deviceManagementService.confirmDevice( + new QualifiedId(payload.userId, null), //TODO Probably a good place to put the DEFAULT_DOMAIN + payload.teamId, + payload.clientId, + payload.refreshToken + ); + + return Response + .ok() + .build(); + } catch (Exception e) { + Logger.exception(e, "ConfirmResourceV0: %s err: %s", payload.userId, e.getMessage()); + return Response + .ok(e) + .status(500) + .build(); + } + } +} diff --git a/src/main/java/com/wire/bots/hold/resource/v0/backend/InitiateResourceV0.java b/src/main/java/com/wire/bots/hold/resource/v0/backend/InitiateResourceV0.java new file mode 100644 index 0000000..38dfcb9 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v0/backend/InitiateResourceV0.java @@ -0,0 +1,60 @@ +package com.wire.bots.hold.resource.v0.backend; + +import com.wire.bots.hold.filters.ServiceAuthorization; +import com.wire.bots.hold.model.api.shared.InitResponse; +import com.wire.bots.hold.model.api.v0.InitPayloadV0; +import com.wire.bots.hold.model.dto.InitializedDeviceDTO; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.xenon.backend.models.QualifiedId; +import com.wire.xenon.tools.Logger; +import io.swagger.annotations.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Api +@Path("/initiate") +@Produces(MediaType.APPLICATION_JSON) +public class InitiateResourceV0 { + private final DeviceManagementService deviceManagementService; + + public InitiateResourceV0(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; + } + + @POST + @ServiceAuthorization + @ApiOperation(value = "Initiate", response = InitResponse.class) + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), + @ApiResponse(code = 500, message = "Something went wrong"), + @ApiResponse(code = 200, message = "CryptoBox initiated")}) + public Response initiate(@ApiParam @Valid @NotNull InitPayloadV0 init) { + try { + final InitializedDeviceDTO initializedDeviceDTO = + deviceManagementService.initiateLegalHoldDevice( + new QualifiedId(init.userId, null), //TODO Probably a good place to put the DEFAULT_DOMAIN + init.teamId + ); + + InitResponse response = new InitResponse(); + response.preKeys = initializedDeviceDTO.getPreKeys(); + response.lastPreKey = initializedDeviceDTO.getLastPreKey(); + response.fingerprint = initializedDeviceDTO.getFingerprint(); + return Response. + ok(response). + build(); + } catch (Exception e) { + Logger.exception(e, "InitiateResourceV0 error: %s", e.getMessage()); + return Response + .ok(e) + .status(500) + .build(); + } + } +} diff --git a/src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java b/src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java new file mode 100644 index 0000000..7068233 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java @@ -0,0 +1,52 @@ +package com.wire.bots.hold.resource.v0.backend; + +import com.wire.bots.hold.filters.ServiceAuthorization; +import com.wire.bots.hold.model.api.v0.InitPayloadV0; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.xenon.backend.models.QualifiedId; +import com.wire.xenon.tools.Logger; +import io.swagger.annotations.*; + +import javax.validation.Valid; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Api +@Path("/remove") +@Produces(MediaType.APPLICATION_JSON) +public class RemoveResourceV0 { + private final DeviceManagementService deviceManagementService; + + public RemoveResourceV0(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; + } + + @POST + @ServiceAuthorization + @ApiOperation(value = "Remove legal hold device") + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), + @ApiResponse(code = 500, message = "Something went wrong"), + @ApiResponse(code = 200, message = "Legal Hold Device was removed")}) + public Response remove(@ApiParam @Valid InitPayloadV0 payload) { + try { + deviceManagementService.removeDevice( + new QualifiedId(payload.userId, null), //TODO Probably a good place to put the DEFAULT_DOMAIN + payload.teamId + ); + + return Response. + ok(). + build(); + } catch (Exception e) { + Logger.exception(e, "RemoveResourceV0 error %s", e.getMessage()); + return Response + .ok(e) + .status(500) + .build(); + } + } +} diff --git a/src/main/java/com/wire/bots/hold/resource/v1/backend/ConfirmResourceV1.java b/src/main/java/com/wire/bots/hold/resource/v1/backend/ConfirmResourceV1.java new file mode 100644 index 0000000..a32883d --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v1/backend/ConfirmResourceV1.java @@ -0,0 +1,53 @@ +package com.wire.bots.hold.resource.v1.backend; + +import com.wire.bots.hold.filters.ServiceAuthorization; +import com.wire.bots.hold.model.api.v1.ConfirmPayloadV1; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.xenon.tools.Logger; +import io.swagger.annotations.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Api +@Path("/v1/confirm") +@Produces(MediaType.APPLICATION_JSON) +public class ConfirmResourceV1 { + private final DeviceManagementService deviceManagementService; + + public ConfirmResourceV1(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; + } + + @POST + @ServiceAuthorization + @ApiOperation(value = "Confirm legal hold device") + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), + @ApiResponse(code = 500, message = "Something went wrong"), + @ApiResponse(code = 200, message = "Legal Hold Device enabled")}) + public Response confirm(@ApiParam @Valid @NotNull ConfirmPayloadV1 payload) { + try { + deviceManagementService.confirmDevice( + payload.userId, + payload.teamId, + payload.clientId, + payload.refreshToken + ); + + return Response + .ok() + .build(); + } catch (Exception e) { + Logger.exception(e, "ConfirmResourceV1: %s err: %s", payload.userId, e.getMessage()); + return Response + .serverError() + .build(); + } + } +} diff --git a/src/main/java/com/wire/bots/hold/resource/v1/backend/InitiateResourceV1.java b/src/main/java/com/wire/bots/hold/resource/v1/backend/InitiateResourceV1.java new file mode 100644 index 0000000..6e2627f --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v1/backend/InitiateResourceV1.java @@ -0,0 +1,58 @@ +package com.wire.bots.hold.resource.v1.backend; + +import com.wire.bots.hold.filters.ServiceAuthorization; +import com.wire.bots.hold.model.api.shared.InitResponse; +import com.wire.bots.hold.model.api.v1.InitPayloadV1; +import com.wire.bots.hold.model.dto.InitializedDeviceDTO; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.xenon.tools.Logger; +import io.swagger.annotations.*; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Api +@Path("/v1/initiate") +@Produces(MediaType.APPLICATION_JSON) +public class InitiateResourceV1 { + private final DeviceManagementService deviceManagementService; + + public InitiateResourceV1(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; + } + + @POST + @ServiceAuthorization + @ApiOperation(value = "Initiate", response = InitResponse.class) + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), + @ApiResponse(code = 500, message = "Something went wrong"), + @ApiResponse(code = 200, message = "CryptoBox initiated")}) + public Response initiate(@ApiParam @Valid @NotNull InitPayloadV1 init) { + try { + final InitializedDeviceDTO initializedDeviceDTO = + deviceManagementService.initiateLegalHoldDevice( + init.userId, + init.teamId + ); + + InitResponse response = new InitResponse(); + response.preKeys = initializedDeviceDTO.getPreKeys(); + response.lastPreKey = initializedDeviceDTO.getLastPreKey(); + response.fingerprint = initializedDeviceDTO.getFingerprint(); + return Response + .ok(response) + .build(); + } catch (Exception e) { + Logger.exception(e, "InitiateResourceV1 error: %s", e.getMessage()); + return Response + .serverError() + .build(); + } + } +} diff --git a/src/main/java/com/wire/bots/hold/resource/v1/backend/RemoveResourceV1.java b/src/main/java/com/wire/bots/hold/resource/v1/backend/RemoveResourceV1.java new file mode 100644 index 0000000..d38ebb9 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v1/backend/RemoveResourceV1.java @@ -0,0 +1,47 @@ +package com.wire.bots.hold.resource.v1.backend; + +import com.wire.bots.hold.filters.ServiceAuthorization; +import com.wire.bots.hold.model.api.v1.InitPayloadV1; +import com.wire.bots.hold.service.DeviceManagementService; +import com.wire.xenon.tools.Logger; +import io.swagger.annotations.*; + +import javax.validation.Valid; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +@Api +@Path("/v1/remove") +@Produces(MediaType.APPLICATION_JSON) +public class RemoveResourceV1 { + private final DeviceManagementService deviceManagementService; + + public RemoveResourceV1(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; + } + + @POST + @ServiceAuthorization + @ApiOperation(value = "Remove legal hold device") + @ApiResponses(value = { + @ApiResponse(code = 400, message = "Bad request. Invalid Payload"), + @ApiResponse(code = 500, message = "Something went wrong"), + @ApiResponse(code = 200, message = "Legal Hold Device was removed")}) + public Response remove(@ApiParam @Valid InitPayloadV1 payload) { + try { + deviceManagementService.removeDevice(payload.userId, payload.teamId); + + return Response + .ok() + .build(); + } catch (Exception e) { + Logger.exception(e, "RemoveResourceV1 error: %s", e.getMessage()); + return Response + .serverError() + .build(); + } + } +} diff --git a/src/main/java/com/wire/bots/hold/service/DeviceManagementService.java b/src/main/java/com/wire/bots/hold/service/DeviceManagementService.java new file mode 100644 index 0000000..8086e2c --- /dev/null +++ b/src/main/java/com/wire/bots/hold/service/DeviceManagementService.java @@ -0,0 +1,111 @@ +package com.wire.bots.hold.service; + +import com.wire.bots.cryptobox.CryptoException; +import com.wire.bots.hold.DAO.AccessDAO; +import com.wire.bots.hold.model.dto.InitializedDeviceDTO; +import com.wire.bots.hold.utils.CryptoDatabaseFactory; +import com.wire.xenon.backend.models.QualifiedId; +import com.wire.xenon.crypto.Crypto; +import com.wire.xenon.models.otr.PreKey; +import com.wire.xenon.tools.Logger; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.UUID; + +import static com.wire.bots.hold.utils.Tools.hexify; + +public class DeviceManagementService { + private final CryptoDatabaseFactory cf; + private final AccessDAO accessDAO; + + public DeviceManagementService(AccessDAO accessDAO, CryptoDatabaseFactory cf) { + this.accessDAO = accessDAO; + this.cf = cf; + } + + /** + * Initializes cryptobox for a new user on LegalHold machine. + * + *

+ * Generates Prekeys and sends them back to the backend in order to be distributed by other clients + * interacting with this user + *

+ * @param userId user setup to be put under legal hold + * @param teamId user's own team + * @return generated prekeys + * @throws IOException + * @throws CryptoException + */ + public InitializedDeviceDTO initiateLegalHoldDevice(QualifiedId userId, UUID teamId) throws IOException, CryptoException { + try (Crypto crypto = cf.create(userId)) { + ArrayList preKeys = crypto.newPreKeys(0, 50); + PreKey lastKey = crypto.newLastPreKey(); + byte[] fingerprint = crypto.getLocalFingerprint(); + + InitializedDeviceDTO device = new InitializedDeviceDTO(preKeys, lastKey, hexify(fingerprint)); + + Logger.info("InitiateLegalHoldDevice: team: %s, user: %s", teamId, userId); + + return device; + } catch (Exception e) { + Logger.exception(e, "InitiateLegalHoldDevice: %s", e.getMessage()); + throw e; + } + } + + /** + * Confirm a user's device under legal hold. + * + *

+ * Stores the refreshToken in order to fetch user notifications while under legal hold + *

+ * @param userId user setup to be put under legal hold + * @param teamId user's own team + * @param clientId user's device + * @param refreshToken token used to get expiring api tokens + */ + public void confirmDevice(QualifiedId userId, UUID teamId, String clientId, String refreshToken) { + int insert = accessDAO.insert(userId.id, + clientId, + refreshToken); + + if (0 == insert) { + Logger.error("ConfirmResource: Failed to insert Access %s:%s", + userId, + clientId); + + throw new RuntimeException("Cannot insert new device"); + } + + Logger.info("ConfirmResource: team: %s, user:%s, client: %s", + teamId, + userId, + clientId); + } + + /** + * Remove a user from legal hold. + * @param userId user setup to be put under legal hold + * @param teamId user's own team + * @throws IOException + * @throws CryptoException + */ + public void removeDevice(QualifiedId userId, UUID teamId) throws IOException, CryptoException { + try (Crypto crypto = cf.create(userId)) { + crypto.purge(); + + int removeAccess = accessDAO.disable(userId.id); + + Logger.info( + "RemoveResource: team: %s, user: %s, removed: %s", + teamId, + userId, + removeAccess + ); + } catch (Exception e) { + Logger.exception(e, "RemoveLegalHoldDevice: %s", e.getMessage()); + throw e; + } + } +} diff --git a/src/main/java/com/wire/bots/hold/utils/CryptoDatabaseFactory.java b/src/main/java/com/wire/bots/hold/utils/CryptoDatabaseFactory.java new file mode 100644 index 0000000..2346468 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/utils/CryptoDatabaseFactory.java @@ -0,0 +1,10 @@ +package com.wire.bots.hold.utils; + +import com.wire.bots.cryptobox.CryptoException; +import com.wire.xenon.backend.models.QualifiedId; +import com.wire.xenon.crypto.Crypto; +import com.wire.xenon.factories.CryptoFactory; + +public interface CryptoDatabaseFactory extends CryptoFactory { + Crypto create(QualifiedId userId) throws CryptoException; +} diff --git a/src/main/java/com/wire/bots/hold/utils/HoldClientRepo.java b/src/main/java/com/wire/bots/hold/utils/HoldClientRepo.java index f808e2a..db95d1c 100644 --- a/src/main/java/com/wire/bots/hold/utils/HoldClientRepo.java +++ b/src/main/java/com/wire/bots/hold/utils/HoldClientRepo.java @@ -2,7 +2,7 @@ import com.wire.bots.cryptobox.CryptoException; import com.wire.bots.hold.DAO.AccessDAO; -import com.wire.bots.hold.model.LHAccess; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.helium.API; import com.wire.xenon.WireClient; import com.wire.xenon.backend.models.QualifiedId; diff --git a/src/test/java/com/wire/bots/hold/ConfirmRemoveResourceV1Test.java b/src/test/java/com/wire/bots/hold/ConfirmRemoveResourceV1Test.java new file mode 100644 index 0000000..41474f1 --- /dev/null +++ b/src/test/java/com/wire/bots/hold/ConfirmRemoveResourceV1Test.java @@ -0,0 +1,133 @@ +package com.wire.bots.hold; + +import com.wire.bots.hold.DAO.AccessDAO; +import com.wire.bots.hold.model.api.v1.ConfirmPayloadV1; +import com.wire.bots.hold.model.api.v1.InitPayloadV1; +import com.wire.bots.hold.model.database.LHAccess; +import com.wire.bots.hold.utils.HttpTestUtils; +import com.wire.xenon.backend.models.QualifiedId; +import io.dropwizard.testing.ConfigOverride; +import io.dropwizard.testing.DropwizardTestSupport; +import org.apache.http.HttpStatus; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.UUID; + +public class ConfirmRemoveResourceV1Test { + private static final String TOKEN = "dummy"; + private static final DropwizardTestSupport SUPPORT = new DropwizardTestSupport<>( + Service.class, "hold.yaml", + ConfigOverride.config("token", TOKEN), + ConfigOverride.config("apiHost", "dummy")); + private static Client client; + private static AccessDAO accessDAO; + + @BeforeClass + public static void init() throws Exception { + SUPPORT.before(); + client = HttpTestUtils.createHttpClient(SUPPORT.getConfiguration(), SUPPORT.getEnvironment()); + + Service app = SUPPORT.getApplication(); + accessDAO = app.getJdbi().onDemand(AccessDAO.class); + } + + @AfterClass + public static void afterClass() { + client.close(); + SUPPORT.after(); + } + + @Test + public void givenConfirmAndRemove_ReturnOk() { + final QualifiedId userId = new QualifiedId(UUID.randomUUID(), UUID.randomUUID().toString()); + final UUID teamId = UUID.randomUUID(); + final String refreshToken = UUID.randomUUID().toString(); + final String clientId = UUID.randomUUID().toString(); + + ConfirmPayloadV1 confirmPayloadV1 = new ConfirmPayloadV1(); + confirmPayloadV1.userId = userId; + confirmPayloadV1.teamId = teamId; + confirmPayloadV1.refreshToken = refreshToken; + confirmPayloadV1.clientId = clientId; + + final Response confirmResponse = postConfirmV1(confirmPayloadV1); + assert confirmResponse.getStatus() == HttpStatus.SC_OK; + + InitPayloadV1 initPayloadV1 = new InitPayloadV1(); + initPayloadV1.userId = userId; + initPayloadV1.teamId = teamId; + + final Response removeResponse = postRemoveV1(initPayloadV1); + assert removeResponse.getStatus() == HttpStatus.SC_OK; + } + + @Test + public void givenRepeatedConfirm_ReturnOk() { + final QualifiedId userId = new QualifiedId(UUID.randomUUID(), UUID.randomUUID().toString()); + final UUID teamId = UUID.randomUUID(); + final String refreshToken = UUID.randomUUID().toString(); + final String clientId = UUID.randomUUID().toString(); + + ConfirmPayloadV1 confirmPayloadV1 = new ConfirmPayloadV1(); + confirmPayloadV1.userId = userId; + confirmPayloadV1.teamId = teamId; + confirmPayloadV1.refreshToken = refreshToken; + confirmPayloadV1.clientId = clientId; + + final Response confirmResponse = postConfirmV1(confirmPayloadV1); + assert confirmResponse.getStatus() == HttpStatus.SC_OK; + + final String updatedRefreshToken = UUID.randomUUID().toString(); + confirmPayloadV1.refreshToken = updatedRefreshToken; + + final Response confirmSecondResponse = postConfirmV1(confirmPayloadV1); + assert confirmSecondResponse.getStatus() == HttpStatus.SC_OK; + + // Confirm the same device is possible and the latest refresh_token should be persisted + final LHAccess lhAccess = accessDAO.get(userId.id); + assert lhAccess != null; + assert lhAccess.cookie.equals(updatedRefreshToken); + assert !lhAccess.created.equals(lhAccess.updated); + } + + @Test + public void givenRemoveUnknownUser_ReturnOk() { + // Remove device is idempotent on confirmed devices and also does not report errors trying to remove unknown users + final QualifiedId userId = new QualifiedId(UUID.randomUUID(), UUID.randomUUID().toString()); + final UUID teamId = UUID.randomUUID(); + + InitPayloadV1 initPayloadV1 = new InitPayloadV1(); + initPayloadV1.userId = userId; + initPayloadV1.teamId = teamId; + + final Response removeResponse = postRemoveV1(initPayloadV1); + assert removeResponse.getStatus() == HttpStatus.SC_OK; + } + + private Response postConfirmV1(ConfirmPayloadV1 confirmPayloadV1){ + return client.target("http://localhost:" + SUPPORT.getLocalPort()) + .path("v1") + .path("confirm") + .request(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, TOKEN) + .accept(MediaType.APPLICATION_JSON) + .post(Entity.entity(confirmPayloadV1, MediaType.APPLICATION_JSON)); + } + + private Response postRemoveV1(InitPayloadV1 initPayloadV1){ + return client.target("http://localhost:" + SUPPORT.getLocalPort()) + .path("v1") + .path("remove") + .request(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, TOKEN) + .accept(MediaType.APPLICATION_JSON) + .post(Entity.entity(initPayloadV1, MediaType.APPLICATION_JSON)); + } +} diff --git a/src/test/java/com/wire/bots/hold/DatabaseTest.java b/src/test/java/com/wire/bots/hold/DatabaseTest.java index 833b28f..3bb7f88 100644 --- a/src/test/java/com/wire/bots/hold/DatabaseTest.java +++ b/src/test/java/com/wire/bots/hold/DatabaseTest.java @@ -6,10 +6,9 @@ import com.wire.bots.hold.DAO.AssetsDAO; import com.wire.bots.hold.DAO.EventsDAO; import com.wire.bots.hold.DAO.MetadataDAO; -import com.wire.bots.hold.model.Config; -import com.wire.bots.hold.model.Event; -import com.wire.bots.hold.model.LHAccess; import com.wire.bots.hold.model.Metadata; +import com.wire.bots.hold.model.database.Event; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.xenon.backend.models.QualifiedId; import com.wire.xenon.models.TextMessage; import io.dropwizard.testing.ConfigOverride; @@ -20,11 +19,11 @@ import java.util.*; -//@Ignore("integration test, needs DB") public class DatabaseTest { private static final DropwizardTestSupport SUPPORT = new DropwizardTestSupport<>( - Service.class, "hold.yaml", - ConfigOverride.config("token", "dummy")); + Service.class, "hold.yaml", + ConfigOverride.config("token", "dummy"), + ConfigOverride.config("apiHost", "dummy")); private static final ObjectMapper mapper = new ObjectMapper(); private static AssetsDAO assetsDAO; diff --git a/src/test/java/com/wire/bots/hold/FallbackDomainFetcherTest.java b/src/test/java/com/wire/bots/hold/FallbackDomainFetcherTest.java index 26f9c4c..d35a8b2 100644 --- a/src/test/java/com/wire/bots/hold/FallbackDomainFetcherTest.java +++ b/src/test/java/com/wire/bots/hold/FallbackDomainFetcherTest.java @@ -11,10 +11,7 @@ import org.junit.Test; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.times; +import static org.mockito.Mockito.*; public class FallbackDomainFetcherTest { diff --git a/src/test/java/com/wire/bots/hold/InitiateResourceV1Test.java b/src/test/java/com/wire/bots/hold/InitiateResourceV1Test.java new file mode 100644 index 0000000..33d754c --- /dev/null +++ b/src/test/java/com/wire/bots/hold/InitiateResourceV1Test.java @@ -0,0 +1,87 @@ +package com.wire.bots.hold; + +import com.wire.bots.hold.model.api.shared.InitResponse; +import com.wire.bots.hold.model.api.v1.InitPayloadV1; +import com.wire.bots.hold.utils.HttpTestUtils; +import com.wire.xenon.backend.models.QualifiedId; +import io.dropwizard.testing.ConfigOverride; +import io.dropwizard.testing.DropwizardTestSupport; +import org.apache.http.HttpStatus; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.UUID; + +public class InitiateResourceV1Test { + private static final String TOKEN = "dummy"; + private static final DropwizardTestSupport SUPPORT = new DropwizardTestSupport<>( + Service.class, "hold.yaml", + ConfigOverride.config("token", TOKEN), + ConfigOverride.config("apiHost", "dummy")); + private static Client client; + + @BeforeClass + public static void init() throws Exception { + SUPPORT.before(); + client = HttpTestUtils.createHttpClient(SUPPORT.getConfiguration(), SUPPORT.getEnvironment()); + } + + @AfterClass + public static void afterClass() { + client.close(); + SUPPORT.after(); + } + + @Test + public void givenApiV1Body_ReturnKeys() { + final QualifiedId userId = new QualifiedId(UUID.randomUUID(), UUID.randomUUID().toString()); + final UUID teamId = UUID.randomUUID(); + + final InitResponse response = postInitiationV1(userId, teamId, TOKEN) + .readEntity(InitResponse.class); + + assert response != null; + assert response.lastPreKey != null; + assert !response.preKeys.isEmpty(); + assert response.fingerprint != null; + } + + @Test + public void givenWrongApiV1Body_Return4xx() { + final QualifiedId userId = null; + final UUID teamId = UUID.randomUUID(); + + final Response response = postInitiationV1(userId, teamId, TOKEN); + + assert response.getStatus() >= 400; + } + + @Test + public void givenWrongToken_ReturnUnauthorized() { + final QualifiedId userId = new QualifiedId(UUID.randomUUID(), UUID.randomUUID().toString()); + final UUID teamId = UUID.randomUUID(); + + final Response response = postInitiationV1(userId, teamId, "wrong_token"); + assert response.getStatus() == HttpStatus.SC_UNAUTHORIZED; + } + + private Response postInitiationV1(QualifiedId userId, UUID teamId, String token){ + InitPayloadV1 initPayloadV1 = new InitPayloadV1(); + initPayloadV1.userId = userId; + initPayloadV1.teamId = teamId; + + return client.target("http://localhost:" + SUPPORT.getLocalPort()) + .path("v1") + .path("initiate") + .request(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, token) + .accept(MediaType.APPLICATION_JSON) + .post(Entity.entity(initPayloadV1, MediaType.APPLICATION_JSON)); + } +} diff --git a/src/test/java/com/wire/bots/hold/utils/HttpTestUtils.java b/src/test/java/com/wire/bots/hold/utils/HttpTestUtils.java new file mode 100644 index 0000000..f9c8d1f --- /dev/null +++ b/src/test/java/com/wire/bots/hold/utils/HttpTestUtils.java @@ -0,0 +1,19 @@ +package com.wire.bots.hold.utils; + +import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; +import com.wire.bots.hold.Config; +import io.dropwizard.client.JerseyClientBuilder; +import io.dropwizard.setup.Environment; +import org.glassfish.jersey.media.multipart.MultiPartFeature; + +import javax.ws.rs.client.Client; + +public class HttpTestUtils { + public static Client createHttpClient(Config config, Environment env) { + return new JerseyClientBuilder(env) + .using(config.getJerseyClient()) + .withProvider(MultiPartFeature.class) + .withProvider(JacksonJsonProvider.class) + .build("test-client"); + } +} diff --git a/src/test/java/com/wire/bots/hold/utils/TestCache.java b/src/test/java/com/wire/bots/hold/utils/TestCache.java index 3a16723..0c36fb2 100644 --- a/src/test/java/com/wire/bots/hold/utils/TestCache.java +++ b/src/test/java/com/wire/bots/hold/utils/TestCache.java @@ -2,7 +2,6 @@ import com.wire.xenon.backend.models.QualifiedId; import com.wire.xenon.backend.models.User; -import org.junit.Test; import java.io.File; import java.util.UUID;