diff --git a/README.md b/README.md index 3e20dd8..153035f 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,10 @@ Uses [lithium](https://github.com/wireapp/lithium) to utilize Wire Bot API. } ``` -Only `name` is mandatory. Specify `url` if you want to use your _Webhook_ to receive events from Wire Backend. Leave `url` _null_ if you -prefer _Websocket_. `avatar` for your bot is optional, and it is `Base64` encoded `jpeg`|`png` image. If `avatar` field is left _null_ +Only `name` is mandatory. Specify `url` if you want to use your _Webhook_ to receive events from Wire Backend. +Leave `url` _null_ if you +prefer _Websocket_. `avatar` for your bot is optional, and it is `Base64` encoded `jpeg`|`png` image. If `avatar` field +is left _null_ default avatar is assigned for the Service. After creating your Service the following json is returned: @@ -62,15 +64,19 @@ After creating your Service the following json is returned: } ``` -Go to your _Team Settings_ page and navigate to _Services_ tab. Add this `service_code` and enable this service for your team. Now your team +Go to your _Team Settings_ page and navigate to _Services_ tab. Add this `service_code` and enable this service for your +team. Now your team members should be able to see your _Service_ when they open _people picker_ and navigate to _services_ tab. ### Webhook -In case `url` was specified when creating the service webhook will be used. All requests coming from Wire to your Service's endpoint will +In case `url` was specified when creating the service webhook will be used. All requests coming from Wire to your +Service's endpoint will have HTTP Header `Authorization` with value: -`Bearer `. Make sure you verify this value in your webhook implementation. Wire will send events as `POST` HTTP -request to the `url` you specified when creating the Service. Your webhook should always return HTTP code `200` as the result. +`Bearer `. Make sure you verify this value in your webhook implementation. Wire will send events +as `POST` HTTP +request to the `url` you specified when creating the Service. Your webhook should always return HTTP code `200` as the +result. ### Websocket @@ -97,7 +103,8 @@ wss://proxy.services.wire.com/api/await/`` } ``` -Your service must be available at the moment `bot_request` event is sent. It must respond with http code `200`. In case of Websocket +Your service must be available at the moment `bot_request` event is sent. It must respond with http code `200`. In case +of Websocket implementation it is enough the socket is connected to the Proxy at that moment. - `conversation.init` If your Service responded with `200` to a `bot_request` another event is sent: `init`. @@ -232,7 +239,8 @@ implementation it is enough the socket is connected to the Proxy at that moment. ### Posting back to Wire conversation -If the event contains `token` field this `token` can be used to respond to this event by sending `Outgoing Message` like: +If the event contains `token` field this `token` can be used to respond to this event by sending `Outgoing Message` +like: Example: @@ -242,7 +250,8 @@ curl -X POST https://proxy.services.wire.com/api/conversation -d '{"type": "text ``` In order to post text, or an image as a bot into Wire conversation you need to send a `POST` request to `/conversation` -You must also specify the HTTP header as `Authorization:Bearer ` where `token` was obtained in `init` or other events +You must also specify the HTTP header as `Authorization:Bearer ` where `token` was obtained in `init` or other +events like: `new_text` or `new_image`. _Outgoing Message_ can be of 4 types: @@ -263,7 +272,33 @@ _Outgoing Message_ can be of 4 types: ``` { "type": "attachment", - "attachment": { "mimeType" : "image/jpeg", "data" : "..." } + "attachment": { + "mimeType" : "image/jpeg", + "height" : 320, + "width" : 160, + "size" : 2048, + "meta" : { + "assetId" : "3-cef231a2-23f2-429e-b76a-b7649594d3fe", + "assetToken" : "...", // Optional + "sha256" : "...", // Base64 encoded SHA256 digest of the file + "otrKey" : "..." // Base64 encoded otr key used to encrypt the file + } + } +} +``` + +or + +``` +{ + "type": "attachment", + "attachment": { + "mimeType" : "image/jpeg", + "height" : 320, + "width" : 160, + "size" : 2048, + "data" : "..." // Base64 encoded image data + } } ``` @@ -323,7 +358,8 @@ The best way how to run Roman is to use Docker, another option is to run the Rom ### Configuration -Almost all necessary variables and configurations are located in the [roman.yaml](roman.yaml). Following environment variables should be +Almost all necessary variables and configurations are located in the [roman.yaml](roman.yaml). Following environment +variables should be set. ```bash @@ -365,7 +401,8 @@ openssl rand -hex 32 We provide [Dockerfile](Dockerfile) and the prepared [runtime image](https://github.com/wireapp/cryptobox4j/blob/master/dockerfiles/Dockerfile.runtime) - -[wirebot/runtime](https://hub.docker.com/r/wirebot/runtime). We don't provide the whole Roman docker image, but feel free to build one from +[wirebot/runtime](https://hub.docker.com/r/wirebot/runtime). We don't provide the whole Roman docker image, but feel +free to build one from the code, all necessary files are present in this repository. #### Build docker image from source code @@ -379,7 +416,8 @@ docker build -t roman:latest . #### Example of Docker run command on local machine (without HTTPS) -In order to run the Roman locally, to test the proxy itself (not sending data to Wire backend) one do not need to specify the HTTPS +In order to run the Roman locally, to test the proxy itself (not sending data to Wire backend) one do not need to +specify the HTTPS certificate and run following command: ```bash @@ -399,7 +437,8 @@ docker run \ #### Example with docker-compose (without HTTPS) -We include [docker-compose.yml](docker-compose.yml) file to run the testing instance of Roman locally using Docker Compose. It includes all +We include [docker-compose.yml](docker-compose.yml) file to run the testing instance of Roman locally using Docker +Compose. It includes all necessary variables and PostgreSQL instance, to get the testing instance up and running. Simply execute: ```bash @@ -408,18 +447,22 @@ docker-compose -f docker-compose.yml up #### Production deployment -In order to run the Roman in the production, one needs to have an HTTPS and to set the `ROMAN_PUB_KEY_BASE64` as well as `PROXY_DOMAIN` +In order to run the Roman in the production, one needs to have an HTTPS and to set the `ROMAN_PUB_KEY_BASE64` as well +as `PROXY_DOMAIN` env variables. See [Configuration section](#configuration) how to obtain them. ### Native JVM -As previously mentioned, Wire recommends running the Roman as a docker container. However, you can run it natively on the JVM as well. -Please note that Roman requires JVM >= 11. To run it natively, one needs to install [Cryptobox4j](https://github.com/wireapp/cryptobox4j) +As previously mentioned, Wire recommends running the Roman as a docker container. However, you can run it natively on +the JVM as well. +Please note that Roman requires JVM >= 11. To run it natively, one needs to +install [Cryptobox4j](https://github.com/wireapp/cryptobox4j) and other cryptographic libraries. You can use [Docker Build Image](https://github.com/wireapp/cryptobox4j/blob/master/dockerfiles/Dockerfile.cryptobox) as an inspiration what needs to be installed and what environment variables need to be set to get the Cryptobox working. -Also, don't forget to read the [Configuration section](#configuration) and set all necessary environment variables for the Roman itselgf. +Also, don't forget to read the [Configuration section](#configuration) and set all necessary environment variables for +the Roman itselgf. First, it is necessary to build the application: @@ -437,16 +480,20 @@ java -jar target/roman.jar server roman.yaml ## Simple Guide to Roman Deployment -The previous lines should give you all necessary material you need how to deploy the Roman in multiple environment and how to set everything -up. Even though Wire runs Roman in cloud and uses Kubernetes setup, we decided to provide as simple guide as possible to deploy your own -Roman using just a `docker-compose`. The following lines provides specific and opinionated simple guide, that requires just few basic +The previous lines should give you all necessary material you need how to deploy the Roman in multiple environment and +how to set everything +up. Even though Wire runs Roman in cloud and uses Kubernetes setup, we decided to provide as simple guide as possible to +deploy your own +Roman using just a `docker-compose`. The following lines provides specific and opinionated simple guide, that requires +just few basic things: - a machine with Docker, Docker Compose and OpenSSL installed - the machine has a public IP address and DNS record pointing to that IP address - *(optional)* install `jq` in order to browse and search in logs -In this example we take the DNS as `roman.example.com`, when deploying, change this value to your own domain. In order to obtain the +In this example we take the DNS as `roman.example.com`, when deploying, change this value to your own domain. In order +to obtain the certificate, we will use [Traefik](https://traefik.io/) edge router and [Let's Encrypt](https://letsencrypt.org/). ### Step by step @@ -457,7 +504,8 @@ certificate, we will use [Traefik](https://traefik.io/) edge router and [Let's E git clone git@github.com:wireapp/roman.git ``` -2. Set the correct DNS in the [docker-compose.prod.yml](docker-compose.prod.yml) - replace `roman.example.com` with your own and replace +2. Set the correct DNS in the [docker-compose.prod.yml](docker-compose.prod.yml) - replace `roman.example.com` with your + own and replace the `developers@example.com` email address with our own email. 3. Create `.env.prod` file that will contain all necessary environmental variables. @@ -483,11 +531,15 @@ docker-compose -f docker-compose.prod.yml --env-file .env.prod up --build -d ``` 5. Check the logs - * proxy - `docker-compose -f docker-compose.prod.yml logs proxy` - should show some noise about certificate and routes registration - * Roman - `docker-compose -f docker-compose.prod.yml logs roman` - should show normal starting procedure and no errors - * with Roman, you can pipe logs data to `jq` (if installed), that way you will see nice and formatted JSONs instead of just lines. - -6. Give it some time to obtain necessary certificates - around 10 minutes should be fine. Then try to access the `https://roman.example.com` + * proxy - `docker-compose -f docker-compose.prod.yml logs proxy` - should show some noise about certificate and + routes registration + * Roman - `docker-compose -f docker-compose.prod.yml logs roman` - should show normal starting procedure and no + errors + * with Roman, you can pipe logs data to `jq` (if installed), that way you will see nice and formatted JSONs instead + of just lines. + +6. Give it some time to obtain necessary certificates - around 10 minutes should be fine. Then try to access + the `https://roman.example.com` to see whether the HTTPS works as expected. If yes, proceed, if no troubleshoot with Traefik proxy. 7. Now you need to download real public key and encode it in base64 - see [Getting the ROMAN_PUB_KEY_BASE64](#getting-the-roman_pub_key_base64) diff --git a/backend/pom.xml b/backend/pom.xml index 249fffa..347ed75 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -6,7 +6,7 @@ com.wire.bots roman - 1.18.2 + 1.20.0 Roman Wire Bot API Proxy @@ -28,8 +28,8 @@ true - 3.5.1 - 2.1.4 + 3.6.0 + 4.0.0 0.11.5 @@ -47,7 +47,7 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4.2 + 2.15.1 io.jsonwebtoken @@ -66,17 +66,10 @@ ${jwt.version} runtime - - io.dropwizard-bundles + com.bazaarvoice.dropwizard dropwizard-configurable-assets-bundle - 1.3.5 - - - io.dropwizard - dropwizard-servlets - - + 0.2.2 io.dropwizard-bundles @@ -88,27 +81,11 @@ javax-websocket-server-impl 9.4.49.v20220914 - - com.liveperson - dropwizard-websockets - 1.3.14 - - - io.dropwizard - dropwizard-core - - - org.eclipse.jetty.websocket - javax-websocket-server-impl - - - io.dropwizard dropwizard-servlets ${dropwizard.version} - io.dropwizard dropwizard-testing @@ -118,7 +95,7 @@ org.mockito mockito-core - 4.8.0 + 5.2.0 test @@ -130,7 +107,7 @@ org.assertj assertj-core - 3.23.1 + 3.24.2 test diff --git a/backend/roman.yaml b/backend/roman.yaml index e6e1c34..134a625 100644 --- a/backend/roman.yaml +++ b/backend/roman.yaml @@ -12,8 +12,6 @@ server: requestLog: appenders: - type: ${APPENDER_TYPE:-console} - filterFactories: - - type: status-filter-factory logging: level: INFO diff --git a/backend/src/main/java/com/wire/bots/roman/Application.java b/backend/src/main/java/com/wire/bots/roman/Application.java index 576eedd..7528c0b 100644 --- a/backend/src/main/java/com/wire/bots/roman/Application.java +++ b/backend/src/main/java/com/wire/bots/roman/Application.java @@ -35,15 +35,13 @@ import com.wire.xenon.MessageHandlerBase; import com.wire.xenon.factories.CryptoFactory; import com.wire.xenon.factories.StorageFactory; -import io.dropwizard.bundles.assets.ConfiguredAssetsBundle; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; -import io.dropwizard.websockets.WebsocketBundle; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; import io.jsonwebtoken.security.Keys; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.FilterRegistration; import org.eclipse.jetty.servlets.CrossOriginFilter; -import javax.servlet.DispatcherType; -import javax.servlet.FilterRegistration; import java.security.Key; import java.util.EnumSet; import java.util.concurrent.ExecutorService; @@ -72,7 +70,6 @@ public void initialize(Bootstrap bootstrap) { instance = (Application) bootstrap.getApplication(); bootstrap.addBundle(new WebsocketBundle(WebSocket.class)); bootstrap.addCommand(new UpdateCertCommand()); - bootstrap.addBundle(new ConfiguredAssetsBundle("/assets/", "/", "index.html")); } @Override diff --git a/backend/src/main/java/com/wire/bots/roman/CachedClientRepo.java b/backend/src/main/java/com/wire/bots/roman/CachedClientRepo.java index b3ad115..45b6950 100644 --- a/backend/src/main/java/com/wire/bots/roman/CachedClientRepo.java +++ b/backend/src/main/java/com/wire/bots/roman/CachedClientRepo.java @@ -12,9 +12,9 @@ import com.wire.xenon.models.otr.Missing; import com.wire.xenon.models.otr.Recipients; import com.wire.xenon.tools.Logger; +import jakarta.ws.rs.client.Client; import org.checkerframework.checker.nullness.qual.Nullable; -import javax.ws.rs.client.Client; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; diff --git a/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java b/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java index 7312282..8073e78 100644 --- a/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java +++ b/backend/src/main/java/com/wire/bots/roman/ImageProcessor.java @@ -1,6 +1,6 @@ package com.wire.bots.roman; -import com.wire.xenon.assets.Picture; +import com.wire.bots.roman.resources.Picture; import javax.imageio.ImageIO; import java.awt.*; @@ -11,14 +11,8 @@ public class ImageProcessor { private static final int MEDIUM_DIMENSION = 2896; - private static final int SMALL_DIMENSION = 2896; - - public static Picture getMediumImage(Picture original) throws Exception { - return getScaledImage(original, MEDIUM_DIMENSION); - } - - public static Picture getSmallImage(Picture original) throws Exception { - return getScaledImage(original, SMALL_DIMENSION); + public static Picture getMediumImage(Picture picture) throws Exception { + return getScaledImage(picture, MEDIUM_DIMENSION); } private static Boolean shouldScaleOriginalSize(double width, double height, double dimension) { @@ -59,12 +53,9 @@ private static BufferedImage resizeImage(BufferedImage originalImage, return resizedImage; } - private static Picture getScaledImage(Picture image, double dimension) throws Exception { + private static Picture getScaledImage(Picture image, int dimension) throws Exception { String resultImageType; switch (image.getMimeType()) { - case "image/gif": - resultImageType = "gif"; - break; case "image/jpeg": resultImageType = "jpg"; break; @@ -89,9 +80,7 @@ private static Picture getScaledImage(Picture image, double dimension) throws Ex try (ByteArrayOutputStream resultStream = new ByteArrayOutputStream()) { ImageIO.write(resultImage, resultImageType, resultStream); resultStream.flush(); - Picture picture = new Picture(resultStream.toByteArray(), image.getMimeType()); - picture.setRetention(image.getRetention()); - return picture; + return new Picture(resultStream.toByteArray(), image.getMimeType()); } } diff --git a/backend/src/main/java/com/wire/bots/roman/MessageHandler.java b/backend/src/main/java/com/wire/bots/roman/MessageHandler.java index 1629121..6027f9e 100644 --- a/backend/src/main/java/com/wire/bots/roman/MessageHandler.java +++ b/backend/src/main/java/com/wire/bots/roman/MessageHandler.java @@ -15,13 +15,13 @@ import com.wire.xenon.backend.models.User; import com.wire.xenon.models.*; import com.wire.xenon.tools.Logger; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.jdbi.v3.core.Jdbi; -import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.Base64; import java.util.UUID; @@ -430,7 +430,7 @@ private boolean send(OutgoingMessage message) { return WebSocket.send(provider.id, message); } } catch (Exception e) { - Logger.exception("MessageHandler.send: error %s", e, e.getMessage()); + Logger.error("MessageHandler.send: error %s", e.getMessage()); return false; } } @@ -465,7 +465,7 @@ private void trace(OutgoingMessage message) { try { if (Logger.getLevel() == Level.FINE) { ObjectMapper objectMapper = new ObjectMapper(); - Logger.debug(objectMapper.writeValueAsString(message)); + Logger.info(objectMapper.writeValueAsString(message)); } } catch (Exception ignore) { diff --git a/backend/src/main/java/com/wire/bots/roman/ProviderClient.java b/backend/src/main/java/com/wire/bots/roman/ProviderClient.java index 82cea2e..3701fbc 100644 --- a/backend/src/main/java/com/wire/bots/roman/ProviderClient.java +++ b/backend/src/main/java/com/wire/bots/roman/ProviderClient.java @@ -7,15 +7,15 @@ import com.wire.xenon.models.AssetKey; import com.wire.xenon.tools.Logger; import com.wire.xenon.tools.Util; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; import javax.validation.constraints.NotNull; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Cookie; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.NewCookie; -import javax.ws.rs.core.Response; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; diff --git a/backend/src/main/java/com/wire/bots/roman/Sender.java b/backend/src/main/java/com/wire/bots/roman/Sender.java index b8c03a0..d6b3103 100644 --- a/backend/src/main/java/com/wire/bots/roman/Sender.java +++ b/backend/src/main/java/com/wire/bots/roman/Sender.java @@ -150,37 +150,34 @@ private UUID sendAttachment(IncomingMessage message, UUID botId) throws Exceptio @Nullable private UUID sendPicture(IncomingMessage message, UUID botId) throws Exception { try (WireClient wireClient = getWireClient(botId)) { - final UUID messageId = UUID.randomUUID(); final Attachment attachment = message.attachment; - Picture asset; + UUID messageId = UUID.randomUUID(); + ImagePreview preview = new ImagePreview(messageId, attachment.mimeType); + preview.setHeight(attachment.height); + preview.setWidth(attachment.width); + preview.setSize(attachment.size.intValue()); + + wireClient.send(preview); + + ImageAsset image; if (message.attachment.meta != null) { - asset = new Picture(messageId, attachment.mimeType); - setAssetMetadata(asset, message.attachment.meta); - asset.setHeight(attachment.height); - asset.setWidth(attachment.width); - asset.setSize(attachment.size.intValue()); + image = new ImageAsset(preview.getMessageId(), new byte[0], attachment.mimeType); + setAssetMetadata(image, message.attachment.meta); } else if (message.attachment.data != null) { final byte[] bytes = Base64.getDecoder().decode(message.attachment.data); - asset = new Picture(bytes, attachment.mimeType); - uploadAssetData(wireClient, asset); + image = new ImageAsset(messageId, bytes, attachment.mimeType); + image.setMessageId(messageId); + uploadAssetData(wireClient, image); } else { throw new Exception("Meta or Data need to be set"); } - wireClient.send(asset); - return asset.getMessageId(); + wireClient.send(image); + return image.getMessageId(); } } - private WireClient getWireClient(UUID botId) throws IOException, CryptoException { - final WireClient wireClient = repo.getClient(botId); - if (wireClient == null) { - throw new MissingStateException(botId); - } - return wireClient; - } - @Nullable public UUID sendNewPoll(IncomingMessage message, UUID botId) throws Exception { try (WireClient wireClient = getWireClient(botId)) { @@ -235,10 +232,19 @@ private void setAssetMetadata(AssetBase asset, AssetMeta meta) { asset.setOtrKey(Base64.getDecoder().decode(meta.otrKey)); } - private void uploadAssetData(WireClient wireClient, AssetBase asset) throws Exception { + private AssetKey uploadAssetData(WireClient wireClient, AssetBase asset) throws Exception { final AssetKey assetKey = wireClient.uploadAsset(asset); asset.setAssetKey(assetKey.id); asset.setAssetToken(assetKey.token); asset.setDomain(assetKey.domain); + return assetKey; + } + + private WireClient getWireClient(UUID botId) throws IOException, CryptoException { + final WireClient wireClient = repo.getClient(botId); + if (wireClient == null) { + throw new MissingStateException(botId); + } + return wireClient; } } diff --git a/backend/src/main/java/com/wire/bots/roman/WebsocketBundle.java b/backend/src/main/java/com/wire/bots/roman/WebsocketBundle.java new file mode 100644 index 0000000..2437097 --- /dev/null +++ b/backend/src/main/java/com/wire/bots/roman/WebsocketBundle.java @@ -0,0 +1,154 @@ +/** + * The MIT License + * Copyright (c) 2017 LivePerson, Inc. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.wire.bots.roman; + +import com.codahale.metrics.MetricRegistry; +import com.wire.bots.roman.model.Config; +import io.dropwizard.core.ConfiguredBundle; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; +import io.dropwizard.jetty.MutableServletContextHandler; +import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.websocket.common.events.EventDriverFactory; +import org.eclipse.jetty.websocket.jsr356.server.ServerContainer; +import org.eclipse.jetty.websocket.server.NativeWebSocketConfiguration; +import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; + +public class WebsocketBundle implements ConfiguredBundle, LifeCycle.Listener { + + private final Collection endpointConfigs = new ArrayList<>(); + private static final Logger LOG = LoggerFactory.getLogger(WebsocketBundle.class); + volatile boolean starting = false; + private final ServerEndpointConfig.Configurator defaultConfigurator; + + + public WebsocketBundle(ServerEndpointConfig.Configurator defaultConfigurator, Class... endpoints) { + this(defaultConfigurator, Arrays.asList(endpoints), new ArrayList<>()); + } + + public WebsocketBundle(Class... endpoints) { + this(null, Arrays.asList(endpoints), new ArrayList<>()); + } + + public WebsocketBundle(ServerEndpointConfig... configs) { + this(null, new ArrayList<>(), Arrays.asList(configs)); + } + + public WebsocketBundle(ServerEndpointConfig.Configurator defaultConfigurator, Collection> endpointClasses, Collection serverEndpointConfigs) { + this.defaultConfigurator = defaultConfigurator; + endpointClasses.forEach(this::addEndpoint); + this.endpointConfigs.addAll(serverEndpointConfigs); + } + + public void addEndpoint(ServerEndpointConfig epC) { + endpointConfigs.add(epC); + if (starting) + throw new RuntimeException("can't add endpoint after starting lifecycle"); + } + + public void addEndpoint(Class clazz) { + ServerEndpoint anno = clazz.getAnnotation(ServerEndpoint.class); + if(anno == null){ + throw new RuntimeException(clazz.getCanonicalName()+" does not have a "+ServerEndpoint.class.getCanonicalName()+" annotation"); + } + ServerEndpointConfig.Builder bldr = ServerEndpointConfig.Builder.create(clazz, anno.value()); + if(defaultConfigurator != null){ + bldr.configurator(defaultConfigurator); + } + endpointConfigs.add(bldr.build()); + if (starting) + throw new RuntimeException("can't add endpoint after starting lifecycle"); + } + + @Override + public void initialize(Bootstrap bootstrap) { + } + + @Override + public void run(Config config, Environment environment) { + environment.lifecycle().addEventListener(new LifeCycle.Listener() { + @Override + public void lifeCycleStarting(LifeCycle event) { + starting = true; + try { + ServerContainer wsContainer = InstWebSocketServerContainerInitializer. + configureContext(environment.getApplicationContext(), environment.metrics()); + + StringBuilder sb = new StringBuilder("Registering websocket endpoints: ") + .append(System.lineSeparator()) + .append(System.lineSeparator()); + endpointConfigs.forEach(rethrow(conf -> addEndpoint(wsContainer, conf, sb))); + LOG.info(sb.toString()); + } catch (ServletException ex) { + throw new RuntimeException(ex); + } + } + + private void addEndpoint(ServerContainer wsContainer, ServerEndpointConfig conf, StringBuilder sb) throws DeploymentException { + wsContainer.addEndpoint(conf); + sb.append(String.format(" WS %s (%s)", conf.getPath(), conf.getEndpointClass().getName())).append(System.lineSeparator()); + } + }); + } + public static class InstWebSocketServerContainerInitializer { + public static ServerContainer configureContext(final MutableServletContextHandler context, final MetricRegistry metrics) throws ServletException { + WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context); + NativeWebSocketConfiguration wsConfig = filter.getConfiguration(); + + ServerContainer wsContainer = new ServerContainer(wsConfig, context.getServer().getThreadPool()); + EventDriverFactory edf = wsConfig.getFactory().getEventDriverFactory(); + edf.clearImplementations(); + + //edf.addImplementation(new InstJsrServerEndpointImpl(metrics)); + //edf.addImplementation(new InstJsrServerExtendsEndpointImpl(metrics)); + context.addBean(wsContainer); + context.setAttribute(javax.websocket.server.ServerContainer.class.getName(), wsContainer); + context.setAttribute(WebSocketUpgradeFilter.class.getName(), filter); + return wsContainer; + } + } + + public static Consumer rethrow(ConsumerCheckException c) { + return t -> { + try { + c.accept(t); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + }; + } + + @FunctionalInterface + public interface ConsumerCheckException { + void accept(T elem) throws Exception; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/wire/bots/roman/commands/UpdateCertCommand.java b/backend/src/main/java/com/wire/bots/roman/commands/UpdateCertCommand.java index d0525ef..151342e 100644 --- a/backend/src/main/java/com/wire/bots/roman/commands/UpdateCertCommand.java +++ b/backend/src/main/java/com/wire/bots/roman/commands/UpdateCertCommand.java @@ -1,24 +1,24 @@ package com.wire.bots.roman.commands; -import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; +import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider; import com.wire.bots.roman.DAO.ProvidersDAO; import com.wire.bots.roman.ProviderClient; import com.wire.bots.roman.Tools; import com.wire.bots.roman.model.Config; import com.wire.bots.roman.model.Provider; import com.wire.xenon.tools.Logger; -import io.dropwizard.cli.ConfiguredCommand; import io.dropwizard.client.JerseyClientBuilder; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.core.cli.ConfiguredCommand; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.jdbi.v3.core.Jdbi; import org.jdbi.v3.sqlobject.SqlObjectPlugin; -import javax.ws.rs.client.Client; -import javax.ws.rs.core.NewCookie; -import javax.ws.rs.core.Response; import java.util.List; public class UpdateCertCommand extends ConfiguredCommand { diff --git a/backend/src/main/java/com/wire/bots/roman/filters/BackendAuthenticationFilter.java b/backend/src/main/java/com/wire/bots/roman/filters/BackendAuthenticationFilter.java index 7dbee52..cf111f1 100644 --- a/backend/src/main/java/com/wire/bots/roman/filters/BackendAuthenticationFilter.java +++ b/backend/src/main/java/com/wire/bots/roman/filters/BackendAuthenticationFilter.java @@ -1,16 +1,16 @@ package com.wire.bots.roman.filters; import io.swagger.annotations.Authorization; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.DynamicFeature; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.FeatureContext; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.Provider; import java.util.Objects; import static com.wire.bots.roman.Const.WIRE_AUTH; diff --git a/backend/src/main/java/com/wire/bots/roman/filters/CspResponseFilter.java b/backend/src/main/java/com/wire/bots/roman/filters/CspResponseFilter.java index a19c319..ef7a2da 100644 --- a/backend/src/main/java/com/wire/bots/roman/filters/CspResponseFilter.java +++ b/backend/src/main/java/com/wire/bots/roman/filters/CspResponseFilter.java @@ -1,8 +1,8 @@ package com.wire.bots.roman.filters; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; public class CspResponseFilter implements ContainerResponseFilter { diff --git a/backend/src/main/java/com/wire/bots/roman/filters/ProxyAuthenticationFilter.java b/backend/src/main/java/com/wire/bots/roman/filters/ProxyAuthenticationFilter.java index 305626e..d6c0b55 100644 --- a/backend/src/main/java/com/wire/bots/roman/filters/ProxyAuthenticationFilter.java +++ b/backend/src/main/java/com/wire/bots/roman/filters/ProxyAuthenticationFilter.java @@ -2,16 +2,16 @@ import com.wire.bots.roman.Tools; import com.wire.lithium.server.monitoring.MDCUtils; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.DynamicFeature; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.FeatureContext; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.Provider; import java.util.Objects; import java.util.UUID; diff --git a/backend/src/main/java/com/wire/bots/roman/filters/ServiceAuthenticationFilter.java b/backend/src/main/java/com/wire/bots/roman/filters/ServiceAuthenticationFilter.java index e40b683..a736540 100644 --- a/backend/src/main/java/com/wire/bots/roman/filters/ServiceAuthenticationFilter.java +++ b/backend/src/main/java/com/wire/bots/roman/filters/ServiceAuthenticationFilter.java @@ -3,15 +3,16 @@ import com.wire.bots.roman.Tools; import com.wire.lithium.server.monitoring.MDCUtils; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.DynamicFeature; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.Cookie; -import javax.ws.rs.core.FeatureContext; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.Cookie; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; import java.util.UUID; import static com.wire.bots.roman.Const.PROVIDER_ID; diff --git a/backend/src/main/java/com/wire/bots/roman/filters/ServiceTokenAuthenticationFilter.java b/backend/src/main/java/com/wire/bots/roman/filters/ServiceTokenAuthenticationFilter.java index 4c14b00..4e786e8 100644 --- a/backend/src/main/java/com/wire/bots/roman/filters/ServiceTokenAuthenticationFilter.java +++ b/backend/src/main/java/com/wire/bots/roman/filters/ServiceTokenAuthenticationFilter.java @@ -4,15 +4,15 @@ import com.wire.bots.roman.Tools; import com.wire.lithium.server.monitoring.MDCUtils; import com.wire.xenon.tools.Logger; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.DynamicFeature; +import jakarta.ws.rs.container.ResourceInfo; +import jakarta.ws.rs.core.FeatureContext; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.DynamicFeature; -import javax.ws.rs.container.ResourceInfo; -import javax.ws.rs.core.FeatureContext; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.Provider; import java.util.UUID; @Provider diff --git a/backend/src/main/java/com/wire/bots/roman/model/Config.java b/backend/src/main/java/com/wire/bots/roman/model/Config.java index 19a5ee5..bb37ae9 100644 --- a/backend/src/main/java/com/wire/bots/roman/model/Config.java +++ b/backend/src/main/java/com/wire/bots/roman/model/Config.java @@ -21,14 +21,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.wire.lithium.Configuration; -import io.dropwizard.bundles.assets.AssetsBundleConfiguration; -import io.dropwizard.bundles.assets.AssetsConfiguration; import io.dropwizard.validation.ValidationMethod; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; -public class Config extends Configuration implements AssetsBundleConfiguration { +public class Config extends Configuration { @NotNull @JsonProperty public String key; @@ -45,14 +43,6 @@ public class Config extends Configuration implements AssetsBundleConfiguration { @JsonProperty public String romanPubKeyBase64; - @JsonProperty - public AssetsConfiguration assets; - - @Override - public AssetsConfiguration getAssetsConfiguration() { - return assets; - } - @ValidationMethod(message = "`romanPubKeyBase64` is not in a valid base64 format") @JsonIgnore public boolean pubKeyFormatIsNotValid() { diff --git a/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java b/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java index 2b16f1e..9ec98c8 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/BroadcastResource.java @@ -14,15 +14,15 @@ import com.wire.xenon.exceptions.MissingStateException; import com.wire.xenon.tools.Logger; import io.swagger.annotations.*; +import jakarta.ws.rs.*; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.jdbi.v3.core.Jdbi; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutorService; diff --git a/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java b/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java index 521031f..83c1398 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/ConversationResource.java @@ -11,17 +11,17 @@ import com.wire.xenon.exceptions.MissingStateException; import com.wire.xenon.tools.Logger; import io.swagger.annotations.*; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.util.UUID; import java.util.logging.Level; @@ -104,7 +104,7 @@ private void trace(IncomingMessage message) { try { if (Logger.getLevel() == Level.FINE) { ObjectMapper mapper = new ObjectMapper(); - Logger.debug(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(message)); + Logger.info(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(message)); } } catch (Exception ignore) { diff --git a/backend/src/main/java/com/wire/bots/roman/resources/MessagesResource.java b/backend/src/main/java/com/wire/bots/roman/resources/MessagesResource.java index 62da6ed..e89d2b8 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/MessagesResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/MessagesResource.java @@ -5,15 +5,15 @@ import com.wire.bots.roman.model.OutgoingMessage; import com.wire.xenon.tools.Logger; import io.swagger.annotations.*; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import javax.ws.rs.Consumes; -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("/messages") diff --git a/backend/src/main/java/com/wire/bots/roman/resources/Picture.java b/backend/src/main/java/com/wire/bots/roman/resources/Picture.java new file mode 100644 index 0000000..8348f86 --- /dev/null +++ b/backend/src/main/java/com/wire/bots/roman/resources/Picture.java @@ -0,0 +1,42 @@ +package com.wire.bots.roman.resources; + +import com.wire.xenon.assets.ImagePreview; + +import java.util.UUID; + +public class Picture { + private final String mimeType; + private final byte[] imageData; + private int width; + private int height; + + public Picture(byte[] image, String mimeType) + { + this.imageData = image; + this.mimeType = mimeType; + } + + public byte[] getImageData() { + return imageData; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } + + public String getMimeType() { + return mimeType; + } +} diff --git a/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java b/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java index 1bd2193..3f4ef4c 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/ProviderResource.java @@ -12,16 +12,16 @@ import com.wire.xenon.tools.Logger; import io.dropwizard.validation.ValidationMethod; import io.swagger.annotations.*; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; import org.hibernate.validator.constraints.Length; import org.jdbi.v3.core.Jdbi; 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 jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import static com.wire.bots.roman.Tools.generateToken; diff --git a/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java b/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java index d3d0dbc..ad8d50c 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/ServiceResource.java @@ -10,22 +10,21 @@ import com.wire.bots.roman.model.Config; import com.wire.bots.roman.model.Provider; import com.wire.bots.roman.model.Service; -import com.wire.xenon.assets.Picture; import com.wire.xenon.backend.models.ErrorMessage; import com.wire.xenon.tools.Logger; import io.dropwizard.validation.ValidationMethod; import io.swagger.annotations.*; +import jakarta.ws.rs.*; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; import org.hibernate.validator.constraints.Length; import org.jdbi.v3.core.Jdbi; import javax.validation.Valid; import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.NewCookie; -import javax.ws.rs.core.Response; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; @@ -68,86 +67,90 @@ public Response create(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String tok Logger.debug("ServiceResource.create: provider: %s, %s", provider.id, provider.email); - Response login = providerClient.login(provider.email, provider.password); - - Logger.debug("ServiceResource.create: login status: %d", login.getStatus()); - - if (login.getStatus() >= 400) { - String msg = login.readEntity(String.class); - Logger.debug("ServiceResource.create: login response: %s", msg); - return Response. - ok(msg). - status(login.getStatus()). - build(); - } - - NewCookie cookie = login.getCookies().get(Z_PROVIDER); - Service service = newService(); service.name = payload.name; service.summary = payload.summary; service.description = payload.description; - if (payload.avatar != null) { - byte[] image = Base64.getDecoder().decode(payload.avatar); - if (image != null) { - Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image)); - mediumImage.setPublic(true); - String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mediumImage.getMimeType()); - service.assets.get(0).key = key; - service.assets.get(1).key = key; + NewCookie cookie; + + try (Response login = providerClient.login(provider.email, provider.password)) { + + Logger.debug("ServiceResource.create: login status: %d", login.getStatus()); + + if (login.getStatus() >= 400) { + String msg = login.readEntity(String.class); + Logger.debug("ServiceResource.create: login response: %s", msg); + return Response. + ok(msg). + status(login.getStatus()). + build(); + } + + cookie = login.getCookies().get(Z_PROVIDER); + + if (payload.avatar != null) { + byte[] image = Base64.getDecoder().decode(payload.avatar); + if (image != null) { + String mimeType = "image/png"; + Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image, mimeType)); + String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mimeType); + service.assets.get(0).key = key; + service.assets.get(1).key = key; + } } } - Response create = providerClient.createService(cookie, service); + try (Response create = providerClient.createService(cookie, service)) { + Logger.debug("ServiceResource.create: create service status: %d", create.getStatus()); - Logger.debug("ServiceResource.create: create service status: %d", create.getStatus()); + if (create.getStatus() >= 400) { + String msg = create.readEntity(String.class); + Logger.debug("ServiceResource.create: create service response: %s", msg); + return Response. + ok(msg). + status(create.getStatus()). + build(); + } - if (create.getStatus() >= 400) { - String msg = create.readEntity(String.class); - Logger.debug("ServiceResource.create: create service response: %s", msg); - return Response. - ok(msg). - status(create.getStatus()). - build(); + service = create.readEntity(Service.class); } - service = create.readEntity(Service.class); + try (Response update = providerClient.enableService(cookie, service.id, provider.password)) { - Response update = providerClient.enableService(cookie, service.id, provider.password); + Logger.debug("ServiceResource.create: enable service status: %d", update.getStatus()); - Logger.debug("ServiceResource.create: enable service status: %d", update.getStatus()); - - if (update.getStatus() >= 400) { - String msg = update.readEntity(String.class); - Logger.debug("ServiceResource.create: enable service response: %s", msg); - return Response. - ok(msg). - status(update.getStatus()). - build(); - } + if (update.getStatus() >= 400) { + String msg = update.readEntity(String.class); + Logger.debug("ServiceResource.create: enable service response: %s", msg); + return Response. + ok(msg). + status(update.getStatus()). + build(); + } - providersDAO.update(providerId, payload.url, service.auth, service.id, payload.name, payload.commandPrefix); + providersDAO.update(providerId, payload.url, service.auth, service.id, payload.name, payload.commandPrefix); - provider = providersDAO.get(providerId); + provider = providersDAO.get(providerId); - _ServiceInformation result = new _ServiceInformation(); - result.auth = provider.serviceAuth; - result.key = token; - result.code = String.format("%s:%s", providerId, provider.serviceId); - result.url = provider.serviceUrl; - result.service = provider.serviceName; - result.company = provider.name; - result.commandPrefix = provider.commandPrefix; + _ServiceInformation result = new _ServiceInformation(); + result.auth = provider.serviceAuth; + result.key = token; + result.code = String.format("%s:%s", providerId, provider.serviceId); + result.url = provider.serviceUrl; + result.service = provider.serviceName; + result.company = provider.name; + result.commandPrefix = provider.commandPrefix; - Logger.info("ServiceResource.create: service authentication %s, code: %s", result.auth, result.code); + Logger.info("ServiceResource.create: service authentication %s, code: %s", result.auth, result.code); - return Response. - ok(result). - status(update.getStatus()). - build(); + return Response. + ok(result). + status(update.getStatus()). + build(); + } } catch (Exception e) { - Logger.exception("ServiceResource.create: %s", e, e.getMessage()); + Logger.exception(e, "ServiceResource.create: %s", e.getMessage()); return Response .ok(new ErrorMessage("Something went wrong")) .status(500) @@ -205,8 +208,9 @@ public Response update(@Context ContainerRequestContext context, if (payload.avatar != null) { byte[] image = Base64.getDecoder().decode(payload.avatar); - Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image)); - String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mediumImage.getMimeType()); + String mimeType = "image/jpeg"; + Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image, mimeType)); + String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mimeType); providerClient.updateServiceAvatar(cookie, provider.serviceId, key); } @@ -225,7 +229,7 @@ public Response update(@Context ContainerRequestContext context, ok(result). build(); } catch (Exception e) { - Logger.exception("ServiceResource.update: %s", e, e.getMessage()); + Logger.exception(e, "ServiceResource.update: %s", e.getMessage()); return Response .ok(new ErrorMessage("Something went wrong")) .status(500) @@ -263,7 +267,7 @@ public Response get(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String token, ok(result). build(); } catch (Exception e) { - Logger.exception("ServiceResource.get: %s", e, e.getMessage()); + Logger.exception(e, "ServiceResource.get: %s", e.getMessage()); return Response .ok(new ErrorMessage("Something went wrong")) .status(500) @@ -287,19 +291,16 @@ public Response delete(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String tok Provider provider = providersDAO.get(providerId); - final int update = providersDAO.deleteService(providerId); + providersDAO.deleteService(providerId); - Response login = providerClient.login(provider.email, provider.password); - - NewCookie cookie = login.getCookies().get(Z_PROVIDER); + try (Response login = providerClient.login(provider.email, provider.password)) { - final Response response = providerClient.deleteService(cookie, provider.serviceId); + NewCookie cookie = login.getCookies().get(Z_PROVIDER); - return Response. - ok(). - build(); + return providerClient.deleteService(cookie, provider.serviceId); + } } catch (Exception e) { - Logger.exception("ServiceResource.delete: %s", e, e.getMessage()); + Logger.exception(e, "ServiceResource.delete: %s", e.getMessage()); return Response .ok(new ErrorMessage("Something went wrong")) .status(500) diff --git a/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java b/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java index ca986d2..3431e41 100644 --- a/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java +++ b/backend/src/main/java/com/wire/bots/roman/resources/UsersResource.java @@ -10,14 +10,14 @@ import com.wire.xenon.tools.Logger; import io.swagger.annotations.*; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import java.util.UUID; @Api