Skip to content

Commit 81e7059

Browse files
authored
fix(integrations): Disallow modifying a repo's integration (#111739)
1 parent 2a2f917 commit 81e7059

File tree

2 files changed

+22
-49
lines changed

2 files changed

+22
-49
lines changed

src/sentry/integrations/api/endpoints/organization_repository_details.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,10 @@
1212
from sentry.api.base import cell_silo_endpoint
1313
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationIntegrationsPermission
1414
from sentry.api.exceptions import ResourceDoesNotExist
15-
from sentry.api.fields.empty_integer import EmptyIntegerField
1615
from sentry.api.serializers import serialize
1716
from sentry.api.serializers.models.repository import RepositorySerializer as RepositoryApiSerializer
1817
from sentry.constants import ObjectStatus
1918
from sentry.deletions.models.scheduleddeletion import CellScheduledDeletion
20-
from sentry.hybridcloud.rpc import coerce_id_from
21-
from sentry.integrations.services.integration import integration_service
2219
from sentry.models.commit import Commit
2320
from sentry.models.organization import Organization
2421
from sentry.models.repository import Repository
@@ -37,7 +34,6 @@ class RepositorySerializer(serializers.Serializer):
3734
)
3835
name = serializers.CharField(required=False)
3936
url = serializers.URLField(required=False, allow_blank=True)
40-
integrationId = EmptyIntegerField(required=False, allow_null=True)
4137

4238

4339
@cell_silo_endpoint
@@ -71,6 +67,11 @@ def put(self, request: Request, organization: Organization, repo_id) -> Response
7167
if repo.status == ObjectStatus.DELETION_IN_PROGRESS:
7268
return Response(status=400)
7369

70+
if "integrationId" in request.data:
71+
return Response(
72+
{"detail": "Changing the repository provider is not allowed"}, status=400
73+
)
74+
7475
serializer = RepositorySerializer(data=request.data, partial=True)
7576

7677
if not serializer.is_valid():
@@ -85,18 +86,6 @@ def put(self, request: Request, organization: Organization, repo_id) -> Response
8586
update_kwargs["status"] = ObjectStatus.HIDDEN
8687
else:
8788
raise NotImplementedError
88-
if result.get("integrationId"):
89-
integration = integration_service.get_integration(
90-
integration_id=result["integrationId"],
91-
organization_id=coerce_id_from(organization),
92-
status=ObjectStatus.ACTIVE,
93-
)
94-
if integration is None:
95-
return Response({"detail": "Invalid integration id"}, status=400)
96-
97-
update_kwargs["integration_id"] = integration.id
98-
update_kwargs["provider"] = f"integrations:{integration.provider}"
99-
10089
if update_kwargs:
10190
old_status = repo.status
10291
with transaction.atomic(router.db_for_write(Repository)):

tests/sentry/integrations/api/endpoints/test_organization_repository_details.py

Lines changed: 17 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@
99
from sentry.models.commit import Commit
1010
from sentry.models.options.organization_option import OrganizationOption
1111
from sentry.models.repository import Repository
12-
from sentry.silo.base import SiloMode
1312
from sentry.testutils.cases import APITestCase
14-
from sentry.testutils.silo import assume_test_silo_mode
1513

1614

1715
class OrganizationRepositoryGetTest(APITestCase):
@@ -211,35 +209,29 @@ def test_put(self) -> None:
211209
self.login_as(user=self.user)
212210

213211
org = self.create_organization(owner=self.user, name="baz")
214-
integration = self.create_integration(
215-
organization=org, provider="example", name="Example", external_id="example:1"
216-
)
217212

218213
repo = Repository.objects.create(
219214
name="example", organization_id=org.id, status=ObjectStatus.DISABLED
220215
)
221216

222217
url = reverse("sentry-api-0-organization-repository-details", args=[org.slug, repo.id])
223-
response = self.client.put(url, data={"status": "visible", "integrationId": integration.id})
218+
response = self.client.put(url, data={"status": "visible"})
224219

