Skip to content

Commit 0daa386

Browse files
authored
fix(hybridcloud) Don't use integration proxy for slack (#65861)
Slack doesn't use refresh tokens and thus doesn't need to go through the integration proxy as there are no refresh tokens to coordinate.
1 parent d29eec1 commit 0daa386

File tree

4 files changed

+60
-155
lines changed

4 files changed

+60
-155
lines changed

src/sentry/integrations/slack/client.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
from requests import PreparedRequest, Response
88

99
from sentry.constants import ObjectStatus
10-
from sentry.models.integrations.integration import Integration
11-
from sentry.services.hybrid_cloud.util import control_silo_function
10+
from sentry.db.postgres.transactions import in_test_hide_transaction_boundary
11+
from sentry.integrations.client import ApiClient
12+
from sentry.services.hybrid_cloud.integration import integration_service
1213
from sentry.shared_integrations.client import BaseApiResponse
13-
from sentry.shared_integrations.client.proxy import IntegrationProxyClient, infer_org_integration
1414
from sentry.shared_integrations.exceptions import ApiError
1515
from sentry.types.integrations import EXTERNAL_PROVIDERS, ExternalProviders
1616
from sentry.utils import json, metrics
@@ -19,7 +19,7 @@
1919
logger = logging.getLogger(__name__)
2020

2121

22-
class SlackClient(IntegrationProxyClient):
22+
class SlackClient(ApiClient):
2323
allow_redirects = False
2424
integration_name = "slack"
2525
base_url = "https://slack.com/api"
@@ -28,37 +28,34 @@ class SlackClient(IntegrationProxyClient):
2828
def __init__(
2929
self,
3030
integration_id: int | None = None,
31-
org_integration_id: int | None = None,
3231
verify_ssl: bool = True,
3332
logging_context: Mapping[str, Any] | None = None,
33+
org_integration_id: int | None = None, # deprecated but used by getsentry
3434
) -> None:
3535
self.integration_id = integration_id
36-
if not org_integration_id and integration_id is not None:
37-
org_integration_id = infer_org_integration(
38-
integration_id=self.integration_id, ctx_logger=logger
39-
)
4036

4137
super().__init__(
42-
org_integration_id=org_integration_id,
4338
verify_ssl=verify_ssl,
4439
integration_id=integration_id,
4540
logging_context=logging_context,
4641
)
4742

48-
@control_silo_function
4943
def authorize_request(self, prepared_request: PreparedRequest) -> PreparedRequest:
50-
integration = None
51-
base_qs = {
52-
"provider": EXTERNAL_PROVIDERS[ExternalProviders.SLACK],
53-
"status": ObjectStatus.ACTIVE,
54-
}
55-
if self.integration_id:
56-
integration = Integration.objects.filter(id=self.integration_id, **base_qs).first()
57-
elif self.org_integration_id:
58-
integration = Integration.objects.filter(
59-
organizationintegration__id=self.org_integration_id, **base_qs
60-
).first()
44+
if "Authorization" in prepared_request.headers or not self.integration_id:
45+
return prepared_request
6146

47+
# TODO(hybridcloud) Pass integration into SlackClient.__init__() so
48+
# we don't have to workaround watermarks here.
49+
# In order to send requests, SlackClient needs to fetch the integration
50+
# to get access tokens which trips up rpc method/transaction
51+
# boundary detection. Those boundaries are not relevant because
52+
# this is a read operation.
53+
with in_test_hide_transaction_boundary():
54+
integration = integration_service.get_integration(
55+
integration_id=self.integration_id,
56+
provider=EXTERNAL_PROVIDERS[ExternalProviders.SLACK],
57+
status=ObjectStatus.ACTIVE,
58+
)
6259
if not integration:
6360
logger.info("no_integration", extra={"path_url": prepared_request.path_url})
6461
return prepared_request
@@ -68,6 +65,12 @@ def authorize_request(self, prepared_request: PreparedRequest) -> PreparedReques
6865
prepared_request.headers["Authorization"] = f"Bearer {token}"
6966
return prepared_request
7067

