From bae2b46208ddee498b2659251d910a92ca29f403 Mon Sep 17 00:00:00 2001 From: spoonman01 Date: Thu, 3 Oct 2024 12:20:07 +0200 Subject: [PATCH] feat(add-api-v1) Add new resources handling qualified id requests from the backend * 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/NotificationProcessor.java | 3 +- src/main/java/com/wire/bots/hold/Service.java | 53 +++++-- .../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/resource/ConfirmResource.java | 66 --------- .../bots/hold/resource/InitiateResource.java | 65 --------- .../{ => 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 | 57 ++++++++ .../v0/backend/InitiateResourceV0.java | 60 ++++++++ .../backend/RemoveResourceV0.java} | 37 ++--- .../v1/backend/ConfirmResourceV1.java | 53 +++++++ .../v1/backend/InitiateResourceV1.java | 58 ++++++++ .../resource/v1/backend/RemoveResourceV1.java | 49 +++++++ .../hold/service/DeviceManagementService.java | 111 +++++++++++++++ .../hold/utils/CryptoDatabaseFactory.java | 10 ++ .../wire/bots/hold/utils/HoldClientRepo.java | 2 +- .../hold/ConfirmRemoveResourceV1Test.java | 132 ++++++++++++++++++ .../java/com/wire/bots/hold/DatabaseTest.java | 6 +- .../bots/hold/InitiateResourceV1Test.java | 86 ++++++++++++ .../wire/bots/hold/utils/HttpTestUtils.java | 19 +++ 38 files changed, 851 insertions(+), 196 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 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 rename src/main/java/com/wire/bots/hold/resource/{RemoveResource.java => v0/backend/RemoveResourceV0.java} (52%) 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/NotificationProcessor.java b/src/main/java/com/wire/bots/hold/NotificationProcessor.java index 3cf36c2..6a8bc86 100644 --- a/src/main/java/com/wire/bots/hold/NotificationProcessor.java +++ b/src/main/java/com/wire/bots/hold/NotificationProcessor.java @@ -3,10 +3,9 @@ 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.Config; -import com.wire.bots.hold.model.LHAccess; import com.wire.bots.hold.model.Notification; import com.wire.bots.hold.model.NotificationList; +import com.wire.bots.hold.model.database.LHAccess; import com.wire.helium.LoginClient; import com.wire.helium.models.Access; import com.wire.xenon.backend.models.Payload; diff --git a/src/main/java/com/wire/bots/hold/Service.java b/src/main/java/com/wire/bots/hold/Service.java index 568fdf7..67d3b3a 100644 --- a/src/main/java/com/wire/bots/hold/Service.java +++ b/src/main/java/com/wire/bots/hold/Service.java @@ -19,22 +19,30 @@ 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.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; @@ -54,6 +62,7 @@ import org.jdbi.v3.sqlobject.SqlObjectPlugin; import javax.ws.rs.client.Client; +import java.util.UUID; import java.util.concurrent.TimeUnit; public class Service extends Application { @@ -105,19 +114,24 @@ public void run(Config config, Environment environment) { 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 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()); @@ -185,10 +199,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 80df73a..0408aed 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.helium.API; import com.wire.xenon.backend.models.QualifiedId; import com.wire.xenon.tools.Logger; 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..68328ee --- /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; + } +} \ No newline at end of file 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..567062d --- /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; + } +} \ No newline at end of file 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..5a0c771 --- /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/resource/ConfirmResource.java b/src/main/java/com/wire/bots/hold/resource/ConfirmResource.java deleted file mode 100644 index 352f489..0000000 --- a/src/main/java/com/wire/bots/hold/resource/ConfirmResource.java +++ /dev/null @@ -1,66 +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(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/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 6a7c649..a95e697 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..6d82c84 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v0/backend/ConfirmResourceV0.java @@ -0,0 +1,57 @@ +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..6f444f4 --- /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/RemoveResource.java b/src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java similarity index 52% rename from src/main/java/com/wire/bots/hold/resource/RemoveResource.java rename to src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java index 56c4a5e..805c6b6 100644 --- a/src/main/java/com/wire/bots/hold/resource/RemoveResource.java +++ b/src/main/java/com/wire/bots/hold/resource/v0/backend/RemoveResourceV0.java @@ -1,10 +1,9 @@ -package com.wire.bots.hold.resource; +package com.wire.bots.hold.resource.v0.backend; -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.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.*; @@ -18,13 +17,11 @@ @Api @Path("/remove") @Produces(MediaType.APPLICATION_JSON) -public class RemoveResource { - private final CryptoFactory cf; - private final AccessDAO accessDAO; +public class RemoveResourceV0 { + private final DeviceManagementService deviceManagementService; - public RemoveResource(AccessDAO accessDAO, CryptoFactory cf) { - this.accessDAO = accessDAO; - this.cf = cf; + public RemoveResourceV0(DeviceManagementService deviceManagementService) { + this.deviceManagementService = deviceManagementService; } @POST @@ -34,24 +31,18 @@ public RemoveResource(AccessDAO accessDAO, CryptoFactory cf) { @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) { + public Response remove(@ApiParam @Valid InitPayloadV0 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); + 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, "RemoveResource.remove: %s", e.getMessage()); + Logger.exception(e, "RemoveResourceV0 error %s", e.getMessage()); return Response .ok(e) .status(500) 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..7395785 --- /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..e70bd34 --- /dev/null +++ b/src/main/java/com/wire/bots/hold/resource/v1/backend/RemoveResourceV1.java @@ -0,0 +1,49 @@ +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..08ed44b --- /dev/null +++ b/src/test/java/com/wire/bots/hold/ConfirmRemoveResourceV1Test.java @@ -0,0 +1,132 @@ +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)); + 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 8ee64b9..684239e 100644 --- a/src/test/java/com/wire/bots/hold/DatabaseTest.java +++ b/src/test/java/com/wire/bots/hold/DatabaseTest.java @@ -5,9 +5,8 @@ import com.wire.bots.hold.DAO.AccessDAO; import com.wire.bots.hold.DAO.AssetsDAO; import com.wire.bots.hold.DAO.EventsDAO; -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.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; @@ -18,7 +17,6 @@ import java.util.*; -//@Ignore("integration test, needs DB") public class DatabaseTest { private static final DropwizardTestSupport SUPPORT = new DropwizardTestSupport<>( Service.class, "hold.yaml", 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..1dbe8ad --- /dev/null +++ b/src/test/java/com/wire/bots/hold/InitiateResourceV1Test.java @@ -0,0 +1,86 @@ +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)); + 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"); + } +}