Skip to content

Commit 0c76c12

Browse files
authored
Merge pull request #420 from seratch/issue-419
Fix #419 Enable access to App in SlackApiLambdaHandler
2 parents 4a0f4e5 + c9fe6d9 commit 0c76c12

File tree

3 files changed

+235
-25
lines changed

3 files changed

+235
-25
lines changed

bolt-aws-lambda/src/main/java/com/slack/api/bolt/aws_lambda/SlackApiLambdaHandler.java

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,47 @@ public abstract class SlackApiLambdaHandler implements RequestHandler<ApiGateway
2626
private final App app;
2727

2828
public SlackApiLambdaHandler(App app) {
29-
this.app = app;
3029
this.requestParser = new SlackRequestParser(app.config());
30+
this.app = app;
31+
app.start();
32+
}
33+
34+
/**
35+
* Returns true if the given incoming request is an internal warmup request.
36+
* You can use your own logic for distinguishing requests from Slack from your own internal warmup trigger.
37+
*/
38+
protected abstract boolean isWarmupRequest(ApiGatewayRequest awsReq);
39+
40+
@Override
41+
public ApiGatewayResponse handleRequest(ApiGatewayRequest input, Context context) {
42+
if (isWarmupRequest(input)) {
43+
// This one is always an internal request
44+
// (It's not possible to create this request body via API Gateway)
45+
log.info("Successfully responded to a warmup request ({})", input);
46+
return null;
47+
}
48+
Request<?> req = toSlackRequest(input);
49+
try {
50+
if (req == null) {
51+
return ApiGatewayResponse.builder().statusCode(400).rawBody("Invalid Request").build();
52+
}
53+
return toApiGatewayResponse(app.run(req));
54+
} catch (Exception e) {
55+
log.error("Failed to respond to a request (request: {}, error: {})", input.getBody(), e.getMessage(), e);
56+
// As this response body can be exposed, it should not have detailed information.
57+
return ApiGatewayResponse.builder().statusCode(500).rawBody("Internal Server Error").build();
58+
}
59+
}
60+
61+
// -------------------------------------------
62+
// Extensible internal methods
63+
// -------------------------------------------
64+
65+
/**
66+
* Returns the underlying {@link App} instance in this handler.
67+
*/
68+
protected App app() {
69+
return app;
3170
}
3271