68+
def finalize_request(self, prepared_request: PreparedRequest) -> PreparedRequest:
69+
"""
70+
Add request authorization headers
71+
"""
72+
return self.authorize_request(prepared_request=prepared_request)
73+
7174
def is_response_fatal(self, response: Response) -> bool:
7275
try:
7376
resp_json = response.json()

src/sentry/integrations/slack/integration.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,7 @@
7171

7272
class SlackIntegration(SlackNotifyBasicMixin, IntegrationInstallation):
7373
def get_client(self) -> SlackClient:
74-
if not self.org_integration:
75-
raise IntegrationError("Organization Integration does not exist")
76-
return SlackClient(org_integration_id=self.org_integration.id, integration_id=self.model.id)
74+
return SlackClient(integration_id=self.model.id)
7775

7876
def get_config_data(self) -> Mapping[str, str]:
7977
metadata_ = self.model.metadata
Lines changed: 4 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import re
22

33
import responses
4-
from django.test import override_settings
54
from responses import matchers
65

76
from sentry.integrations.slack.client import SlackClient
87
from sentry.silo.base import SiloMode
9-
from sentry.silo.util import PROXY_BASE_PATH, PROXY_OI_HEADER, PROXY_PATH, PROXY_SIGNATURE_HEADER
108
from sentry.testutils.cases import TestCase
119
from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
12-
from tests.sentry.integrations.test_helpers import add_control_silo_proxy_response
1310

1411

1512
@region_silo_test
@@ -39,14 +36,6 @@ def _add_response(*, json, match):
3936
match=match,
4037
)
4138

42-
add_control_silo_proxy_response(
43-
method=responses.POST,
44-
path="chat.postMessage",
45-
status=200,
46-
json=json,
47-
additional_matchers=match,
48-
)
49-
5039
_add_response(
5140
json=self.mock_user_access_token_response,
5241
match=[
@@ -64,47 +53,6 @@ def _add_response(*, json, match):
6453
match=[matchers.header_matcher({})],
6554
)
6655

67-
def assert_proxy_request(self, request, is_proxy=True):
68-
assert (PROXY_BASE_PATH in request.url) == is_proxy
69-
assert (PROXY_OI_HEADER in request.headers) == is_proxy
70-
assert (PROXY_SIGNATURE_HEADER in request.headers) == is_proxy
71-
if is_proxy:
72-
assert request.headers[PROXY_OI_HEADER] is not None
73-
74-
@responses.activate
75-
def test_integration_proxy_is_active(self):
76-
class SlackProxyTestClient(SlackClient):
77-
_use_proxy_url_for_tests = True
78-
79-
with override_settings(SILO_MODE=SiloMode.MONOLITH):
80-
client = SlackProxyTestClient(integration_id=self.integration.id)
81-
client.post("/chat.postMessage", data=self.payload)
82-
request = responses.calls[0].request
83-
84-
assert "/chat.postMessage" in request.url
85-
assert client.base_url in request.url
86-
self.assert_proxy_request(request, is_proxy=False)
87-
88-
responses.calls.reset()
89-
with override_settings(SILO_MODE=SiloMode.CONTROL):
90-
client = SlackProxyTestClient(integration_id=self.integration.id)
91-
client.post("/chat.postMessage", data=self.payload)
92-
request = responses.calls[0].request
93-
94-
assert "/chat.postMessage" in request.url
95-
assert client.base_url in request.url
96-
self.assert_proxy_request(request, is_proxy=False)
97-
98-
responses.calls.reset()
99-
with override_settings(SILO_MODE=SiloMode.REGION):
100-
client = SlackProxyTestClient(integration_id=self.integration.id)
101-
client.post("/chat.postMessage", data=self.payload)
102-
request = responses.calls[0].request
103-
104-
assert request.headers[PROXY_PATH] == "chat.postMessage"
105-
assert client.base_url not in request.url
106-
self.assert_proxy_request(request, is_proxy=True)
107-
10856
@responses.activate
10957
def test_authorize_with_no_id_noop(self):
11058
client = SlackClient()
@@ -121,13 +69,6 @@ def test_authorize_manually(self):
12169
)
12270
assert response == self.mock_user_access_token_response
12371

