diff --git a/slack-app-backend/pom.xml b/slack-app-backend/pom.xml index c417ef625..45082e8ab 100644 --- a/slack-app-backend/pom.xml +++ b/slack-app-backend/pom.xml @@ -41,13 +41,6 @@ ${slf4j.version} - - javax.servlet - javax.servlet-api - ${javax.servlet-api.version} - provided - - org.eclipse.jetty jetty-servlet diff --git a/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/SlackEventsApiServlet.java b/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/SlackEventsApiServlet.java deleted file mode 100644 index 948a8224a..000000000 --- a/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/SlackEventsApiServlet.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.slack.api.app_backend.events.servlet; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.slack.api.app_backend.SlackSignature; -import com.slack.api.app_backend.events.EventsDispatcher; -import com.slack.api.app_backend.events.EventsDispatcherFactory; -import com.slack.api.util.json.GsonFactory; -import lombok.extern.slf4j.Slf4j; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.Locale; -import java.util.stream.Collectors; - -@Deprecated // Please consider Bolt framework with bolt-servlet adapter instead -@Slf4j -public abstract class SlackEventsApiServlet extends HttpServlet { - - private EventsDispatcher dispatcher = EventsDispatcherFactory.getInstance(); - private SlackSignatureVerifier signatureVerifier; - - protected abstract void setupDispatcher(EventsDispatcher dispatcher); - - public void init() throws ServletException { - super.init(); - setupDispatcher(dispatcher); - dispatcher.start(); - signatureVerifier = new SlackSignatureVerifier(new SlackSignature.Generator(getSlackSigningSecret())); - } - - public void destroy() { - super.destroy(); - dispatcher.stop(); - } - - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - String requestBody = doReadRequestBodyAsString(req); - - // NOTE: It's also possible to do the same in a servlet filter - if (isSignatureVerifierEnabled()) { - boolean validSignature = this.signatureVerifier.isValid(req, requestBody); - if (!validSignature) { // invalid signature - if (log.isDebugEnabled()) { - String signature = req.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE); - log.debug("An invalid X-Slack-Signature detected - {}", signature); - } - resp.setStatus(401); - return; - } - } - - String contentType = req.getHeader("Content-Type"); - if (contentType != null && contentType.toLowerCase(Locale.ENGLISH).trim().startsWith("application/json")) { - JsonObject payload = GsonFactory.createSnakeCase().fromJson(requestBody, JsonElement.class).getAsJsonObject(); - String eventType = payload.get("type").getAsString(); - if (eventType != null && eventType.equals("url_verification")) { - String challenge = payload.get("challenge").getAsString(); - // url_verification: https://api.slack.com/events/url_verification - resp.setStatus(200); - resp.setHeader("Content-Type", "text/plain"); - resp.getOutputStream().write(challenge.getBytes(Charset.forName("UTF-8"))); - } else { - dispatcher.enqueue(requestBody); - resp.setStatus(200); - } - } else { - log.warn("Unexpected request detected - Content-Type: {}", req.getHeader("Content-Type")); - resp.setStatus(400); - } - - } - - /** - * Returns the signing secret supposed to be used for verifying requests from Slack. - */ - protected String getSlackSigningSecret() { - return System.getenv(SlackSignature.Secret.DEFAULT_ENV_NAME); - } - - /** - * If you'd like to do the same in a servlet filter, return false instead. - */ - protected boolean isSignatureVerifierEnabled() { - return true; - } - - /** - * Reads the request body and returns the value as a string. - */ - protected String doReadRequestBodyAsString(HttpServletRequest req) throws IOException { - return req.getReader().lines().collect(Collectors.joining(System.lineSeparator())); - } - -} diff --git a/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/SlackSignatureVerifier.java b/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/SlackSignatureVerifier.java deleted file mode 100644 index e17e37c20..000000000 --- a/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/SlackSignatureVerifier.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.slack.api.app_backend.events.servlet; - -import com.slack.api.app_backend.SlackSignature; -import lombok.extern.slf4j.Slf4j; - -import javax.servlet.http.HttpServletRequest; - -@Deprecated // Please consider Bolt framework with bolt-servlet adapter instead -@Slf4j -public class SlackSignatureVerifier { - - private final SlackSignature.Verifier verifier; - - public SlackSignatureVerifier() { - this(new SlackSignature.Generator()); - } - - public SlackSignatureVerifier(SlackSignature.Generator signatureGenerator) { - this.verifier = new SlackSignature.Verifier(signatureGenerator); - } - - public boolean isValid(HttpServletRequest request, String requestBody) { - return isValid(request, requestBody, System.currentTimeMillis()); - } - - public boolean isValid(HttpServletRequest request, String requestBody, long nowInMillis) { - if (request != null && request.getHeaderNames() != null) { - String requestTimestamp = request.getHeader(SlackSignature.HeaderNames.X_SLACK_REQUEST_TIMESTAMP); - String requestSignature = request.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE); - return verifier.isValid(requestTimestamp, requestBody, requestSignature, nowInMillis); - } else { - return false; - } - } - -} diff --git a/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/package-info.java b/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/package-info.java deleted file mode 100644 index cd35ec7ba..000000000 --- a/slack-app-backend/src/main/java/com/slack/api/app_backend/events/servlet/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Using the classes under this package is no longer recommended. - * Please consider going with bolt-servlet for new app development. - */ -package com.slack.api.app_backend.events.servlet; \ No newline at end of file diff --git a/slack-app-backend/src/test/java/test_locally/app_backend/events/EventsApiHandlerTest.java b/slack-app-backend/src/test/java/test_locally/app_backend/events/EventsApiHandlerTest.java deleted file mode 100644 index a37a5f6a7..000000000 --- a/slack-app-backend/src/test/java/test_locally/app_backend/events/EventsApiHandlerTest.java +++ /dev/null @@ -1,209 +0,0 @@ -package test_locally.app_backend.events; - -import com.slack.api.app_backend.events.EventsDispatcher; -import com.slack.api.app_backend.events.handler.AppUninstalledHandler; -import com.slack.api.app_backend.events.handler.GoodbyeHandler; -import com.slack.api.app_backend.events.handler.MessageHandler; -import com.slack.api.app_backend.events.payload.AppUninstalledPayload; -import com.slack.api.app_backend.events.payload.GoodbyePayload; -import com.slack.api.app_backend.events.payload.MessagePayload; -import com.slack.api.app_backend.events.payload.UrlVerificationPayload; -import com.slack.api.app_backend.events.servlet.SlackEventsApiServlet; -import com.slack.api.util.json.GsonFactory; -import org.eclipse.jetty.http.HttpTester; -import org.eclipse.jetty.servlet.ServletTester; -import org.junit.Ignore; -import org.junit.Test; - -import javax.servlet.annotation.WebServlet; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -// TODO: These tests somehow fail on TravisCI builds. -// will come up with better ways to do the similar. -public class EventsApiHandlerTest { - - @WebServlet(urlPatterns = "/") - public static class SampleServlet extends SlackEventsApiServlet { - @Override - protected boolean isSignatureVerifierEnabled() { - return false; - } - - @Override - protected void setupDispatcher(EventsDispatcher dispatcher) { - dispatcher.register(MESSAGE); - dispatcher.register(APP_UNINSTALLED); - dispatcher.register(GOODBYE); - } - } - - // -------------------------------- - // message - - public static AtomicInteger MESSAGE_CALL_COUNTER = new AtomicInteger(0); - - public static MessageHandler MESSAGE = new MessageHandler() { - @Override - public void handle(MessagePayload payload) { - MESSAGE_CALL_COUNTER.incrementAndGet(); - } - }; - - // -------------------------------- - // app_uninstalled - - public static AtomicInteger APP_UNINSTALLED_CALL_COUNTER = new AtomicInteger(0); - - public static AppUninstalledHandler APP_UNINSTALLED = new AppUninstalledHandler() { - @Override - public void handle(AppUninstalledPayload event) { - APP_UNINSTALLED_CALL_COUNTER.incrementAndGet(); - } - }; - - // -------------------------------- - // goodbye - - public static AtomicInteger GOODBYE_CALL_COUNTER = new AtomicInteger(0); - - public static GoodbyeHandler GOODBYE = new GoodbyeHandler() { - @Override - public void handle(GoodbyePayload payload) { - GOODBYE_CALL_COUNTER.incrementAndGet(); - } - }; - - // ------------------------------------------------------------------- - - @Ignore - @Test - public void urlVerification() throws Exception { - ServletTester tester = getServletTester(); - HttpTester.Request request = prepareRequest(); - - UrlVerificationPayload payload = new UrlVerificationPayload(); - payload.setChallenge("cha-xxxxxx"); - payload.setToken("token-xxxx"); - request.setContent(GsonFactory.createSnakeCase().toJson(payload)); - - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - assertThat(response.getContent(), is(equalTo("cha-xxxxxx"))); - assertThat(response.getStatus(), is(equalTo(200))); - assertThat(response.get("Content-Type"), is(equalTo("text/plain"))); - } - - @Ignore - @Test - public void message() throws Exception { - ServletTester tester = getServletTester(); - HttpTester.Request request = prepareRequest(); - - request.setContent("{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"message\",\n" + - " \"channel\": \"C2147483705\",\n" + - " \"user\": \"U2147483697\",\n" + - " \"text\": \"Hello world\",\n" + - " \"ts\": \"1355517523.000005\"\n" + - "} ,\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response1 = HttpTester.parseResponse(tester.getResponses(request.generate())); - HttpTester.Response response2 = HttpTester.parseResponse(tester.getResponses(request.generate())); - HttpTester.Response response3 = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(200L); - - assertThat(response1.getStatus(), is(equalTo(200))); - assertThat(response2.getStatus(), is(equalTo(200))); - assertThat(response3.getStatus(), is(equalTo(200))); - assertThat(MESSAGE_CALL_COUNTER.get(), is(3)); - } - - @Ignore - @Test - public void app_uninstalled() throws Exception { - ServletTester tester = getServletTester(); - HttpTester.Request request = prepareRequest(); - - request.setContent("{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"app_uninstalled\"\n" + - " },\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(200L); - - assertThat(response.getStatus(), is(equalTo(200))); - assertThat(APP_UNINSTALLED_CALL_COUNTER.get(), is(1)); - } - - @Ignore - @Test - public void goodbye() throws Exception { - ServletTester tester = getServletTester(); - HttpTester.Request request = prepareRequest(); - - request.setContent("{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"goodbye\"\n" + - " },\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(200L); - - assertThat(response.getStatus(), is(equalTo(200))); - assertThat(GOODBYE_CALL_COUNTER.get(), is(1)); - } - - // ------------------------------------------------------------------- - - private static ServletTester getServletTester() throws Exception { - ServletTester tester = new ServletTester(); - tester.addServlet(SampleServlet.class, "/"); - tester.start(); - return tester; - } - - private static HttpTester.Request prepareRequest() { - HttpTester.Request request = HttpTester.newRequest(); - request.setMethod("POST"); - request.setHeader("Host", "tester"); // should be "tester" - request.setURI("/"); - request.setVersion("HTTP/1.1"); - request.setHeader("content-type", "application/json"); - request.setHeader("Connection", "close"); - return request; - } - -} diff --git a/slack-app-backend/src/test/java/test_locally/app_backend/events/servlet/EventsApiTest.java b/slack-app-backend/src/test/java/test_locally/app_backend/events/servlet/EventsApiTest.java deleted file mode 100644 index 60fdb45a8..000000000 --- a/slack-app-backend/src/test/java/test_locally/app_backend/events/servlet/EventsApiTest.java +++ /dev/null @@ -1,205 +0,0 @@ -package test_locally.app_backend.events.servlet; - -import com.slack.api.app_backend.events.EventsDispatcher; -import com.slack.api.app_backend.events.handler.AppUninstalledHandler; -import com.slack.api.app_backend.events.handler.GoodbyeHandler; -import com.slack.api.app_backend.events.handler.MessageHandler; -import com.slack.api.app_backend.events.payload.AppUninstalledPayload; -import com.slack.api.app_backend.events.payload.GoodbyePayload; -import com.slack.api.app_backend.events.payload.MessagePayload; -import com.slack.api.app_backend.events.payload.UrlVerificationPayload; -import com.slack.api.app_backend.events.servlet.SlackEventsApiServlet; -import com.slack.api.util.json.GsonFactory; -import org.eclipse.jetty.http.HttpTester; -import org.eclipse.jetty.servlet.ServletTester; -import org.junit.Before; -import org.junit.Test; -import util.ServletTestUtils; - -import javax.servlet.annotation.WebServlet; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; - -public class EventsApiTest { - - private static final String signingSecret = "secret"; - - public static AtomicInteger messageCalls = new AtomicInteger(0); - public static MessageHandler message = new MessageHandler() { - @Override - public void handle(MessagePayload payload) { - messageCalls.incrementAndGet(); - } - }; - - public static AtomicInteger appUninstallsCalls = new AtomicInteger(0); - public static AppUninstalledHandler allUninstall = new AppUninstalledHandler() { - @Override - public void handle(AppUninstalledPayload event) { - appUninstallsCalls.incrementAndGet(); - } - }; - - public static AtomicInteger goodbyeCalls = new AtomicInteger(0); - public static GoodbyeHandler goodbye = new GoodbyeHandler() { - @Override - public void handle(GoodbyePayload payload) { - goodbyeCalls.incrementAndGet(); - } - }; - - @Before - public void setup() { - messageCalls.set(0); - appUninstallsCalls.set(0); - goodbyeCalls.set(0); - } - - SlackWebApp webApp = new SlackWebApp(); - - @WebServlet(urlPatterns = "/") - public static class SlackWebApp extends SlackEventsApiServlet { - - @Override - protected String getSlackSigningSecret() { - return signingSecret; - } - - @Override - protected void setupDispatcher(EventsDispatcher dispatcher) { - assertNotNull(getSlackSigningSecret()); - dispatcher.register(message); - dispatcher.register(allUninstall); - dispatcher.register(goodbye); - } - } - - @Test - public void destroy() { - SlackWebApp app = new SlackWebApp(); - app.destroy(); - } - - @Test - public void urlVerification() throws Exception { - ServletTester tester = ServletTestUtils.getServletTester(webApp); - - UrlVerificationPayload payload = new UrlVerificationPayload(); - payload.setChallenge("cha-xxxxxx"); - payload.setToken("token-xxxx"); - - HttpTester.Request request = ServletTestUtils.prepareRequest(signingSecret, GsonFactory.createSnakeCase().toJson(payload)); - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - assertThat(response.getContent(), is(equalTo("cha-xxxxxx"))); - assertThat(response.getStatus(), is(equalTo(200))); - assertThat(response.get("Content-Type"), is(startsWith("text/plain"))); - } - - @Test - public void message() throws Exception { - ServletTester tester = ServletTestUtils.getServletTester(webApp); - HttpTester.Request request = ServletTestUtils.prepareRequest(signingSecret, "{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"message\",\n" + - " \"channel\": \"C2147483705\",\n" + - " \"user\": \"U2147483697\",\n" + - " \"text\": \"Hello world\",\n" + - " \"ts\": \"1355517523.000005\"\n" + - "} ,\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response1 = HttpTester.parseResponse(tester.getResponses(request.generate())); - HttpTester.Response response2 = HttpTester.parseResponse(tester.getResponses(request.generate())); - HttpTester.Response response3 = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(1000L); - - assertThat(response1.getStatus(), is(equalTo(200))); - assertThat(response2.getStatus(), is(equalTo(200))); - assertThat(response3.getStatus(), is(equalTo(200))); - assertThat(messageCalls.get(), is(3)); - } - - @Test - public void app_uninstalled() throws Exception { - ServletTester tester = ServletTestUtils.getServletTester(webApp); - HttpTester.Request request = ServletTestUtils.prepareRequest(signingSecret, "{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"app_uninstalled\"\n" + - " },\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(1000L); - - assertThat(response.getStatus(), is(equalTo(200))); - assertThat(appUninstallsCalls.get(), is(1)); - } - - @Test - public void goodbye() throws Exception { - ServletTester tester = ServletTestUtils.getServletTester(webApp); - HttpTester.Request request = ServletTestUtils.prepareRequest(signingSecret, "{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"goodbye\"\n" + - " },\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(1000L); - - assertThat(response.getStatus(), is(equalTo(200))); - assertThat(goodbyeCalls.get(), is(1)); - } - - @Test - public void invalid() throws Exception { - ServletTester tester = ServletTestUtils.getServletTester(webApp); - HttpTester.Request request = ServletTestUtils.prepareRequest("invalid", "{\n" + - " \"token\": \"XXYYZZ\",\n" + - " \"team_id\": \"TXXXXXXXX\",\n" + - " \"api_app_id\": \"AXXXXXXXXX\",\n" + - " \"event\": {\n" + - " \"type\": \"goodbye\"\n" + - " },\n" + - " \"type\": \"event_callback\",\n" + - " \"event_id\": \"EvXXXXXXXX\",\n" + - " \"event_time\": 1234567890\n" + - "}"); - - HttpTester.Response response = HttpTester.parseResponse(tester.getResponses(request.generate())); - - // wait for the async execution - Thread.sleep(1000L); - - assertThat(response.getStatus(), is(equalTo(401))); - assertThat(goodbyeCalls.get(), is(0)); - } -} diff --git a/slack-app-backend/src/test/java/test_locally/app_backend/events/servlet/SlackSignatureVerifierTest.java b/slack-app-backend/src/test/java/test_locally/app_backend/events/servlet/SlackSignatureVerifierTest.java deleted file mode 100644 index a6c7b3762..000000000 --- a/slack-app-backend/src/test/java/test_locally/app_backend/events/servlet/SlackSignatureVerifierTest.java +++ /dev/null @@ -1,49 +0,0 @@ -package test_locally.app_backend.events.servlet; - -import com.slack.api.app_backend.SlackSignature; -import com.slack.api.app_backend.events.servlet.SlackSignatureVerifier; -import org.junit.Test; - -import javax.servlet.http.HttpServletRequest; -import java.util.Enumeration; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -// https://api.slack.com/docs/verifying-requests-from-slack -public class SlackSignatureVerifierTest { - - @Test - public void validServletRequest() { - SlackSignature.Generator generator = new SlackSignature.Generator("8f742231b10e8888abcd99yyyzzz85a5"); - Long currentTimeInMilliSeconds = System.currentTimeMillis(); - String requestTimestamp = Long.toString(currentTimeInMilliSeconds / 1000); - String requestBody = "token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c"; - - String validSignature = generator.generate(requestTimestamp, requestBody); - HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); - when(httpServletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class)); - when(httpServletRequest.getHeader(SlackSignature.HeaderNames.X_SLACK_REQUEST_TIMESTAMP)).thenReturn(requestTimestamp); - when(httpServletRequest.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE)).thenReturn(validSignature); - - SlackSignatureVerifier verifier = new SlackSignatureVerifier(generator); - assertThat(verifier.isValid(httpServletRequest, requestBody), is(true)); - } - - @Test - public void inValidServletRequest() { - SlackSignature.Generator generator = new SlackSignature.Generator("8f742231b10e8888abcd99yyyzzz85a5"); - String timestamp = "1531420618"; - String requestBody = "token=xyzz0WbapA4vBCDEFasx0q6G&team_id=T1DC2JH3J&team_domain=testteamnow&channel_id=G8PSS9T3V&channel_name=foobar&user_id=U2CERLKJA&user_name=roadrunner&command=%2Fwebhook-collect&text=&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT1DC2JH3J%2F397700885554%2F96rGlfmibIGlgcZRskXaIFfN&trigger_id=398738663015.47445629121.803a0bc887a14d10d2c447fce8b6703c"; - SlackSignatureVerifier verifier = new SlackSignatureVerifier(generator); - String validSignature = "v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503"; - HttpServletRequest httpServletRequest = mock(HttpServletRequest.class); - when(httpServletRequest.getHeaderNames()).thenReturn(mock(Enumeration.class)); - when(httpServletRequest.getHeader(SlackSignature.HeaderNames.X_SLACK_REQUEST_TIMESTAMP)).thenReturn(timestamp); - when(httpServletRequest.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE)).thenReturn(validSignature); - assertThat(verifier.isValid(httpServletRequest, requestBody), is(false)); - } - -} diff --git a/slack-app-backend/src/test/java/test_with_remote_apis/commands/SlashCommandApiBackend.java b/slack-app-backend/src/test/java/test_with_remote_apis/commands/SlashCommandApiBackend.java deleted file mode 100644 index edeea7bd1..000000000 --- a/slack-app-backend/src/test/java/test_with_remote_apis/commands/SlashCommandApiBackend.java +++ /dev/null @@ -1,70 +0,0 @@ -package test_with_remote_apis.commands; - -import com.slack.api.app_backend.SlackSignature; -import com.slack.api.app_backend.events.servlet.SlackSignatureVerifier; -import com.slack.api.app_backend.slash_commands.SlashCommandPayloadParser; -import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; - -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.stream.Collectors; - -public class SlashCommandApiBackend { - - @Slf4j - @WebServlet - public static class SlackEventsServlet extends HttpServlet { - - // Configure this env variable to run this servlet - private final String slackSigningSecret = System.getenv("SLACK_TEST_SIGNING_SECRET"); - - private final SlackSignature.Generator signatureGenerator = new SlackSignature.Generator(slackSigningSecret); - private final SlackSignatureVerifier signatureVerifier = new SlackSignatureVerifier(signatureGenerator); - private final SlashCommandPayloadParser parser = new SlashCommandPayloadParser(); - - protected String doReadRequestBodyAsString(HttpServletRequest req) throws IOException { - return req.getReader().lines().collect(Collectors.joining(System.lineSeparator())); - } - - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - String requestBody = doReadRequestBodyAsString(req); - log.info("request - {}", requestBody); - boolean validSignature = this.signatureVerifier.isValid(req, requestBody); - if (!validSignature) { // invalid signature - if (log.isDebugEnabled()) { - String signature = req.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE); - log.debug("An invalid X-Slack-Signature detected - {}", signature); - } - resp.setStatus(401); - return; - } - - SlashCommandPayload payload = parser.parse(requestBody); - log.info("payload - {}", payload); - - resp.setStatus(200); - resp.setHeader("Content-Type", "text/plain"); - if (payload.getText() != null) { - resp.getWriter().write(payload.getText()); - } - } - } - - // https://www.eclipse.org/jetty/documentation/current/embedding-jetty.html - - public static void main(String[] args) throws Exception { - Server server = new Server(3000); - ServletHandler handler = new ServletHandler(); - server.setHandler(handler); - handler.addServletWithMapping(SlackEventsServlet.class, "/slack/events"); - server.start(); - server.join(); - } -} - diff --git a/slack-app-backend/src/test/java/test_with_remote_apis/events_subscription/SimpleEventsApiBackend.java b/slack-app-backend/src/test/java/test_with_remote_apis/events_subscription/SimpleEventsApiBackend.java deleted file mode 100644 index b6cafd5c5..000000000 --- a/slack-app-backend/src/test/java/test_with_remote_apis/events_subscription/SimpleEventsApiBackend.java +++ /dev/null @@ -1,40 +0,0 @@ -package test_with_remote_apis.events_subscription; - -import com.slack.api.app_backend.events.EventsDispatcher; -import com.slack.api.app_backend.events.handler.MessageHandler; -import com.slack.api.app_backend.events.payload.MessagePayload; -import com.slack.api.app_backend.events.servlet.SlackEventsApiServlet; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; - -import javax.servlet.annotation.WebServlet; - -public class SimpleEventsApiBackend { - - @Slf4j - @WebServlet - public static class SlackEventsServlet extends SlackEventsApiServlet { - @Override - protected void setupDispatcher(EventsDispatcher dispatcher) { - dispatcher.register(new MessageHandler() { - @Override - public void handle(MessagePayload payload) { - log.info("payload: {}", payload); - } - }); - } - } - - // https://www.eclipse.org/jetty/documentation/current/embedding-jetty.html - - public static void main(String[] args) throws Exception { - Server server = new Server(3000); - ServletHandler handler = new ServletHandler(); - server.setHandler(handler); - handler.addServletWithMapping(SlackEventsServlet.class, "/slack/events"); - server.start(); - server.join(); - } -} - diff --git a/slack-app-backend/src/test/java/test_with_remote_apis/interactive_messages/BlockKitBackend.java b/slack-app-backend/src/test/java/test_with_remote_apis/interactive_messages/BlockKitBackend.java deleted file mode 100644 index 4bc52f47b..000000000 --- a/slack-app-backend/src/test/java/test_with_remote_apis/interactive_messages/BlockKitBackend.java +++ /dev/null @@ -1,137 +0,0 @@ -package test_with_remote_apis.interactive_messages; - -import com.google.gson.Gson; -import com.slack.api.app_backend.SlackSignature; -import com.slack.api.app_backend.events.servlet.SlackSignatureVerifier; -import com.slack.api.app_backend.interactive_components.payload.BlockActionPayload; -import com.slack.api.app_backend.interactive_components.response.ActionResponse; -import com.slack.api.app_backend.interactive_components.response.BlockSuggestionResponse; -import com.slack.api.app_backend.interactive_components.response.Option; -import com.slack.api.app_backend.slash_commands.SlashCommandPayloadParser; -import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload; -import com.slack.api.app_backend.util.JsonPayloadExtractor; -import com.slack.api.app_backend.util.JsonPayloadTypeDetector; -import com.slack.api.model.block.SectionBlock; -import com.slack.api.model.block.composition.MarkdownTextObject; -import com.slack.api.model.block.composition.PlainTextObject; -import com.slack.api.model.block.element.ExternalSelectElement; -import com.slack.api.util.json.GsonFactory; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; - -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Arrays; -import java.util.stream.Collectors; - -public class BlockKitBackend { - - @Slf4j - @WebServlet - public static class SlackEventsServlet extends HttpServlet { - - // Configure these two env variables to run this servlet - private final String slackSigningSecret = System.getenv("SLACK_TEST_SIGNING_SECRET"); - - private final SlackSignature.Generator signatureGenerator = new SlackSignature.Generator(slackSigningSecret); - private final SlackSignatureVerifier signatureVerifier = new SlackSignatureVerifier(signatureGenerator); - - private final SlashCommandPayloadParser commandPayloadParser = new SlashCommandPayloadParser(); - - private final Gson gson = GsonFactory.createSnakeCase(); - private final JsonPayloadExtractor payloadExtractor = new JsonPayloadExtractor(); - private final JsonPayloadTypeDetector payloadTypeDetector = new JsonPayloadTypeDetector(); - - protected String doReadRequestBodyAsString(HttpServletRequest httpReq) throws IOException { - return httpReq.getReader().lines().collect(Collectors.joining(System.lineSeparator())); - } - - protected void doPost(HttpServletRequest httpReq, HttpServletResponse httpResp) throws IOException { - // any command to invoke - String requestBody = doReadRequestBodyAsString(httpReq); - log.info("requestBody - {}", requestBody); - - boolean validSignature = this.signatureVerifier.isValid(httpReq, requestBody); - if (!validSignature) { // invalid signature - if (log.isDebugEnabled()) { - String signature = httpReq.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE); - log.debug("An invalid X-Slack-Signature detected - {}", signature); - } - httpResp.setStatus(401); - return; - } - - if (requestBody.startsWith("payload=")) { - String json = payloadExtractor.extractIfExists(requestBody); - String payloadType = payloadTypeDetector.detectType(json); - - if (payloadType.equals("block_actions")) { - // extracts trigger_id for views.open - BlockActionPayload payload = gson.fromJson(json, BlockActionPayload.class); - log.info("block_actions - {}", payload); - } else if (payloadType.equals("block_suggestion")) { - log.info("block_suggestion - {}", json); - - httpResp.setStatus(200); - httpResp.setHeader("Content-Type", "application/json"); - - BlockSuggestionResponse response = BlockSuggestionResponse.builder() - .options(Arrays.asList( - Option.builder().text(PlainTextObject.builder().text("label1").build()).value("v1").build(), - Option.builder().text(MarkdownTextObject.builder().text("label2").build()).value("v2").build(), - Option.builder().text(PlainTextObject.builder().text("label3").build()).value("v3").build() - )) - .build(); - String responseBody = gson.toJson(response); - log.info("response - {}", responseBody); - httpResp.getWriter().write(responseBody); - - return; - - } else { - log.info("Unexpected payload - {}", payloadType); - } - } else { - SlashCommandPayload payload = commandPayloadParser.parse(requestBody); - log.info("command - {}", payload); -// Payload webhookPayload = Payload.builder() -// .blocks(Arrays.asList( -// SectionBlock.builder() -// .text(PlainTextObject.builder().text("Hi").build()) -// .accessory(ExternalSelectElement.builder().actionId("external_select_id").build()) -// .build())) -// .build(); -// WebhookResponse response = slack.send(payload.getResponseUrl(), webhookPayload); -// log.info("response - {}", response); - } - - // ack - httpResp.setStatus(200); - httpResp.setHeader("Content-Type", "application/json"); - - ActionResponse response = new ActionResponse(); - response.setResponseType("ephemeral"); - response.setBlocks(Arrays.asList(SectionBlock.builder() - .text(PlainTextObject.builder().text("Hi").build()) - .accessory(ExternalSelectElement.builder().actionId("external_select_id").build()).build())); - String responseBody = gson.toJson(response); - httpResp.getWriter().write(responseBody); - } - } - - // https://www.eclipse.org/jetty/documentation/current/embedding-jetty.html - - public static void main(String[] args) throws Exception { - Server server = new Server(3000); - ServletHandler handler = new ServletHandler(); - server.setHandler(handler); - handler.addServletWithMapping(SlackEventsServlet.class, "/slack/events"); - server.start(); - server.join(); - } -} - diff --git a/slack-app-backend/src/test/java/test_with_remote_apis/views/ViewsApiBackend.java b/slack-app-backend/src/test/java/test_with_remote_apis/views/ViewsApiBackend.java deleted file mode 100644 index 92bf6d3cd..000000000 --- a/slack-app-backend/src/test/java/test_with_remote_apis/views/ViewsApiBackend.java +++ /dev/null @@ -1,190 +0,0 @@ -package test_with_remote_apis.views; - -import com.google.gson.Gson; -import com.slack.api.Slack; -import com.slack.api.app_backend.SlackSignature; -import com.slack.api.app_backend.events.servlet.SlackSignatureVerifier; -import com.slack.api.app_backend.interactive_components.payload.BlockActionPayload; -import com.slack.api.app_backend.slash_commands.SlashCommandPayloadParser; -import com.slack.api.app_backend.slash_commands.payload.SlashCommandPayload; -import com.slack.api.app_backend.util.JsonPayloadExtractor; -import com.slack.api.app_backend.util.JsonPayloadTypeDetector; -import com.slack.api.app_backend.views.payload.ViewClosedPayload; -import com.slack.api.app_backend.views.payload.ViewSubmissionPayload; -import com.slack.api.methods.SlackApiException; -import com.slack.api.methods.response.views.ViewsOpenResponse; -import com.slack.api.methods.response.views.ViewsUpdateResponse; -import com.slack.api.model.block.InputBlock; -import com.slack.api.model.block.LayoutBlock; -import com.slack.api.model.block.SectionBlock; -import com.slack.api.model.block.composition.PlainTextObject; -import com.slack.api.model.block.element.ButtonElement; -import com.slack.api.model.block.element.PlainTextInputElement; -import com.slack.api.model.view.View; -import com.slack.api.model.view.ViewSubmit; -import com.slack.api.model.view.ViewTitle; -import com.slack.api.util.json.GsonFactory; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; - -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -public class ViewsApiBackend { - - @Slf4j - @WebServlet - public static class SlackEventsServlet extends HttpServlet { - - // Configure these two env variables to run this servlet - private final String slackSigningSecret = System.getenv("SLACK_TEST_SIGNING_SECRET"); - private final String token = System.getenv("SLACK_BOT_USER_TEST_OAUTH_ACCESS_TOKEN"); - - private final SlackSignature.Generator signatureGenerator = new SlackSignature.Generator(slackSigningSecret); - private final SlackSignatureVerifier signatureVerifier = new SlackSignatureVerifier(signatureGenerator); - - private final SlashCommandPayloadParser commandPayloadParser = new SlashCommandPayloadParser(); - - private final Gson gson = GsonFactory.createSnakeCase(); - private final JsonPayloadExtractor payloadExtractor = new JsonPayloadExtractor(); - private final JsonPayloadTypeDetector payloadTypeDetector = new JsonPayloadTypeDetector(); - - private final Slack slack = Slack.getInstance(); - - protected String doReadRequestBodyAsString(HttpServletRequest httpReq) throws IOException { - return httpReq.getReader().lines().collect(Collectors.joining(System.lineSeparator())); - } - - protected void doPost(HttpServletRequest httpReq, HttpServletResponse httpResp) throws IOException { - // any command to invoke - String requestBody = doReadRequestBodyAsString(httpReq); - log.info("requestBody - {}", requestBody); - - boolean validSignature = this.signatureVerifier.isValid(httpReq, requestBody); - if (!validSignature) { // invalid signature - if (log.isDebugEnabled()) { - String signature = httpReq.getHeader(SlackSignature.HeaderNames.X_SLACK_SIGNATURE); - log.debug("An invalid X-Slack-Signature detected - {}", signature); - } - httpResp.setStatus(401); - return; - } - - String triggerId; - if (requestBody.startsWith("payload=")) { - String json = payloadExtractor.extractIfExists(requestBody); - String payloadType = payloadTypeDetector.detectType(json); - - if (payloadType.equals("view_submission")) { - // just receives view_submission requests - ViewSubmissionPayload payload = gson.fromJson(json, ViewSubmissionPayload.class); - log.info("view_submission - {}", payload); - httpResp.setStatus(200); - return; - - } else if (payloadType.equals("view_closed")) { - // just receives view_closed requests when notifyOnClose is true - ViewClosedPayload payload = gson.fromJson(json, ViewClosedPayload.class); - log.info("view_closed - {}", payload); - httpResp.setStatus(200); - return; - - } else if (payloadType.equals("block_actions")) { - // extracts trigger_id for views.open - BlockActionPayload payload = gson.fromJson(json, BlockActionPayload.class); - if (payload.getView() != null) { - try { - View newView = View.builder() - .type("modal") - .callbackId("callback_id2") - .title(ViewTitle.builder().type("plain_text").text("Title2").build()) - .submit(ViewSubmit.builder().type("plain_text").text("Submit2").build()) - .blocks(Arrays.asList(InputBlock.builder() - .blockId("text_input") - .label(PlainTextObject.builder().text("text").build()) - .element(PlainTextInputElement.builder().actionId("single").multiline(true).build()) - .build())) - .build(); - ViewsUpdateResponse apiResponse = slack.methods(token).viewsUpdate(req -> req - .viewId(payload.getView().getId()) - .view(newView)); - log.info("views.update - {}", apiResponse); - } catch (SlackApiException e) { - log.error(e.getResponseBody(), e); - } - return; - } else { - triggerId = payload.getTriggerId(); - } - } else { - log.info("Unexpected payload - {}", payloadType); - return; - } - } else { - // extracts trigger_id for views.open - SlashCommandPayload payload = commandPayloadParser.parse(requestBody); - triggerId = payload.getTriggerId(); - } - - List blocks = new ArrayList<>(); - blocks.add(InputBlock.builder() - .blockId("text_input") - .label(PlainTextObject.builder().text("text").build()) - .element(PlainTextInputElement.builder().actionId("single").multiline(true).build()) - .build()); - blocks.add(SectionBlock.builder() - .text(PlainTextObject.builder().text("button").build()) - .accessory(ButtonElement.builder().text(PlainTextObject.builder().text("submit").build()).actionId("click").value("1").build()) - .build()); - - View view = View.builder() - .type("modal") - .callbackId("callback_id") - .title(ViewTitle.builder().type("plain_text").text("Title").build()) - .submit(ViewSubmit.builder().type("plain_text").text("Submit").build()) - .notifyOnClose(true) - .blocks(blocks).build(); - log.info(triggerId); - log.info("view: {}", view); - try { - ViewsOpenResponse apiResponse = slack.methods(token).viewsOpen(req -> req - .view(view) - .triggerId(triggerId)); - log.info("views.open - {}", apiResponse); - - if (!apiResponse.isOk()) { - httpResp.setStatus(500); - httpResp.getWriter().write(apiResponse.getError()); - return; - } - } catch (SlackApiException e) { - httpResp.setStatus(500); - httpResp.getWriter().write(e.getMessage()); - return; - } - - // ack - httpResp.setStatus(200); - } - } - - // https://www.eclipse.org/jetty/documentation/current/embedding-jetty.html - - public static void main(String[] args) throws Exception { - Server server = new Server(3000); - ServletHandler handler = new ServletHandler(); - server.setHandler(handler); - handler.addServletWithMapping(SlackEventsServlet.class, "/slack/events"); - server.start(); - server.join(); - } -} -