diff --git a/src/dispatch/plugins/dispatch_slack/case/interactive.py b/src/dispatch/plugins/dispatch_slack/case/interactive.py index 6708b06ca037..cfc78499fc01 100644 --- a/src/dispatch/plugins/dispatch_slack/case/interactive.py +++ b/src/dispatch/plugins/dispatch_slack/case/interactive.py @@ -1697,7 +1697,7 @@ def handle_user_mention( users_not_in_case = [] for user_id in mentioned_users: user_email = dispatch_slack_service.get_user_email(client, user_id) - if not participant_service.get_by_case_id_and_email( + if user_email and not participant_service.get_by_case_id_and_email( db_session=db_session, case_id=context["subject"].id, email=user_email ): users_not_in_case.append(user_email) diff --git a/src/dispatch/plugins/dispatch_slack/incident/interactive.py b/src/dispatch/plugins/dispatch_slack/incident/interactive.py index a9d681e3f099..ff074a6d55d4 100644 --- a/src/dispatch/plugins/dispatch_slack/incident/interactive.py +++ b/src/dispatch/plugins/dispatch_slack/incident/interactive.py @@ -794,9 +794,12 @@ def handle_timeline_added_event( # if user is not found, we default to "Unknown" try: message_sender_email = get_user_email(client=client, user_id=message_sender_id) - individual = individual_service.get_by_email_and_project( - db_session=db_session, email=message_sender_email, project_id=incident.project.id - ) + if message_sender_email: + individual = individual_service.get_by_email_and_project( + db_session=db_session, + email=message_sender_email, + project_id=incident.project.id, + ) except Exception: individual = None @@ -1076,12 +1079,13 @@ def handle_member_joined_channel( if inviter and inviter_is_user: # Participant is added into the incident channel using an @ message or /invite command. inviter_email = get_user_email(client=client, user_id=inviter) - added_by_participant = participant_service.get_by_incident_id_and_email( - db_session=db_session, incident_id=context["subject"].id, email=inviter_email - ) - participant.added_by = added_by_participant + if inviter_email: + added_by_participant = participant_service.get_by_incident_id_and_email( + db_session=db_session, incident_id=context["subject"].id, email=inviter_email + ) + participant.added_by = added_by_participant - else: + if not participant.added_by: # User joins via the `join` button on Web Application or Slack. # We default to the incident commander when we don't know who added the user or the user is the Dispatch bot. incident = incident_service.get( @@ -1129,14 +1133,15 @@ def handle_member_joined_channel( if inviter and inviter_is_user: # Participant is added into the incident channel using an @ message or /invite command. inviter_email = get_user_email(client=client, user_id=inviter) - added_by_participant = participant_service.get_by_case_id_and_email( - db_session=db_session, - case_id=context["subject"].id, - email=inviter_email, - ) - participant.added_by = added_by_participant + if inviter_email: + added_by_participant = participant_service.get_by_case_id_and_email( + db_session=db_session, + case_id=context["subject"].id, + email=inviter_email, + ) + participant.added_by = added_by_participant - else: + if not participant.added_by: # User joins via the `join` button on Web Application or Slack. # We default to the incident commander when we don't know who added the user or the user is the Dispatch bot. participant.added_by = case.assignee @@ -1565,7 +1570,9 @@ def handle_assign_role_submission_event( ack_assign_role_submission_event(ack=ack) assignee_user_id = form_data[AssignRoleBlockIds.user]["value"] assignee_role = form_data[AssignRoleBlockIds.role]["value"] - assignee_email = get_user_email(client=client, user_id=assignee_user_id) + assignee_email = ( + get_user_email(client=client, user_id=assignee_user_id) or "unknown@unknown.com" + ) # we assign the role incident_flows.incident_assign_role_flow( @@ -2556,15 +2563,18 @@ def handle_incident_notification_subscribe_button_click( user_id = context["user_id"] user_email = get_user_email(client=client, user_id=user_id) - if incident.tactical_group: - group_flows.update_group( - subject=incident, - group=incident.tactical_group, - group_action=GroupAction.add_member, - group_member=user_email, - db_session=db_session, - ) - message = f"Success! We've subscribed you to incident {incident.name}. You will start receiving all tactical reports about this incident via email." + if not user_email: + message = "Sorry, we can't invite you to this incident. There was a problem finding your user." + else: + if incident.tactical_group: + group_flows.update_group( + subject=incident, + group=incident.tactical_group, + group_action=GroupAction.add_member, + group_member=user_email, + db_session=db_session, + ) + message = f"Success! We've subscribed you to incident {incident.name}. You will start receiving all tactical reports about this incident via email." respond(text=message, response_type="ephemeral", replace_original=False, delete_original=False) diff --git a/src/dispatch/plugins/dispatch_slack/service.py b/src/dispatch/plugins/dispatch_slack/service.py index 15437b630c06..e5a598c79d5a 100644 --- a/src/dispatch/plugins/dispatch_slack/service.py +++ b/src/dispatch/plugins/dispatch_slack/service.py @@ -274,10 +274,10 @@ def get_user_profile_by_email(client: WebClient, email: str) -> SlackResponse: return _get_user_profile_by_email(WebClientWrapper(client), email) -def get_user_email(client: WebClient, user_id: str) -> str: +def get_user_email(client: WebClient, user_id: str) -> str | None: """Gets the user's email.""" user_info = get_user_info_by_id(client, user_id) - return user_info["profile"]["email"] + return user_info["profile"].get("email") def get_user_avatar_url(client: WebClient, email: str) -> str: diff --git a/src/dispatch/workflow/models.py b/src/dispatch/workflow/models.py index b29749b6ca7c..32e45995380a 100644 --- a/src/dispatch/workflow/models.py +++ b/src/dispatch/workflow/models.py @@ -21,7 +21,7 @@ Pagination, ) from dispatch.participant.models import ParticipantRead -from dispatch.plugin.models import PluginInstance, PluginInstanceRead +from dispatch.plugin.models import PluginInstance, PluginInstanceReadMinimal from dispatch.project.models import ProjectRead from .enums import WorkflowInstanceStatus @@ -121,7 +121,7 @@ class WorkflowSignal(DispatchBase): class WorkflowBase(DispatchBase): name: NameStr resource_id: str - plugin_instance: PluginInstanceRead + plugin_instance: PluginInstanceReadMinimal parameters: Optional[List[dict]] = [] enabled: Optional[bool] description: Optional[str] = Field(None, nullable=True) diff --git a/src/dispatch/workflow/views.py b/src/dispatch/workflow/views.py index 553b8e7229c9..bdf421e41b41 100644 --- a/src/dispatch/workflow/views.py +++ b/src/dispatch/workflow/views.py @@ -1,8 +1,9 @@ -from fastapi import APIRouter, HTTPException, status +from fastapi import APIRouter, HTTPException, status, Depends from pydantic.error_wrappers import ErrorWrapper, ValidationError from dispatch.database.core import DbSession from dispatch.database.service import CommonParameters, search_filter_sort_paginate +from dispatch.auth.permissions import SensitiveProjectActionPermission, PermissionsDependency from dispatch.exceptions import NotFoundError from dispatch.models import PrimaryKey from dispatch.plugin import service as plugin_service @@ -27,7 +28,10 @@ def get_workflows(common: CommonParameters): return search_filter_sort_paginate(model="Workflow", **common) -@router.get("/{workflow_id}", response_model=WorkflowRead) +@router.get( + "/{workflow_id}", + response_model=WorkflowRead, +) def get_workflow(db_session: DbSession, workflow_id: PrimaryKey): """Get a workflow.""" workflow = get(db_session=db_session, workflow_id=workflow_id) @@ -39,7 +43,10 @@ def get_workflow(db_session: DbSession, workflow_id: PrimaryKey): return workflow -@router.get("/instances/{workflow_instance_id}", response_model=WorkflowInstanceRead) +@router.get( + "/instances/{workflow_instance_id}", + response_model=WorkflowInstanceRead, +) def get_workflow_instance(db_session: DbSession, workflow_instance_id: PrimaryKey): """Get a workflow instance.""" workflow_instance = get_instance(db_session=db_session, instance_id=workflow_instance_id) @@ -51,7 +58,11 @@ def get_workflow_instance(db_session: DbSession, workflow_instance_id: PrimaryKe return workflow_instance -@router.post("", response_model=WorkflowRead) +@router.post( + "", + response_model=WorkflowRead, + dependencies=[Depends(PermissionsDependency([SensitiveProjectActionPermission]))], +) def create_workflow(db_session: DbSession, workflow_in: WorkflowCreate): """Create a new workflow.""" plugin_instance = plugin_service.get_instance( @@ -66,7 +77,11 @@ def create_workflow(db_session: DbSession, workflow_in: WorkflowCreate): return create(db_session=db_session, workflow_in=workflow_in) -@router.put("/{workflow_id}", response_model=WorkflowRead) +@router.put( + "/{workflow_id}", + response_model=WorkflowRead, + dependencies=[Depends(PermissionsDependency([SensitiveProjectActionPermission]))], +) def update_workflow(db_session: DbSession, workflow_id: PrimaryKey, workflow_in: WorkflowUpdate): """Update a workflow.""" workflow = get(db_session=db_session, workflow_id=workflow_id) @@ -78,7 +93,11 @@ def update_workflow(db_session: DbSession, workflow_id: PrimaryKey, workflow_in: return update(db_session=db_session, workflow=workflow, workflow_in=workflow_in) -@router.delete("/{workflow_id}", response_model=None) +@router.delete( + "/{workflow_id}", + response_model=None, + dependencies=[Depends(PermissionsDependency([SensitiveProjectActionPermission]))], +) def delete_workflow(db_session: DbSession, workflow_id: PrimaryKey): """Delete a workflow.""" workflow = get(db_session=db_session, workflow_id=workflow_id) @@ -90,7 +109,10 @@ def delete_workflow(db_session: DbSession, workflow_id: PrimaryKey): delete(db_session=db_session, workflow_id=workflow_id) -@router.post("/{workflow_id}/run", response_model=WorkflowInstanceRead) +@router.post( + "/{workflow_id}/run", + response_model=WorkflowInstanceRead, +) def run_workflow( db_session: DbSession, workflow_id: PrimaryKey,