124-
@responses.activate
125-
@assume_test_silo_mode(SiloMode.CONTROL)
126-
def test_authorize_with_org_integration_id(self):
127-
client = SlackClient(org_integration_id=self.organization_integration.id)
128-
response = client.post("/chat.postMessage", data=self.payload)
129-
assert response == self.mock_access_token_response
130-
13172
@responses.activate
13273
@assume_test_silo_mode(SiloMode.CONTROL)
13374
def test_authorize_with_integration_id(self):
@@ -139,22 +80,18 @@ def test_authorize_with_integration_id(self):
13980
@assume_test_silo_mode(SiloMode.CONTROL)
14081
def test_authorize_user_access_token(self):
14182
self.integration.update(metadata={"user_access_token": self.user_access_token})
142-
client = SlackClient(org_integration_id=self.organization_integration.id)
83+
client = SlackClient(integration_id=self.integration.id)
14384
response = client.post("/chat.postMessage", data=self.payload)
14485
assert response == self.mock_user_access_token_response
14586

14687
@responses.activate
14788
def test_no_authorization_in_region_mode(self):
148-
client = SlackClient(org_integration_id=self.organization_integration.id)
149-
response = client.post("/chat.postMessage", data=self.payload)
150-
assert response == self.mock_not_authed_response
151-
15289
client = SlackClient(integration_id=self.integration.id)
15390
response = client.post("/chat.postMessage", data=self.payload)
154-
assert response == self.mock_not_authed_response
91+
assert response == self.mock_access_token_response
15592

15693
with assume_test_silo_mode(SiloMode.CONTROL):
15794
self.integration.update(metadata={"user_access_token": self.user_access_token})
158-
client = SlackClient(org_integration_id=self.organization_integration.id)
95+
client = SlackClient(integration_id=self.integration.id)
15996
response = client.post("/chat.postMessage", data=self.payload)
160-
assert response == self.mock_not_authed_response
97+
assert response == self.mock_user_access_token_response

tests/sentry/integrations/slack/webhooks/events/test_message_im.py

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

33
from sentry.models.identity import Identity, IdentityStatus
44
from sentry.silo import SiloMode
5-
from sentry.silo.util import PROXY_BASE_URL_HEADER, PROXY_OI_HEADER, PROXY_SIGNATURE_HEADER
65
from sentry.testutils.cases import IntegratedApiTestCase
76
from sentry.testutils.helpers import get_response_text
87
from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
@@ -66,34 +65,22 @@ def test_identifying_channel_correctly(self):
6665
data = json.loads(request.body)
6766
assert data.get("channel") == event_data["channel"]
6867

69-
def _check_proxying(self) -> None:
70-
assert len(responses.calls) == 1
71-
request = responses.calls[0].request
72-
assert request.headers[PROXY_OI_HEADER] == str(self.organization_integration.id)
73-
assert request.headers[PROXY_BASE_URL_HEADER] == "https://slack.com/api"
74-
assert PROXY_SIGNATURE_HEADER in request.headers
75-
7668
@responses.activate
7769
def test_user_message_im_notification_platform(self):
7870
responses.add(responses.POST, "https://slack.com/api/chat.postMessage", json={"ok": True})
7971
resp = self.post_webhook(event_data=json.loads(MESSAGE_IM_EVENT))
8072
assert resp.status_code == 200, resp.content
8173

82-
if self.should_call_api_without_proxying():
83-
assert len(responses.calls) == 1
84-
request = responses.calls[0].request
85-
assert (
86-
request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
87-
)
88-
data = json.loads(request.body)
89-
heading, contents = self.get_block_section_text(data)
90-
assert heading == "Unknown command: `helloo`"
91-
assert (
92-
contents
93-
== "Here are the commands you can use. Commands not working? Re-install the app!"
94-
)
95-
else:
96-
self._check_proxying()
74+
assert len(responses.calls) == 1
75+
request = responses.calls[0].request
76+
assert request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
77+
data = json.loads(request.body)
78+
heading, contents = self.get_block_section_text(data)
79+
assert heading == "Unknown command: `helloo`"
80+
assert (
81+
contents
82+
== "Here are the commands you can use. Commands not working? Re-install the app!"
83+
)
9784

