Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 50 additions & 17 deletions src/sentry/integrations/slack/webhooks/command.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import logging
from collections.abc import Iterable

from rest_framework import status
from rest_framework.request import Request
Expand All @@ -9,7 +10,6 @@
from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.helpers.teams import is_team_admin
from sentry.integrations.models.external_actor import ExternalActor
from sentry.integrations.slack.message_builder.disconnected import SlackDisconnectedMessageBuilder
from sentry.integrations.slack.requests.base import SlackDMRequest, SlackRequestError
Expand All @@ -18,8 +18,8 @@
from sentry.integrations.slack.views.link_team import build_team_linking_url
from sentry.integrations.slack.views.unlink_team import build_team_unlinking_url
from sentry.integrations.types import ExternalProviders
from sentry.models.organization import Organization
from sentry.models.organizationmember import OrganizationMember
from sentry.models.organizationmemberteam import OrganizationMemberTeam

_logger = logging.getLogger("sentry.integration.slack.bot-commands")

Expand All @@ -44,15 +44,27 @@
NO_CHANNEL_ID_MESSAGE = "Could not identify the Slack channel ID. Please try again."


def is_team_linked_to_channel(organization: Organization, slack_request: SlackDMRequest) -> bool:
"""Check if a Slack channel already has a team linked to it"""
return ExternalActor.objects.filter(
organization_id=organization.id,
integration_id=slack_request.integration.id,
provider=ExternalProviders.SLACK.value,
external_name=slack_request.channel_name,
external_id=slack_request.channel_id,
).exists()
def get_orgs_with_teams_linked_to_channel(
organization_ids: list[int], slack_request: SlackDMRequest
) -> set[int]:
"""Get the organizations with teams linked to a Slack channel"""
return set(
ExternalActor.objects.filter(
organization_id__in=organization_ids,
integration_id=slack_request.integration.id,
provider=ExternalProviders.SLACK.value,
external_name=slack_request.channel_name,
external_id=slack_request.channel_id,
).values_list("organization_id", flat=True)
)


def get_team_admin_member_ids(org_members: Iterable[OrganizationMember]) -> set[int]:
return set(
OrganizationMemberTeam.objects.filter(
organizationmember_id__in=[om.id for om in org_members], role="admin"
).values_list("organizationmember_id", flat=True)
)


@region_silo_endpoint
Expand Down Expand Up @@ -91,10 +103,17 @@ def link_team(self, slack_request: SlackDMRequest) -> Response:
integration, identity_user
)

# Batch check for team admin roles to avoid N+1 queries
team_admin_member_ids = get_team_admin_member_ids(organization_memberships)

has_valid_role = False
for organization_membership in organization_memberships:
if is_valid_role(organization_membership) or is_team_admin(organization_membership):
if (
is_valid_role(organization_membership)
or organization_membership.id in team_admin_member_ids
):
has_valid_role = True
break

if not has_valid_role:
return self.reply(slack_request, INSUFFICIENT_ROLE_MESSAGE)
Expand Down Expand Up @@ -128,15 +147,29 @@ def unlink_team(self, slack_request: SlackDMRequest) -> Response:
integration, identity_user
)

# Batch check which organizations have teams linked to this channel
linked_org_ids = get_orgs_with_teams_linked_to_channel(
[om.organization_id for om in organization_memberships], slack_request
)

if not linked_org_ids:
return self.reply(slack_request, TEAM_NOT_LINKED_MESSAGE)

# Batch check for team admin roles to avoid N+1 queries
team_admin_member_ids = get_team_admin_member_ids(organization_memberships)

# Find an organization where user has both a linked team AND sufficient permissions
found: OrganizationMember | None = None
for organization_membership in organization_memberships:
if is_team_linked_to_channel(organization_membership.organization, slack_request):
found = organization_membership
if organization_membership.organization_id in linked_org_ids:
if (
is_valid_role(organization_membership)
or organization_membership.id in team_admin_member_ids
):
found = organization_membership
break

if not found:
return self.reply(slack_request, TEAM_NOT_LINKED_MESSAGE)

if not is_valid_role(found) and not is_team_admin(found):
return self.reply(slack_request, INSUFFICIENT_ROLE_MESSAGE)

if not slack_request.user_id:
Expand Down
Loading