Skip to content

Commit c1fcf02

Browse files
slack - direct messaging (#175)
1 parent c12c044 commit c1fcf02

File tree

3 files changed

+68
-43
lines changed

3 files changed

+68
-43
lines changed

llmstack/apps/apis.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
from llmstack.apps.app_session_utils import create_app_session
2424
from llmstack.apps.handlers.app_processor_runner import AppProcessorRunner
25-
from llmstack.apps.handlers.app_runner_factory import AppRunerFactory
25+
from llmstack.apps.handlers.app_runner_factory import AppRunnerFactory
2626
from llmstack.apps.integration_configs import (
2727
DiscordIntegrationConfig,
2828
SlackIntegrationConfig,
@@ -943,15 +943,15 @@ def run_app_internal(
943943

944944
app_runner_class = None
945945
if platform == "discord":
946-
app_runner_class = AppRunerFactory.get_app_runner("discord")
946+
app_runner_class = AppRunnerFactory.get_app_runner("discord")
947947
elif platform == "slack":
948-
app_runner_class = AppRunerFactory.get_app_runner("slack")
948+
app_runner_class = AppRunnerFactory.get_app_runner("slack")
949949
elif platform == "twilio-sms":
950-
app_runner_class = AppRunerFactory.get_app_runner("twilio-sms")
950+
app_runner_class = AppRunnerFactory.get_app_runner("twilio-sms")
951951
elif platform == "twilio-voice":
952-
app_runner_class = AppRunerFactory.get_app_runner("twilio-voice")
952+
app_runner_class = AppRunnerFactory.get_app_runner("twilio-voice")
953953
else:
954-
app_runner_class = AppRunerFactory.get_app_runner(app.type.slug)
954+
app_runner_class = AppRunnerFactory.get_app_runner(app.type.slug)
955955

956956
app_runner = app_runner_class(
957957
app=app,

llmstack/apps/handlers/app_runner_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from functools import cache
22

33

4-
class AppRunerFactory:
4+
class AppRunnerFactory:
55
@staticmethod
66
@cache
77
def get_app_runner(app_type_slug):

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)