Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redis installation and oauth state services #1432

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions bolt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

<properties>
<commons-text.version>1.13.0</commons-text.version>
<jedis.version>5.2.0</jedis.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -45,6 +46,12 @@
<version>${aws.s3.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
import java.util.Map;
import java.util.Optional;

/**
* InstallationService implementation using Amazon S3.
*
* @see <a href="https://aws.amazon.com/s3/">Amazon S3</a>
*/
@Slf4j
public class AmazonS3InstallationService implements InstallationService {

Expand Down Expand Up @@ -92,16 +97,16 @@ public void setHistoricalDataEnabled(boolean isHistoricalDataEnabled) {
}

@Override
public void saveInstallerAndBot(Installer i) throws Exception {
public void saveInstallerAndBot(Installer installer) throws Exception {
try (S3Client s3 = this.createS3Client()) {
if (isHistoricalDataEnabled()) {
save(s3, getInstallerKey(i) + "-latest", JsonOps.toJsonString(i), "AWS S3 putObject result of Installer data - {}, {}");
save(s3, getBotKey(i) + "-latest", JsonOps.toJsonString(i.toBot()), "AWS S3 putObject result of Bot data - {}, {}");
save(s3, getInstallerKey(i) + "-" + i.getInstalledAt(), JsonOps.toJsonString(i), "AWS S3 putObject result of Installer data - {}, {}");
save(s3, getBotKey(i) + "-" + i.getInstalledAt(), JsonOps.toJsonString(i.toBot()), "AWS S3 putObject result of Bot data - {}, {}");
save(s3, getInstallerKey(installer) + "-latest", JsonOps.toJsonString(installer), "AWS S3 putObject result of Installer data - {}, {}");
save(s3, getBotKey(installer) + "-latest", JsonOps.toJsonString(installer.toBot()), "AWS S3 putObject result of Bot data - {}, {}");
save(s3, getInstallerKey(installer) + "-" + installer.getInstalledAt(), JsonOps.toJsonString(installer), "AWS S3 putObject result of Installer data - {}, {}");
save(s3, getBotKey(installer) + "-" + installer.getInstalledAt(), JsonOps.toJsonString(installer.toBot()), "AWS S3 putObject result of Bot data - {}, {}");
} else {
save(s3, getInstallerKey(i), JsonOps.toJsonString(i), "AWS S3 putObject result of Installer data - {}, {}");
save(s3, getBotKey(i), JsonOps.toJsonString(i.toBot()), "AWS S3 putObject result of Bot data - {}, {}");
save(s3, getInstallerKey(installer), JsonOps.toJsonString(installer), "AWS S3 putObject result of Installer data - {}, {}");
save(s3, getBotKey(installer), JsonOps.toJsonString(installer.toBot()), "AWS S3 putObject result of Bot data - {}, {}");
}
}
}
Expand Down Expand Up @@ -332,8 +337,10 @@ protected S3Client createS3Client() {
.build();
}

private String getInstallerKey(Installer i) {
return getInstallerKey(i.getEnterpriseId(), i.getTeamId(), i.getInstallerUserId());
private String getInstallerKey(Installer installer) {
return getInstallerKey(installer.getEnterpriseId(),
installer.getTeamId(),
installer.getInstallerUserId());
}

private String getInstallerKey(String enterpriseId, String teamId, String userId) {
Expand All @@ -345,8 +352,9 @@ private String getInstallerKey(String enterpriseId, String teamId, String userId
+ userId;
}

private String getBotKey(Installer i) {
return getBotKey(i.getEnterpriseId(), i.getTeamId());
private String getBotKey(Installer installer) {
return getBotKey(installer.getEnterpriseId(),
installer.getTeamId());
}

private String getBotKey(String enterpriseId, String teamId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ public boolean isAvailableInDatabase(String state) {

@Override
public void deleteStateFromDatastore(String state) throws Exception {
DeleteObjectResponse deleteObjectResponse;
try (S3Client s3 = this.createS3Client()) {
s3.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(getKey(state)).build());
deleteObjectResponse = s3.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(getKey(state)).build());
}
if (log.isDebugEnabled()) {
log.debug("AWS S3 deleteObject result of state data - {}", deleteObjectResponse.toString());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

import static java.util.stream.Collectors.joining;

/**
* InstallationService implementation using local file system.
*/
@Slf4j
public class FileInstallationService implements InstallationService {

Expand Down Expand Up @@ -132,10 +135,12 @@ public Installer findInstaller(String enterpriseId, String teamId, String userId
if (json == null && enterpriseId != null) {
json = loadFileContent(getInstallerPath(null, teamId, userId));
if (json != null) {
Installer i = JsonOps.fromJson(json, DefaultInstaller.class);
i.setEnterpriseId(enterpriseId);
save(getInstallerPath(enterpriseId, teamId, userId), i.getInstalledAt(), JsonOps.toJsonString(i));
return i;
Installer installer = JsonOps.fromJson(json, DefaultInstaller.class);
installer.setEnterpriseId(enterpriseId);
save(getInstallerPath(enterpriseId, teamId, userId),
installer.getInstalledAt(),
JsonOps.toJsonString(installer));
return installer;
}
}
if (json != null) {
Expand Down Expand Up @@ -165,7 +170,7 @@ private static void deleteAllFilesMatchingPrefix(String keyPrefix, String dir) {
try {
Files.delete(path);
} catch (IOException e) {
log.error("Failed to delete a file: {}", path.toString(), e);
log.error("Failed to delete a file: {}", path, e);
}
}
});
Expand All @@ -174,8 +179,10 @@ private static void deleteAllFilesMatchingPrefix(String keyPrefix, String dir) {
}
}

private String getInstallerPath(Installer i) throws IOException {
return getInstallerPath(i.getEnterpriseId(), i.getTeamId(), i.getInstallerUserId());
private String getInstallerPath(Installer installer) throws IOException {
return getInstallerPath(installer.getEnterpriseId(),
installer.getTeamId(),
installer.getInstallerUserId());
}

private String getInstallerPath(String enterpriseId, String teamId, String userId) throws IOException {
Expand Down Expand Up @@ -227,7 +234,7 @@ private String loadFileContent(String filepath) throws IOException {
String content = Files.readAllLines(Paths.get(filepath))
.stream()
.collect(joining());
if (content == null || content.trim().isEmpty() || content.trim().equals("null")) {
if (content.trim().isEmpty() || content.trim().equals("null")) {
return null;
}
return content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ private Long findExpirationMillisFor(String state) {
return Long.valueOf(value);
} catch (IOException e) {
return null;
} catch (NumberFormatException e) {
return null;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package com.slack.api.bolt.service.builtin;

import com.slack.api.bolt.Initializer;
import com.slack.api.bolt.model.Bot;
import com.slack.api.bolt.model.Installer;
import com.slack.api.bolt.model.builtin.DefaultBot;
import com.slack.api.bolt.model.builtin.DefaultInstaller;
import com.slack.api.bolt.service.InstallationService;
import com.slack.api.bolt.util.JsonOps;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol.ResponseKeyword;

/**
* InstallationService implementation using Redis (or ValKey)
*
* @see <a href="https://aws.amazon.com/elasticache/">Amazon ElastiCache</a>
*/
@Slf4j
public class RedisInstallationService implements InstallationService {

private final JedisPool jedisPool;
private boolean historicalDataEnabled;

public RedisInstallationService(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}

@Override
public Initializer initializer() {
return (app) -> {
try (Jedis jedis = jedisPool.getResource()) {
String pong = jedis.ping();
if (!pong.equals(ResponseKeyword.PONG.name())) {
throw new IllegalStateException("Failed to ping redis");
}

log.debug("successfully initialized connection to redis");
} catch (Exception e) {
throw new IllegalStateException("Failed to access redis", e);
}
};
}

@Override
public boolean isHistoricalDataEnabled() {
return historicalDataEnabled;
}

@Override
public void setHistoricalDataEnabled(boolean isHistoricalDataEnabled) {
this.historicalDataEnabled = isHistoricalDataEnabled;
}

@Override
public void saveInstallerAndBot(Installer installer) throws Exception {
save(getInstallerPath(installer),
installer.getInstalledAt(),
JsonOps.toJsonString(installer));
save(getBotPath(installer.getEnterpriseId(), installer.getTeamId()),
installer.getInstalledAt(),
JsonOps.toJsonString(installer.toBot()));
}

@Override
public void saveBot(Bot bot) {
save(getBotPath(bot.getEnterpriseId(), bot.getTeamId()), bot.getInstalledAt(), JsonOps.toJsonString(bot));
}

@Override
public void deleteBot(Bot bot) throws Exception {
try (Jedis jedis = jedis()) {
jedis.del(getBotPath(bot.getEnterpriseId(), bot.getTeamId()));
}
}

@Override
public void deleteInstaller(Installer installer) throws Exception {
try (Jedis jedis = jedis()) {
jedis.del(getInstallerPath(installer));
}
}

@Override
public Bot findBot(String enterpriseId, String teamId) {
String json;
if (enterpriseId != null) {
// try finding org-level bot token first
json = loadRedisContent(getBotPath(enterpriseId, null));
if (json != null) {
return JsonOps.fromJson(json, DefaultBot.class);
}
// not found - going to find workspace level installation
}
json = loadRedisContent(getBotPath(enterpriseId, teamId));
if (json == null && enterpriseId != null) {
json = loadRedisContent(getBotPath(null, teamId));
if (json != null) {
Bot bot = JsonOps.fromJson(json, DefaultBot.class);
bot.setEnterpriseId(enterpriseId);
save(getBotPath(enterpriseId, teamId), bot.getInstalledAt(), JsonOps.toJsonString(bot));
return bot;
}
}
if (json == null) {
return null;
}
return JsonOps.fromJson(json, DefaultBot.class);
}

@Override
public Installer findInstaller(String enterpriseId, String teamId, String userId) {
String json;
if (enterpriseId != null) {
// try finding org-level user token first
json = loadRedisContent(getInstallerPath(enterpriseId, null, userId));
if (json != null) {
return JsonOps.fromJson(json, DefaultInstaller.class);
}
// not found - going to find workspace level installation
}
json = loadRedisContent(getInstallerPath(enterpriseId, teamId, userId));
if (json == null && enterpriseId != null) {
json = loadRedisContent(getInstallerPath(null, teamId, userId));
if (json != null) {
Installer installer = JsonOps.fromJson(json, DefaultInstaller.class);
installer.setEnterpriseId(enterpriseId);
save(getInstallerPath(enterpriseId, teamId, userId),
installer.getInstalledAt(),
JsonOps.toJsonString(installer));
return installer;
}
}
if (json == null) {
return null;
}
return JsonOps.fromJson(json, DefaultInstaller.class);
}

@Override
public void deleteAll(String enterpriseId, String teamId) {
String keyPrefix = Optional.ofNullable(enterpriseId).orElse("none")
+ "-"
+ Optional.ofNullable(teamId).orElse("none");
try (Jedis jedis = jedis()) {
jedis.keys(keyPrefix).forEach(jedis::del);
}
}

private String getInstallerPath(Installer installer) {
return getInstallerPath(installer.getEnterpriseId(),
installer.getTeamId(),
installer.getInstallerUserId());
}

private String getInstallerPath(String enterpriseId, String teamId, String userId) {
String key = Optional.ofNullable(enterpriseId).orElse("none")
+ "-"
+ Optional.ofNullable(teamId).orElse("none")
+ "-"
+ userId;
if (isHistoricalDataEnabled()) {
key = key + "-latest";
}
return "installer/" + key;
}

private String getBotPath(String enterpriseId, String teamId) {
String key = Optional.ofNullable(enterpriseId).orElse("none")
+ "-"
+ Optional.ofNullable(teamId).orElse("none");
if (isHistoricalDataEnabled()) {
key = key + "-latest";
}
return "bot/" + key;
}

private Jedis jedis() {
return jedisPool.getResource();
}

private void save(String path, Long installedAt, String json) {
try (Jedis jedis = jedis()) {
// latest
jedis.set(path, json);

if (isHistoricalDataEnabled()) {
// the historical data
jedis.set(path.replaceFirst("-latest$", "-" + installedAt), json);
}
}
}

private String loadRedisContent(String path) {
try (Jedis jedis = jedis()) {
if (!jedis.exists(path)) {
return null;
}
String content = jedis.get(path);
if (content.trim().isEmpty() || content.trim().equals("null")) {
return null;
}
return content;
}
}

}
Loading