3372
protected Request<?> toSlackRequest(ApiGatewayRequest awsReq) {
@@ -53,29 +92,9 @@ protected ApiGatewayResponse toApiGatewayResponse(Response slackResp) {
5392
.build();
5493
}
5594

56-
protected abstract boolean isWarmupRequest(ApiGatewayRequest awsReq);
57-
58-
@Override
59-
public ApiGatewayResponse handleRequest(ApiGatewayRequest input, Context context) {
60-
if (isWarmupRequest(input)) {
61-
// This one is always an internal request
62-
// (It's not possible to create this request body via API Gateway)
63-
app.start();
64-
log.info("Successfully responded to a warmup request ({})", input);
65-
return null;
66-
}
67-
Request<?> req = toSlackRequest(input);
68-
try {
69-
if (req == null) {
70-
return ApiGatewayResponse.builder().statusCode(400).rawBody("Invalid Request").build();
71-
}
72-
return toApiGatewayResponse(app.run(req));
73-
} catch (Exception e) {
74-
log.error("Failed to respond to a request (request: {}, error: {})", input.getBody(), e.getMessage(), e);
75-
// As this response body can be exposed, it should not have detailed information.
76-
return ApiGatewayResponse.builder().statusCode(500).rawBody("Internal Server Error").build();
77-
}
78-
}
95+
// -------------------------------------------
96+
// Private utility methods
97+
// -------------------------------------------
7998

8099
private static Map<String, String> toStringToStringMap(Map<String, List<String>> stringToStringListMap) {
81100
Map<String, String> headers = new HashMap<>();
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package test_locally;
2+
3+
import com.amazonaws.services.lambda.runtime.Context;
4+
import com.slack.api.Slack;
5+
import com.slack.api.SlackConfig;
6+
import com.slack.api.app_backend.SlackSignature;
7+
import com.slack.api.app_backend.events.payload.MessagePayload;
8+
import com.slack.api.bolt.App;
9+
import com.slack.api.bolt.AppConfig;
10+
import com.slack.api.bolt.aws_lambda.SlackApiLambdaHandler;
11+
import com.slack.api.bolt.aws_lambda.request.ApiGatewayRequest;
12+
import com.slack.api.bolt.aws_lambda.request.RequestContext;
13+
import com.slack.api.bolt.aws_lambda.response.ApiGatewayResponse;
14+
import com.slack.api.bolt.response.Response;
15+
import com.slack.api.model.event.MessageEvent;
16+
import com.slack.api.util.json.GsonFactory;
17+
import org.junit.Test;
18+
import util.AuthTestMockServer;
19+
20+
import java.io.UnsupportedEncodingException;
21+
import java.net.URLEncoder;
22+
import java.util.Arrays;
23+
import java.util.HashMap;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.concurrent.atomic.AtomicBoolean;
27+
import java.util.concurrent.atomic.AtomicInteger;
28+
29+
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertTrue;
31+
import static org.mockito.Mockito.mock;
32+
import static util.ObjectInitializer.initProperties;
33+
34+
public class Issue419UseCaseTest {
35+
36+
static class SampleHandler extends SlackApiLambdaHandler {
37+
AtomicInteger messageCount = new AtomicInteger(0);
38+
public SampleHandler(App app) {
39+
super(app);
40+
}
41+
42+
@Override
43+
protected boolean isWarmupRequest(ApiGatewayRequest awsReq) {
44+
return false;
45+
}
46+
47+
public void enableEventSubscription() {
48+
App app = app();
49+
app.event(MessageEvent.class, (event, ctx) -> {
50+
messageCount.incrementAndGet();
51+
return ctx.ack();
52+
});
53+
}
54+
}
55+
56+
String signingSecret = "secret";
57+
58+
@Test
59+
public void enableAdditionalListenersRuntime_issue_419() throws Exception {
60+
AuthTestMockServer slackApiServer = new AuthTestMockServer();
61+
slackApiServer.start();
62+
63+
try {
64+
SlackConfig slackConfig = new SlackConfig();
65+
slackConfig.setMethodsEndpointUrlPrefix(slackApiServer.getMethodsEndpointPrefix());
66+
Slack slack = Slack.getInstance(slackConfig);
67+
App app = new App(AppConfig.builder().slack(slack)
68+
.singleTeamBotToken(AuthTestMockServer.ValidToken)
69+
.signingSecret(signingSecret)
70+
.build()
71+
);
72+
73+
AtomicBoolean called = new AtomicBoolean(false);
74+
app.blockAction("start-event-subscription", (req, ctx) -> {
75+
called.set(true);
76+
// return ctx.ack();
77+
Map<String, List<String>> headers = new HashMap<>();
78+
headers.put("additional-header", Arrays.asList("foo"));
79+
return Response.builder().statusCode(200).headers(headers).build();
80+
});
81+
82+
SampleHandler handler = new SampleHandler(app);
83+
84+
Context context = mock(Context.class);
85+
86+
// block_actions
87+
ApiGatewayRequest req = buildBlockActionsEvent();
88+
ApiGatewayResponse response = handler.handleRequest(req, context);
89+
90+
assertEquals(200, response.getStatusCode());
91+
assertTrue(called.get());
92+
93+
// message event
94+
ApiGatewayRequest message1 = buildMessageEvent();
95+
ApiGatewayResponse message1Response = handler.handleRequest(message1, context);
96+
assertEquals(200, message1Response.getStatusCode());
97+
assertEquals(0, handler.messageCount.get());
98+
99+
// enable other listeners
100+
handler.enableEventSubscription();
101+
102+
// message event
103+
ApiGatewayRequest message2 = buildMessageEvent();
104+
ApiGatewayResponse message2Response = handler.handleRequest(message2, context);
105+
assertEquals(200, message2Response.getStatusCode());
106+
assertEquals(1, handler.messageCount.get());
107+
108+
} finally {
109+
slackApiServer.stop();
110+
}
111+
}
112+
113+
ApiGatewayRequest buildBlockActionsEvent() throws UnsupportedEncodingException {
114+
ApiGatewayRequest req = new ApiGatewayRequest();
115+
initProperties(req);
116+
Map<String, String> headers = new HashMap<>();
117+
String timestamp = String.valueOf((System.currentTimeMillis() / 1000));
118+
String blockActionsPayload = "{\n" +
119+
" \"type\": \"block_actions\",\n" +
120+
" \"team\": {\n" +
121+
" \"id\": \"\"\n" +
122+
" },\n" +
123+
" \"user\": {\n" +
124+
" \"id\": \"\"\n" +
125+
" },\n" +
126+
" \"api_app_id\": \"\",\n" +
127+
" \"token\": \"\",\n" +
128+
" \"container\": {\n" +
129+
" },\n" +
130+
" \"trigger_id\": \"\",\n" +
131+
" \"channel\": {\n" +
132+
" \"id\": \"\"\n" +
133+
" },\n" +
134+
" \"message\": {\n" +
135+
" \"type\": \"\",\n" +
136+
" \"subtype\": \"\",\n" +
137+
" \"team\": \"\",\n" +
138+
" \"channel\": \"\",\n" +
139+
" \"user\": \"\",\n" +
140+
" \"username\": \"\",\n" +
141+
" \"text\": \"\",\n" +
142+
" \"blocks\": []\n" +
143+
" }," +
144+
" \"actions\":[{\n" +
145+
" \"type\":\"button\",\n" +
146+
" \"block_id\":\"two-block\",\n" +
147+
" \"action_id\":\"start-event-subscription\",\n" +
148+
" \"text\":{\n" +
149+
" \"type\":\"plain_text\",\n" +
150+
" \"text\":\"Let's Go!\",\n" +
151+
" \"emoji\":true\n" +
152+
" },\n" +
153+
" \"action_ts\":\"1571318425.267782\"\n" +
154+
" }]\n" +
155+
"}";
156+
String requestBody = "payload=" + URLEncoder.encode(blockActionsPayload, "UTF-8");
157+
SlackSignature.Generator signatureGenerator = new SlackSignature.Generator(signingSecret);
158+
headers.put(SlackSignature.HeaderNames.X_SLACK_REQUEST_TIMESTAMP, timestamp);
159+
headers.put(SlackSignature.HeaderNames.X_SLACK_SIGNATURE, signatureGenerator.generate(timestamp, requestBody));
160+
req.setHeaders(headers);
161+
req.setRequestContext(initProperties(new RequestContext()));
162+
req.setBody(requestBody);
163+
return req;
164+
}
165+
166+
ApiGatewayRequest buildMessageEvent() {
167+
ApiGatewayRequest req = new ApiGatewayRequest();
168+
initProperties(req);
169+
Map<String, String> headers = new HashMap<>();
170+
String timestamp = String.valueOf((System.currentTimeMillis() / 1000));
171+
MessagePayload payload = new MessagePayload();
172+
payload.setType("event_callback");
173+
payload.setTeamId("T123");
174+
MessageEvent message = new MessageEvent();
175+
message.setText("Hello");
176+
message.setTs("123.123");
177+
message.setTeam("T123");
178+
message.setUser("U123");
179+
payload.setEvent(message);
180+
String requestBody = GsonFactory.createSnakeCase().toJson(payload);
181+
SlackSignature.Generator signatureGenerator = new SlackSignature.Generator(signingSecret);
182+
headers.put(SlackSignature.HeaderNames.X_SLACK_REQUEST_TIMESTAMP, timestamp);
183+
headers.put(SlackSignature.HeaderNames.X_SLACK_SIGNATURE, signatureGenerator.generate(timestamp, requestBody));
184+
req.setHeaders(headers);
185+
req.setRequestContext(initProperties(new RequestContext()));
186+
req.setBody(requestBody);
187+
return req;
188+
}
189+
190+
}

json-logs/samples/rtm/LinkSharedEvent.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"domain": "",
1010
"url": ""
1111
}
12-
]
12+
],
13+
"event_ts": ""
1314
}

0 commit comments

Comments
 (0)