Skip to content

Commit

Permalink
Merge pull request #46 from wireapp/staging
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
dkovacevic authored Mar 2, 2021
2 parents e1ef393 + 6ddf4cd commit ca98ae2
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 54 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ Uses [lithium](https://github.com/wireapp/lithium) to utilize Wire Bot API

```
{
"name": "My Cool Bot",
"url": "https://my.server.com/webhook", // Pass _null_ if you prefere websockets
"avatar": "..." // Base64 encoded image
"name": "My Cool Bot",
"summary": "Short summary of this cool bot" // Optional
"url": "https://my.server.com/webhook", // Optional: Leave as null if you prefere websockets
"avatar": "..." // Optional: Base64 encoded image
}
```

Expand Down Expand Up @@ -69,7 +70,7 @@ wss://proxy.services.wire.com/await/`<app_key>`

### Events that are sent as HTTP `POST` to your endpoint (Webhook or Websocket)

- `bot_request`: When bot is added to a conversation ( 1-1 conversation or a group)
- `conversation.bot_request`: When bot is added to a conversation ( 1-1 conversation or a group)
```
{
"type": "conversation.bot_request",
Expand All @@ -85,7 +86,7 @@ wss://proxy.services.wire.com/await/`<app_key>`
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.

- `init`: If your Service responded with `200` to a `bot_request` another event is sent: `init`.
- `conversation.init`: If your Service responded with `200` to a `bot_request` another event is sent: `init`.
`text` field contains the name of the conversation your bot is being added to.
```
{
Expand All @@ -98,7 +99,7 @@ Your service must be available at the moment `bot_request` event is sent. It mus
}
```

- `new_text`: When text is posted in a conversation where this bot is present
- `conversation.new_text`: When text is posted in a conversation where this bot is present
```
{
"type": "conversation.new_text",
Expand All @@ -111,7 +112,7 @@ Your service must be available at the moment `bot_request` event is sent. It mus
"text": "Hi everybody!"
}
```
- `new_image`: When an image is posted in a conversation where this bot is present
- `conversation.new_image`: When an image is posted in a conversation where this bot is present

```
{
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<artifactId>roman</artifactId>
<groupId>com.wire.bots</groupId>
<version>0.2.0</version>
<version>0.2.1</version>

<repositories>
<repository>
Expand Down
2 changes: 2 additions & 0 deletions roman.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ key: ${APP_KEY:-}
domain: ${PROXY_DOMAIN:-https://proxy.services.wire.com}
apiHost: ${WIRE_API_HOST:-https://prod-nginz-https.wire.com}

# TLS public key in base64 format
romanPubKeyBase64: ${ROMAN_PUB_KEY_BASE64:-}
8 changes: 8 additions & 0 deletions src/main/java/com/wire/bots/roman/DAO/ProvidersDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ int update(@Bind("id") UUID id,
@Bind("serviceId") UUID serviceId,
@Bind("serviceName") String serviceName);

@SqlUpdate("UPDATE Providers SET " +
"url = null," +
"service_auth = null, " +
"service = null, " +
"service_name = null " +
"WHERE id = :id")
int deleteService(@Bind("id") UUID providerId);

@SqlUpdate("UPDATE Providers SET url = :url WHERE id = :id")
int updateUrl(@Bind("id") UUID id,
@Bind("url") String url);
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/com/wire/bots/roman/ProviderClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ public Response createService(NewCookie zprovider, Service service) {
.post(Entity.entity(service, MediaType.APPLICATION_JSON));
}

public Response deleteService(NewCookie zprovider, UUID serviceId) {
return servicesTarget
.path(serviceId.toString())
.request(MediaType.APPLICATION_JSON)
.cookie(zprovider)
.delete();
}

public Response enableService(NewCookie zprovider, UUID serviceId, String password) {
_UpdateService updateService = new _UpdateService();
updateService.enabled = true;
Expand Down
29 changes: 4 additions & 25 deletions src/main/java/com/wire/bots/roman/Tools.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@

import io.jsonwebtoken.Jwts;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.URI;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
Expand Down Expand Up @@ -41,23 +35,8 @@ static String generateToken(UUID botId, long exp) {
.compact();
}

public static String getPubkey(String hostname) throws IOException {
String str = null;
String raw_hostname = URI.create(hostname).getHost();
PublicKey publicKey = getPublicKey(raw_hostname);
if (publicKey != null)
str = Base64.getEncoder().encodeToString(publicKey.getEncoded());
final String start = "-----BEGIN PUBLIC KEY-----";
final String end = "-----END PUBLIC KEY-----";
return String.format("%s\n%s\n%s", start, str, end);
}

private static PublicKey getPublicKey(String hostname) throws IOException {
SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket(hostname, 443);
socket.startHandshake();
Certificate[] certs = socket.getSession().getPeerCertificates();
Certificate cert = certs[0];
return cert.getPublicKey();
public static String decodeBase64(final String base64String) {
byte[] keyBytes = Base64.getDecoder().decode(base64String);
return new String(keyBytes, StandardCharsets.UTF_8);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wire.bots.roman.commands;

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.wire.bots.roman.Application;
import com.wire.bots.roman.DAO.ProvidersDAO;
import com.wire.bots.roman.ProviderClient;
import com.wire.bots.roman.Tools;
Expand All @@ -18,7 +19,6 @@
import javax.ws.rs.client.Client;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.List;

public class UpdateCertCommand extends ConfiguredCommand<Config> {
Expand All @@ -38,7 +38,7 @@ public void configure(Subparser subparser) {
}

@Override
public void run(Bootstrap<Config> bootstrap, Namespace namespace, Config config) throws IOException {
public void run(Bootstrap<Config> bootstrap, Namespace namespace, Config config) {
Environment environment = new Environment("UpdateCertCommand");

Client client = new JerseyClientBuilder(environment)
Expand All @@ -55,7 +55,7 @@ public void run(Bootstrap<Config> bootstrap, Namespace namespace, Config config)

String hostname = namespace.getString("domain");

String pubkey = Tools.getPubkey(hostname);
String pubkey = Tools.decodeBase64(Application.getInstance().getConfig().romanPubKeyBase64);

System.out.printf("\nCert:\n%s\n\n", pubkey);

Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/wire/bots/roman/model/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@

package com.wire.bots.roman.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.wire.lithium.Configuration;
import io.dropwizard.validation.ValidationMethod;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

public class Config extends Configuration {
Expand All @@ -31,4 +34,17 @@ public class Config extends Configuration {
@NotNull
@JsonProperty
public String domain;

@NotNull
@NotEmpty
@JsonProperty
public String romanPubKeyBase64;

@ValidationMethod(message = "`romanPubKeyBase64` is not in a valid base64 format")
@JsonIgnore
public boolean pubKeyFormatIsNotValid() {
return romanPubKeyBase64 != null
&& !romanPubKeyBase64.isEmpty()
&& romanPubKeyBase64.matches("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public ProviderResource(Jdbi jdbi, ProviderClient providerClient) {
public Response register(@ApiParam @Valid _NewUser payload) {
try {
String name = payload.name;
String email = payload.email;
String email = payload.email.toLowerCase();

Response register = providerClient.register(name, email);

Expand Down Expand Up @@ -83,7 +83,8 @@ public Response register(@ApiParam @Valid _NewUser payload) {
@ApiOperation(value = "Login as Wire Bot Developer")
public Response login(@ApiParam @Valid SignIn payload) {
try {
Provider provider = providersDAO.get(payload.email);
final String email = payload.email.toLowerCase();
Provider provider = providersDAO.get(email);
if (provider == null || !SCryptUtil.check(payload.password, provider.hash)) {
return Response
.ok(new ErrorMessage("Wrong email or password"))
Expand Down
67 changes: 52 additions & 15 deletions src/main/java/com/wire/bots/roman/resources/ServiceResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.wire.bots.roman.*;
import com.wire.bots.roman.DAO.ProvidersDAO;
import com.wire.bots.roman.filters.ServiceAuthorization;
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;
Expand All @@ -27,12 +28,12 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Objects;
import java.util.UUID;

import static com.wire.bots.roman.Const.Z_PROVIDER;
Expand Down Expand Up @@ -84,10 +85,12 @@ public Response create(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String tok

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());
service.assets.get(0).key = key;
service.assets.get(1).key = key;
if (image != null) {
Picture mediumImage = ImageProcessor.getMediumImage(new Picture(image));
String key = providerClient.uploadProfilePicture(cookie, mediumImage.getImageData(), mediumImage.getMimeType());
service.assets.get(0).key = key;
service.assets.get(1).key = key;
}
}

Response create = providerClient.createService(cookie, service);
Expand Down Expand Up @@ -159,7 +162,8 @@ public Response update(@Context ContainerRequestContext context,
}

if (payload.url != null) {
providersDAO.updateUrl(provider.id, payload.url);
String url = payload.url.equals("null") ? null : payload.url;
providersDAO.updateUrl(provider.id, url);
}

Response login = providerClient.login(provider.email, provider.password);
Expand Down Expand Up @@ -243,12 +247,45 @@ public Response get(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String token,
}
}

private Service newService() throws IOException {
final String domain = Application.getInstance().getConfig().domain;
@DELETE
@ApiOperation(value = "Delete the Service", response = _Result.class)
@ServiceAuthorization
public Response delete(@ApiParam(hidden = true) @CookieParam(Z_ROMAN) String token,
@Context ContainerRequestContext context) {
try {
UUID providerId = (UUID) context.getProperty(Const.PROVIDER_ID);

Logger.debug("ServiceResource.delete: provider: %s", providerId);

Provider provider = providersDAO.get(providerId);

final int update = providersDAO.deleteService(providerId);

Response login = providerClient.login(provider.email, provider.password);

NewCookie cookie = login.getCookies().get(Z_PROVIDER);

final Response response = providerClient.deleteService(cookie, provider.serviceId);

return Response.
ok().
build();

} catch (Exception e) {
e.printStackTrace();
Logger.error("ServiceResource.delete: %s", e);
return Response
.ok(new ErrorMessage("Something went wrong"))
.status(500)
.build();
}
}

private Service newService() {
final Config config = Application.getInstance().getConfig();
Service ret = new Service();
ret.baseUrl = domain;
ret.pubkey = Tools.getPubkey(domain);
ret.baseUrl = config.domain;
ret.pubkey = Tools.decodeBase64(config.romanPubKeyBase64);

ret.assets = new ArrayList<>();
Service._Asset asset1 = new Service._Asset();
Expand Down Expand Up @@ -278,9 +315,8 @@ static class _NewService {
public String avatar;

@JsonProperty
@NotNull
@Length(min = 3, max = 128)
public String summary;
public String summary = "Summary";

@ValidationMethod(message = "`url` is not a valid URL")
@JsonIgnore
Expand All @@ -298,7 +334,8 @@ public boolean isUrlValid() {
@ValidationMethod(message = "`avatar` is not a Base64 encoded string")
@JsonIgnore
public boolean isAvatarValid() {
return avatar == null || avatar.matches("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$");
return avatar == null
|| (!avatar.isEmpty() && avatar.matches("^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$"));
}
}

Expand All @@ -317,7 +354,7 @@ static class _UpdateService {
@ValidationMethod(message = "`url` is not a valid URL")
@JsonIgnore
public boolean isUrlValid() {
if (url == null)
if (url == null || Objects.equals(url, "null"))
return true;
try {
new URL(url).toURI();
Expand Down Expand Up @@ -360,4 +397,4 @@ static class _Result {
@JsonProperty
public String service;
}
}
}
13 changes: 12 additions & 1 deletion src/test/java/com/wire/bots/roman/integrations/DatabaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
public class DatabaseTest {
private static final DropwizardTestSupport<Config> SUPPORT = new DropwizardTestSupport<>(
Application.class, "roman.yaml",
ConfigOverride.config("key", "TcZA2Kq4GaOcIbQuOvasrw34321cZAfLW4Ga54fsds43hUuOdcdm42"));
ConfigOverride.config("key", "TcZA2Kq4GaOcIbQuOvasrw34321cZAfLW4Ga54fsds43hUuOdcdm42"),
ConfigOverride.config("romanPubKeyBase64", "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3xtHqyZPlb0lxlnP0rNA\n" +
"JVmAjB1Tenl11brkkKihcJNRAYrnrT/6sPX4u2lVn/aPncUTjN8omL47MBct7qYV\n" +
"1VY4a5beOyNiVL0ZjZMuh07aL9Z2A4cu67tKZrCoGttn3jpSVlqoOtwEgW+Tpgpm\n" +
"KojcRC4DDXEZTEvRoi0RLzAyWCH/8hwWzXR7J082zmn0Ur211QVbOJN/62PAIWyj\n" +
"l5bLglp00AY5OnBHgRNwwRkBJIJLwgNm8u9+0ZplqmMGd3C/QFNngCOeRvFe+5g4\n" +
"qfO4/FOlbkM2kYFAi5KUowfG7cdMQELI+fe4v7yNsgrbMKhnIiLtDIU4wiQIRjbr\n" +
"ZwIDAQAB"));
private Jdbi jdbi;

@Before
Expand Down Expand Up @@ -91,6 +98,10 @@ public void testProviderDAO() {
provider = providersDAO.get(providerId);
assert provider != null;
assert provider.serviceName.equals(newName);

final int deleteService = providersDAO.deleteService(providerId);
provider = providersDAO.get(providerId);

}

@Test
Expand Down

0 comments on commit ca98ae2

Please sign in to comment.