225220
assert response.status_code == 200
226221

227222
repo = Repository.objects.get(id=repo.id)
228223
assert repo.status == ObjectStatus.ACTIVE
229-
assert repo.integration_id == integration.id
230224

231225
def test_put_cancel_deletion(self) -> None:
232226
self.login_as(user=self.user)
233227

234228
org = self.create_organization(owner=self.user, name="baz")
235-
integration = self.create_integration(
236-
organization=org, provider="example", name="Example", external_id="example:1"
237-
)
238229

239230
repo = Repository.objects.create(
240231
name="uuid-name",
241232
external_id="uuid-external-id",
242233
organization_id=org.id,
234+
provider="integrations:example",
243235
status=ObjectStatus.PENDING_DELETION,
244236
config={"pending_deletion_name": "example-name"},
245237
)
@@ -256,13 +248,12 @@ def test_put_cancel_deletion(self) -> None:
256248
)
257249

258250
url = reverse("sentry-api-0-organization-repository-details", args=[org.slug, repo.id])
259-
response = self.client.put(url, data={"status": "visible", "integrationId": integration.id})
251+
response = self.client.put(url, data={"status": "visible"})
260252

261253
assert response.status_code == 200
262254

263255
repo = Repository.objects.get(id=repo.id)
264256
assert repo.status == ObjectStatus.ACTIVE
265-
assert repo.integration_id == integration.id
266257
assert repo.provider == "integrations:example"
267258
assert repo.name == "example-name"
268259
assert repo.external_id == "example-external-id"
@@ -332,37 +323,30 @@ def test_put_hide_repo_with_commits(self, mock_cleanup_task: MagicMock) -> None:
332323
}
333324
)
334325

335-
def test_put_bad_integration_org(self) -> None:
326+
def test_put_rejects_integration_id(self) -> None:
336327
self.login_as(user=self.user)
337328

338329
org = self.create_organization(owner=self.user, name="baz")
339-
with assume_test_silo_mode(SiloMode.CONTROL):
340-
integration = self.create_provider_integration(provider="example", name="example")
330+
integration = self.create_integration(
331+
organization=org, provider="example", name="Example", external_id="example:1"
332+
)
341333

342-
repo = Repository.objects.create(name="example", organization_id=org.id)
334+
repo = Repository.objects.create(
335+
name="example",
336+
organization_id=org.id,
337+
provider="integrations:github",
338+
integration_id=None,
339+
)
343340

344341
url = reverse("sentry-api-0-organization-repository-details", args=[org.slug, repo.id])
345-
# integration isn't linked to org
346342
response = self.client.put(url, data={"status": "visible", "integrationId": integration.id})
347343

348344
assert response.status_code == 400
349-
assert response.data["detail"] == "Invalid integration id"
350-
assert Repository.objects.get(id=repo.id).name == "example"
351-
352-
def test_put_bad_integration_id(self) -> None:
353-
self.login_as(user=self.user)
354-
355-
org = self.create_organization(owner=self.user, name="baz")
345+
assert response.data["detail"] == "Changing the repository provider is not allowed"
356346

357-
repo = Repository.objects.create(name="example", organization_id=org.id)
358-
359-
url = reverse("sentry-api-0-organization-repository-details", args=[org.slug, repo.id])
360-
# integration isn't linked to org
361-
response = self.client.put(url, data={"status": "visible", "integrationId": "notanumber"})
362-
363-
assert response.status_code == 400
364-
assert response.data == {"integrationId": ["A valid integer is required."]}
365-
assert Repository.objects.get(id=repo.id).name == "example"
347+
repo = Repository.objects.get(id=repo.id)
348+
assert repo.provider == "integrations:github"
349+
assert repo.integration_id is None
366350

367351
@patch("sentry.tasks.seer.cleanup.cleanup_seer_repository_preferences.apply_async")
368352
def test_put_hide_repo_triggers_cleanup(self, mock_cleanup_task: MagicMock) -> None:

0 commit comments

Comments
 (0)