Skip to content

Commit a69b6db

Browse files
committed
feature(slack-app): enable response to direct messages, excluding bots
1 parent 6a2518a commit a69b6db

File tree

1 file changed

+61
-36
lines changed

1 file changed

+61
-36
lines changed

llmstack/apps/handlers/slack_app.py

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,30 @@ def process_slack_message_text(text):
3030
return re.sub(r"<@.*>(\|)?", "", text).strip()
3131

3232

33-
def get_slack_user_email(slack_user_id, slack_bot_token):
33+
def get_slack_user(slack_user_id, slack_bot_token):
3434
http_request = requests.get(
3535
"https://slack.com/api/users.info",
3636
params={
3737
"user": slack_user_id,
3838
},
39-
headers={"Authorization": f"Bearer {slack_bot_token}"},
39+
headers={
40+
"Authorization": f"Bearer {slack_bot_token}",
41+
},
4042
)
43+
44+
slack_user = None
4145
if http_request.status_code == 200:
42-
return http_request.json()["user"]["profile"]["email"]
43-
else:
44-
return None
46+
http_response = http_request.json()
47+
slack_user = http_response["user"]["profile"]
48+
return slack_user
4549

4650

4751
class SlackAppRunner(AppRunner):
52+
def __init__(self, *args, **kwargs):
53+
super().__init__(*args, **kwargs)
54+
self._slack_user = {}
55+
self._slack_user_email = ""
56+
4857
def app_init(self):
4958
self.slack_config = (
5059
SlackIntegrationConfig().from_dict(
@@ -60,31 +69,38 @@ def app_init(self):
6069
self.app_run_request_user = self._get_app_request_user(
6170
self.request.data,
6271
)
63-
self.session_id = self._get_slack_app_seession_id(self.request.data)
72+
self.session_id = self._get_slack_app_session_id(self.request.data)
6473
self.app_session = self._get_or_create_app_session()
6574

6675
def _get_app_request_user(self, slack_request_payload):
67-
self._slack_user_email = ""
76+
request_user = AnonymousUser()
77+
6878
if "event" in slack_request_payload and "user" in slack_request_payload["event"]:
79+
slack_user_id = slack_request_payload["event"]["user"]
6980
try:
70-
slack_user_id = slack_request_payload["event"]["user"]
71-
slack_user_email = get_slack_user_email(
72-
slack_user_id,
73-
self.slack_bot_token,
81+
self._slack_user = (
82+
get_slack_user(
83+
slack_user_id,
84+
self.slack_bot_token,
85+
)
86+
or {}
7487
)
75-
if slack_user_email is not None:
76-
self._slack_user_email = slack_user_email
77-
user_object = User.objects.get(email=slack_user_email)
78-
return user_object if user_object is not None else AnonymousUser()
79-
except Exception:
88+
# The email address of a Slack user is not guaranteed to be available for bot users.
89+
self._slack_user_email = self._slack_user.get("email") or ""
90+
user_object = (
91+
User.objects.filter(email=self._slack_user_email).first() if self._slack_user_email else None
92+
)
93+
if user_object:
94+
request_user = user_object
95+
except Exception as e:
8096
logger.exception(
81-
f"Error in fetching user object from slack payload {slack_request_payload}",
97+
f"Error in fetching user object from slack payload {slack_request_payload}: {e}",
8298
)
8399

84-
return AnonymousUser()
100+
return request_user
85101

86-
def _get_slack_app_seession_id(self, slack_request_payload):
87-
if slack_request_payload["type"] == "event_callback" and "event" in slack_request_payload:
102+
def _get_slack_app_session_id(self, slack_request_payload):
103+
if slack_request_payload.get("type") == "event_callback" and "event" in slack_request_payload:
88104
thread_ts = None
89105
session_identifier = None
90106
if "thread_ts" in slack_request_payload["event"]:
@@ -180,28 +196,37 @@ def _is_app_accessible(self):
180196
):
181197
raise Exception("Invalid Slack request")
182198

183-
# Verify the request type is either url_verification or event_callback
184-
if self.request.data.get("type") not in [
185-
"event_callback",
186-
"url_verification",
187-
]:
188-
raise Exception("Invalid Slack request")
199+
request_type = self.request.data.get("type")
189200

190-
# Verify the request is coming from the app we expect and the event
191-
# type is app_mention
192-
if self.request.data.get("type") == "event_callback" and (
193-
self.request.data.get(
194-
"api_app_id",
195-
)
196-
!= self.slack_config.get("app_id")
197-
or self.request.data.get("event").get("type") != "app_mention"
198-
):
201+
# the request type should be either url_verification or event_callback
202+
is_valid_request_type = request_type in ["url_verification", "event_callback"]
203+
is_valid_app_token = self.request.data.get("token") == self.slack_config.get("verification_token")
204+
is_valid_app_id = self.request.data.get("api_app_id") == self.slack_config.get("app_id")
205+
206+
# Validate that the app token, app ID and the request type are all valid.
207+
if not (is_valid_app_token and is_valid_app_id and is_valid_request_type):
199208
raise Exception("Invalid Slack request")
200209

201210
# URL verification is allowed without any further checks
202-
if self.request.data.get("type") == "url_verification":
211+
if request_type == "url_verification":
203212
return True
204213

214+
# Verify the request is coming from the app we expect and the event
215+
# type is app_mention
216+
elif request_type == "event_callback":
217+
event_data = self.request.data.get("event") or {}
218+
event_type = event_data.get("type")
219+
channel_type = event_data.get("channel_type")
220+
221+
if event_type == "app_mention":
222+
return True
223+
224+
elif event_type == "message":
225+
# Only allow direct messages from users and not from bots
226+
if channel_type == "im" and "subtype" not in event_data and "bot_id" not in event_data:
227+
return True
228+
raise Exception("Invalid Slack request")
229+
205230
return super()._is_app_accessible()
206231

207232
def _get_csp(self):

0 commit comments

Comments
 (0)