Skip to content
Draft
Show file tree
Hide file tree
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
16 changes: 16 additions & 0 deletions src/sentry/integrations/api/serializers/models/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
from sentry.api.serializers import Serializer, register, serialize
from sentry.constants import ObjectStatus
from sentry.integrations.base import IntegrationProvider
from sentry.integrations.constants import SlackScope
from sentry.integrations.models.integration import Integration
from sentry.integrations.models.organization_integration import OrganizationIntegration
from sentry.integrations.services.integration import (
RpcIntegration,
RpcOrganizationIntegration,
integration_service,
)
from sentry.integrations.utils.github_permissions import get_missing_github_app_permissions
from sentry.shared_integrations.exceptions import ApiError
from sentry.users.models.user import User
from sentry.users.services.user import RpcUser
Expand All @@ -30,6 +32,7 @@ class OrganizationIntegrationResponse(TypedDict):
domainName: str | None
accountType: str | None
scopes: list[str] | None
outOfDate: bool | None
status: str
provider: Any
configOrganization: Any
Expand Down Expand Up @@ -70,6 +73,7 @@ class IntegrationSerializerResponse(TypedDict):
domainName: str | None
accountType: str | None
scopes: list[str] | None
outOfDate: bool | None
status: str
provider: IntegrationProviderInfo

Expand All @@ -84,13 +88,25 @@ def serialize(
**kwargs: Any,
) -> IntegrationSerializerResponse:
provider = obj.get_provider()

out_of_date = None

match provider.key:
case "github":
out_of_date = bool(get_missing_github_app_permissions(obj.metadata))
case "slack":
out_of_date = SlackScope.APP_MENTIONS_READ not in (
obj.metadata.get("scopes") or []
)

return {
"id": str(obj.id),
"name": obj.name,
"icon": obj.metadata.get("icon"),
"domainName": obj.metadata.get("domain_name"),
"accountType": obj.metadata.get("account_type"),
"scopes": obj.metadata.get("scopes"),
"outOfDate": out_of_date,
"status": obj.get_status_display(),
"provider": serialize_provider(provider),
}
Expand Down
21 changes: 21 additions & 0 deletions src/sentry/integrations/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from enum import StrEnum


class SlackScope(StrEnum):
"""
OAuth scopes for the Slack integration.

Keep this outside ``sentry.integrations.slack`` so import-sensitive code
(like API serializers during app startup) can reference Slack protocol
constants without executing the heavy Slack package ``__init__`` and
triggering circular imports.
"""

CHANNELS_HISTORY = "channels:history"
"""Allows the bot to read message history in channels."""
GROUPS_HISTORY = "groups:history"
"""Allows the bot to read message history in private groups."""
APP_MENTIONS_READ = "app_mentions:read"
"""Allows the bot to read mentions in app messages."""
ASSISTANT_WRITE = "assistant:write"
"""Allows the bot to act as a Slack Agent."""
15 changes: 1 addition & 14 deletions src/sentry/integrations/slack/utils/constants.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
from enum import StrEnum
from sentry.integrations.constants import SlackScope # noqa: F401

SLACK_RATE_LIMITED_MESSAGE = "Requests to Slack exceeded the rate limit. Please try again later."


class SlackScope(StrEnum):
"""OAuth scopes for the Slack integration."""

CHANNELS_HISTORY = "channels:history"
"""Allows the bot to read message history in channels."""
GROUPS_HISTORY = "groups:history"
"""Allows the bot to read message history in private groups."""
APP_MENTIONS_READ = "app_mentions:read"
"""Allows the bot to read mentions in app messages."""
ASSISTANT_WRITE = "assistant:write"
"""Allows the bot to act as a Slack Agent."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from sentry.api.serializers import serialize
from sentry.integrations.utils.github_permissions import GITHUB_APP_REQUIRED_PERMISSIONS_OPTION
from sentry.testutils.cases import TestCase
from sentry.testutils.helpers.options import override_options


class IntegrationSerializerTest(TestCase):
def setUp(self) -> None:
super().setUp()
self.user = self.create_user()
self.organization = self.create_organization(owner=self.user)

def test_non_github_provider_has_null_out_of_date(self) -> None:
integration = self.create_provider_integration(
provider="slack",
external_id="TXXXX",
name="Team A",
metadata={"permissions": {"contents": "read"}},
)

result = serialize(integration, self.user)

assert result["outOfDate"] is None

@override_options({GITHUB_APP_REQUIRED_PERMISSIONS_OPTION: {"contents": "write"}})
def test_github_out_of_date_when_missing_permissions(self) -> None:
integration = self.create_provider_integration(
provider="github",
external_id="1",
name="octocat",
metadata={"permissions": {"contents": "read"}},
)

result = serialize(integration, self.user)

assert result["outOfDate"] is True

@override_options({GITHUB_APP_REQUIRED_PERMISSIONS_OPTION: {"contents": "write"}})
def test_github_not_out_of_date_when_permissions_satisfied(self) -> None:
integration = self.create_provider_integration(
provider="github",
external_id="2",
name="octocat",
metadata={"permissions": {"contents": "write"}},
)

result = serialize(integration, self.user)

assert result["outOfDate"] is False
Loading