9885
@responses.activate
9986
def test_user_message_link(self):
@@ -107,16 +94,11 @@ def test_user_message_link(self):
10794
resp = self.post_webhook(event_data=json.loads(MESSAGE_IM_EVENT_LINK))
10895
assert resp.status_code == 200, resp.content
10996

110-
if self.should_call_api_without_proxying():
111-
assert len(responses.calls) == 1
112-
request = responses.calls[0].request
113-
assert (
114-
request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
115-
)
116-
data = json.loads(request.body)
117-
assert "Link your Slack identity" in get_response_text(data)
118-
else:
119-
self._check_proxying()
97+
assert len(responses.calls) == 1
98+
request = responses.calls[0].request
99+
assert request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
100+
data = json.loads(request.body)
101+
assert "Link your Slack identity" in get_response_text(data)
120102

121103
@responses.activate
122104
def test_user_message_already_linked(self):
@@ -138,16 +120,11 @@ def test_user_message_already_linked(self):
138120
resp = self.post_webhook(event_data=json.loads(MESSAGE_IM_EVENT_LINK))
139121
assert resp.status_code == 200, resp.content
140122

141-
if self.should_call_api_without_proxying():
142-
assert len(responses.calls) == 1
143-
request = responses.calls[0].request
144-
assert (
145-
request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
146-
)
147-
data = json.loads(request.body)
148-
assert "You are already linked" in get_response_text(data)
149-
else:
150-
self._check_proxying()
123+
assert len(responses.calls) == 1
124+
request = responses.calls[0].request
125+
assert request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
126+
data = json.loads(request.body)
127+
assert "You are already linked" in get_response_text(data)
151128

152129
@responses.activate
153130
def test_user_message_unlink(self):
@@ -168,16 +145,11 @@ def test_user_message_unlink(self):
168145
resp = self.post_webhook(event_data=json.loads(MESSAGE_IM_EVENT_UNLINK))
169146
assert resp.status_code == 200, resp.content
170147

171-
if self.should_call_api_without_proxying():
172-
assert len(responses.calls) == 1
173-
request = responses.calls[0].request
174-
assert (
175-
request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
176-
)
177-
data = json.loads(request.body)
178-
assert "Click here to unlink your identity" in get_response_text(data)
179-
else:
180-
self._check_proxying()
148+
assert len(responses.calls) == 1
149+
request = responses.calls[0].request
150+
assert request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
151+
data = json.loads(request.body)
152+
assert "Click here to unlink your identity" in get_response_text(data)
181153

182154
@responses.activate
183155
def test_user_message_already_unlinked(self):
@@ -192,16 +164,11 @@ def test_user_message_already_unlinked(self):
192164
resp = self.post_webhook(event_data=json.loads(MESSAGE_IM_EVENT_UNLINK))
193165
assert resp.status_code == 200, resp.content
194166

195-
if self.should_call_api_without_proxying():
196-
assert len(responses.calls) == 1
197-
request = responses.calls[0].request
198-
assert (
199-
request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
200-
)
201-
data = json.loads(request.body)
202-
assert "You do not have a linked identity to unlink" in get_response_text(data)
203-
else:
204-
self._check_proxying()
167+
assert len(responses.calls) == 1
168+
request = responses.calls[0].request
169+
assert request.headers["Authorization"] == "Bearer xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx"
170+
data = json.loads(request.body)
171+
assert "You do not have a linked identity to unlink" in get_response_text(data)
205172

206173
def test_bot_message_im(self):
207174
resp = self.post_webhook(event_data=json.loads(MESSAGE_IM_BOT_EVENT))

0 commit comments

Comments
 (0)