Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit cea0f45

Browse files
authored
enhancement(slack): adding slash command and button to manually engage user for mfa prompt (#5429)
1 parent 08262ef commit cea0f45

File tree

4 files changed

+270
-27
lines changed

4 files changed

+270
-27
lines changed

src/dispatch/plugins/dispatch_slack/case/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class CaseNotificationActions(DispatchEnum):
1010
reopen = "case-notification-reopen"
1111
resolve = "case-notification-resolve"
1212
triage = "case-notification-triage"
13+
user_mfa = "case-notification-user-mfa"
1314
invite_user_case = ConversationButtonActions.invite_user_case
1415

1516

src/dispatch/plugins/dispatch_slack/case/interactive.py

Lines changed: 173 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from dispatch.plugins.dispatch_slack.case.messages import (
5858
create_case_message,
5959
create_signal_engagement_message,
60+
create_manual_engagement_message,
6061
)
6162
from dispatch.plugins.dispatch_slack.config import SlackConversationConfiguration
6263
from dispatch.plugins.dispatch_slack.decorators import message_dispatcher
@@ -75,6 +76,7 @@
7576
relative_date_picker_input,
7677
resolution_input,
7778
title_input,
79+
participant_select,
7880
)
7981
from dispatch.plugins.dispatch_slack.middleware import (
8082
action_context_middleware,
@@ -147,6 +149,8 @@ def configure(config: SlackConversationConfiguration):
147149

148150
app.command(config.slack_command_update_case, middleware=middleware)(handle_update_case_command)
149151

152+
app.command(config.slack_command_engage_user, middleware=middleware)(handle_engage_user_command)
153+
150154

151155
# Commands
152156

@@ -339,6 +343,105 @@ def handle_list_signals_command(
339343
)
340344

341345

346+
def handle_engage_user_command(
347+
ack: Ack,
348+
body: dict,
349+
client: WebClient,
350+
context: BoltContext,
351+
db_session: Session,
352+
) -> None:
353+
"""Handles engage user command."""
354+
ack()
355+
356+
case = case_service.get(db_session=db_session, case_id=context["subject"].id)
357+
default_engagement = "We'd like to verify your identity. Can you please confirm this is you?"
358+
359+
blocks = [
360+
Context(
361+
elements=[
362+
MarkdownText(
363+
text="Accept the defaults or adjust as needed. Person to engage must already be a participant in the case."
364+
)
365+
]
366+
),
367+
participant_select(label="Person to engage", participants=case.participants),
368+
description_input(label="Engagement text", initial_value=default_engagement),
369+
]
370+
371+
modal = Modal(
372+
title="Engage user via MFA",
373+
submit="Engage",
374+
blocks=blocks,
375+
close="Close",
376+
callback_id="manual-engage-mfa",
377+
private_metadata=context["subject"].json(),
378+
).build()
379+
380+
client.views_open(
381+
trigger_id=body["trigger_id"],
382+
view=modal,
383+
)
384+
385+
386+
@app.view(
387+
"manual-engage-mfa",
388+
middleware=[
389+
action_context_middleware,
390+
db_middleware,
391+
modal_submit_middleware,
392+
],
393+
)
394+
def engage(
395+
ack: Ack,
396+
body: dict,
397+
client: WebClient,
398+
context: BoltContext,
399+
db_session: Session,
400+
form_data: dict,
401+
) -> None:
402+
"""Handles the engage user action."""
403+
ack()
404+
405+
if form_data.get(DefaultBlockIds.participant_select):
406+
user_email = client.users_info(user=form_data[DefaultBlockIds.participant_select]["value"])[
407+
"user"
408+
]["profile"]["email"]
409+
else:
410+
# TODO: log error
411+
return
412+
413+
if form_data.get(DefaultBlockIds.description_input):
414+
engagement = form_data[DefaultBlockIds.description_input]
415+
else:
416+
# TODO log error
417+
return
418+
419+
case = case_service.get(db_session=db_session, case_id=context["subject"].id)
420+
421+
user = client.users_lookupByEmail(email=user_email)
422+
423+
result = client.chat_postMessage(
424+
text="Engaging user...",
425+
channel=case.conversation.channel_id,
426+
thread_ts=case.conversation.thread_id if case.has_thread else None,
427+
)
428+
thread_ts = result.data.get("ts")
429+
430+
blocks = create_manual_engagement_message(
431+
case=case,
432+
channel_id=case.conversation.channel_id,
433+
engagement=engagement,
434+
user_email=user_email,
435+
user_id=user["user"]["id"],
436+
thread_ts=thread_ts,
437+
)
438+
client.chat_update(
439+
blocks=blocks,
440+
channel=case.conversation.channel_id,
441+
ts=thread_ts,
442+
)
443+
444+
342445
def _draw_list_signal_modal(
343446
client: WebClient,
344447
body: dict,
@@ -1435,6 +1538,26 @@ def create_channel_button_click(
14351538
client.views_open(trigger_id=body["trigger_id"], view=modal)
14361539

14371540

1541+
@app.action(
1542+
CaseNotificationActions.user_mfa,
1543+
middleware=[button_context_middleware, db_middleware, user_middleware],
1544+
)
1545+
def user_mfa_button_click(
1546+
ack: Ack,
1547+
body: dict,
1548+
client: WebClient,
1549+
context: BoltContext,
1550+
db_session: Session,
1551+
):
1552+
return handle_engage_user_command(
1553+
ack=ack,
1554+
body=body,
1555+
client=client,
1556+
context=context,
1557+
db_session=db_session,
1558+
)
1559+
1560+
14381561
def ack_handle_create_channel_event(ack: Ack, case: Case) -> None:
14391562
"""Handles the case channel creation event."""
14401563
msg = (
@@ -2192,7 +2315,9 @@ def engagement_button_approve_click(
21922315
mfa_plugin = plugin_service.get_active_instance(
21932316
db_session=db_session, project_id=context["subject"].project_id, plugin_type="auth-mfa"
21942317
)
2195-
mfa_enabled = True if mfa_plugin and engagement.require_mfa else False
2318+
2319+
require_mfa = engagement.require_mfa if engagement else True
2320+
mfa_enabled = True if mfa_plugin and require_mfa else False
21962321

21972322
blocks = [
21982323
Section(text="Confirm that this is expected and that it is not suspicious behavior."),
@@ -2288,7 +2413,10 @@ def handle_engagement_submission_event(
22882413
mfa_plugin = plugin_service.get_active_instance(
22892414
db_session=db_session, project_id=context["subject"].project_id, plugin_type="auth-mfa"
22902415
)
2291-
mfa_enabled = True if mfa_plugin and engagement.require_mfa else False
2416+
2417+
require_mfa = engagement.require_mfa if engagement else True
2418+
mfa_enabled = True if mfa_plugin and require_mfa else False
2419+
22922420
challenge, challenge_url = mfa_plugin.instance.create_mfa_challenge(
22932421
action="signal-engagement-confirmation",
22942422
current_user=user,
@@ -2299,8 +2427,12 @@ def handle_engagement_submission_event(
22992427
ack_mfa_required_submission_event(ack=ack, mfa_enabled=mfa_enabled, challenge_url=challenge_url)
23002428

23012429
case = case_service.get(db_session=db_session, case_id=metadata["id"])
2302-
signal_instance = signal_service.get_signal_instance(
2303-
db_session=db_session, signal_instance_id=UUID(metadata["signal_instance_id"])
2430+
signal_instance = (
2431+
signal_service.get_signal_instance(
2432+
db_session=db_session, signal_instance_id=UUID(metadata["signal_instance_id"])
2433+
)
2434+
if metadata["signal_instance_id"]
2435+
else None
23042436
)
23052437
# Get context provided by the user
23062438
context_from_user = body["view"]["state"]["values"][DefaultBlockIds.description_input][
@@ -2324,6 +2456,7 @@ def handle_engagement_submission_event(
23242456
signal_instance=signal_instance,
23252457
user=user_who_clicked_button,
23262458
view_id=body["view"]["id"],
2459+
thread_id=context["subject"].thread_id,
23272460
)
23282461
db_session.commit()
23292462
return
@@ -2339,6 +2472,7 @@ def handle_engagement_submission_event(
23392472
signal_instance=signal_instance,
23402473
user=user_who_clicked_button,
23412474
view_id=body["view"]["id"],
2475+
thread_id=context["subject"].thread_id,
23422476
)
23432477

23442478

@@ -2347,12 +2481,13 @@ def send_engagement_response(
23472481
client: WebClient,
23482482
context_from_user: str,
23492483
db_session: Session,
2350-
engagement: SignalEngagement,
2484+
engagement: SignalEngagement | None,
23512485
engaged_user: str,
23522486
response: str,
2353-
signal_instance: SignalInstance,
2487+
signal_instance: SignalInstance | None,
23542488
user: DispatchUser,
23552489
view_id: str,
2490+
thread_id: str,
23562491
):
23572492
if response == MfaChallengeStatus.APPROVED:
23582493
title = "Approve"
@@ -2395,19 +2530,26 @@ def send_engagement_response(
23952530
user_email=engaged_user,
23962531
engagement_status=engagement_status,
23972532
)
2398-
client.chat_update(
2399-
blocks=blocks,
2400-
channel=case.conversation.channel_id,
2401-
ts=signal_instance.engagement_thread_ts,
2402-
)
2403-
resolve_case(
2404-
case=case,
2405-
channel_id=case.conversation.channel_id,
2406-
client=client,
2407-
db_session=db_session,
2408-
context_from_user=context_from_user,
2409-
user=user,
2410-
)
2533+
if signal_instance:
2534+
client.chat_update(
2535+
blocks=blocks,
2536+
channel=case.conversation.channel_id,
2537+
ts=signal_instance.engagement_thread_ts,
2538+
)
2539+
resolve_case(
2540+
case=case,
2541+
channel_id=case.conversation.channel_id,
2542+
client=client,
2543+
db_session=db_session,
2544+
context_from_user=context_from_user,
2545+
user=user,
2546+
)
2547+
else:
2548+
client.chat_update(
2549+
blocks=blocks,
2550+
channel=case.conversation.channel_id,
2551+
ts=thread_id,
2552+
)
24112553

24122554

24132555
def resolve_case(
@@ -2528,9 +2670,14 @@ def handle_engagement_deny_submission_event(
25282670
metadata = json.loads(body["view"]["private_metadata"])
25292671
engaged_user = metadata["user"]
25302672
case = case_service.get(db_session=db_session, case_id=metadata["id"])
2531-
signal_instance = signal_service.get_signal_instance(
2532-
db_session=db_session, signal_instance_id=UUID(metadata["signal_instance_id"])
2673+
signal_instance = (
2674+
signal_service.get_signal_instance(
2675+
db_session=db_session, signal_instance_id=UUID(metadata["signal_instance_id"])
2676+
)
2677+
if metadata["signal_instance_id"]
2678+
else None
25332679
)
2680+
25342681
engagement = signal_service.get_signal_engagement(
25352682
db_session=db_session,
25362683
signal_engagement_id=metadata["engagement_id"],
@@ -2551,6 +2698,10 @@ def handle_engagement_deny_submission_event(
25512698
channel=case.conversation.channel_id,
25522699
thread_ts=case.conversation.thread_id,
25532700
)
2701+
2702+
thread_ts = (
2703+
signal_instance.engagement_thread_ts if signal_instance else context["subject"].thread_id
2704+
)
25542705
blocks = create_signal_engagement_message(
25552706
case=case,
25562707
channel_id=case.conversation.channel_id,
@@ -2562,5 +2713,5 @@ def handle_engagement_deny_submission_event(
25622713
client.chat_update(
25632714
blocks=blocks,
25642715
channel=case.conversation.channel_id,
2565-
ts=signal_instance.engagement_thread_ts,
2716+
ts=thread_ts,
25662717
)

0 commit comments

Comments
 (0)