diff --git a/pyproject.toml b/pyproject.toml index 3481d666bacb..a0af311b0cf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,7 @@ dependencies = [ "sentry-redis-tools>=0.5.0", "sentry-viewercontext>=0.1.0", "sentry-relay>=0.10.1", - "sentry-scm>=1.9.0", + "sentry-scm>=2.0.0", "sentry-sdk[http2]>=2.72.0", "sentry-usage-accountant>=0.0.10", "simplejson>=3.17.6", diff --git a/src/sentry/scm/endpoints/scm_rpc.py b/src/sentry/scm/endpoints/scm_rpc.py index b61b156131e4..b3f8d21e33c2 100644 --- a/src/sentry/scm/endpoints/scm_rpc.py +++ b/src/sentry/scm/endpoints/scm_rpc.py @@ -1,6 +1,7 @@ from django.conf import settings from django.http import HttpResponse, StreamingHttpResponse from rest_framework.request import Request +from scm import Secret from scm.rpc.server import RpcServer from sentry_sdk import traces @@ -13,7 +14,7 @@ def make_server(): fetchers = make_fetchers() return RpcServer( - secrets=settings.SCM_RPC_SHARED_SECRET or [], + secrets=[Secret(secret) for secret in settings.SCM_RPC_SHARED_SECRET or []], fetch_repository=fetchers.fetch_repository, fetch_provider=fetchers.fetch_provider, record_count=record_count_metric, diff --git a/tests/sentry/scm/endpoints/test_scm_rpc.py b/tests/sentry/scm/endpoints/test_scm_rpc.py index 48cd85768c1c..2d64e2ecaf97 100644 --- a/tests/sentry/scm/endpoints/test_scm_rpc.py +++ b/tests/sentry/scm/endpoints/test_scm_rpc.py @@ -6,6 +6,7 @@ import responses from django.test import override_settings from django.urls import reverse +from scm import Secret from scm.actions import create_branch from scm.manager import SourceCodeManager from scm.rpc.helpers import sign_get, sign_post @@ -56,7 +57,8 @@ def _convert(self, django_response): def get(self, url, headers=None): return self._convert(self._client.get(url, headers=headers)) - def post(self, url, data=None, headers=None): + def post(self, url, data=None, headers=None, stream=False): + # `stream` is accepted to match the Session protocol; _convert reads the whole body either way. h = dict(headers) if headers else {} content_type = h.pop("Content-Type", "application/octet-stream") return self._convert( @@ -89,11 +91,11 @@ def setUp(self) -> None: self.organization.id, self.repo.id, base_url="", - signing_secret="a-long-value-that-is-hard-to-guess", + signing_secret=Secret("a-long-value-that-is-hard-to-guess"), ) self.default_headers = { - "Authorization": f"rpcsignature {sign_get('a-long-value-that-is-hard-to-guess', self.organization.id, self.repo.id)}", + "Authorization": f"rpcsignature {sign_get(Secret('a-long-value-that-is-hard-to-guess'), self.organization.id, self.repo.id)}", "X-Organization-Id": str(self.organization.id), "X-Repository-Id": str(self.repo.id), } @@ -136,7 +138,7 @@ def test_get_fetches_the_integration_once_per_request(self): integration_id=integration.id, ) headers = { - "Authorization": f"rpcsignature {sign_get('a-long-value-that-is-hard-to-guess', self.organization.id, repo.id)}", + "Authorization": f"rpcsignature {sign_get(Secret('a-long-value-that-is-hard-to-guess'), self.organization.id, repo.id)}", "X-Organization-Id": str(self.organization.id), "X-Repository-Id": str(repo.id), } @@ -169,7 +171,7 @@ def test_get_missing_auth(self): def test_get_invalid_secret(self): def sig(secret): - return f"rpcsignature {sign_get(secret, self.organization.id, self.repo.id)}" + return f"rpcsignature {sign_get(Secret(secret), self.organization.id, self.repo.id)}" # Succeeds with correct secret. headers = { @@ -201,7 +203,7 @@ def test_post_invalid_secret(self): data=b"test", headers={ **self.default_headers, - "Authorization": f"rpcsignature {sign_post('s', b'test')}", + "Authorization": f"rpcsignature {sign_post(Secret('s'), b'test')}", }, ), 401, diff --git a/uv.lock b/uv.lock index 07b7c8372d4a..dd28619475ac 100644 --- a/uv.lock +++ b/uv.lock @@ -2439,7 +2439,7 @@ requires-dist = [ { name = "sentry-protos", specifier = ">=0.91.0" }, { name = "sentry-redis-tools", specifier = ">=0.5.0" }, { name = "sentry-relay", specifier = ">=0.10.1" }, - { name = "sentry-scm", specifier = ">=1.9.0" }, + { name = "sentry-scm", specifier = ">=2.0.0" }, { name = "sentry-sdk", extras = ["http2"], specifier = ">=2.72.0" }, { name = "sentry-usage-accountant", specifier = ">=0.0.10" }, { name = "sentry-viewercontext", specifier = ">=0.1.0" }, @@ -2654,14 +2654,16 @@ wheels = [ [[package]] name = "sentry-scm" -version = "1.9.0" +version = "2.0.0" source = { registry = "https://pypi.devinfra.sentry.io/simple" } dependencies = [ { name = "msgspec" }, + { name = "pyjwt" }, { name = "requests" }, + { name = "urllib3" }, ] wheels = [ - { url = "https://pypi.devinfra.sentry.io/wheels/sentry_scm-1.9.0-py3-none-any.whl", hash = "sha256:5610e6046adaa42ba089416db1d37dbf31e50880c6e44fc66b058d48facd41cf" }, + { url = "https://pypi.devinfra.sentry.io/wheels/sentry_scm-2.0.0-py3-none-any.whl", hash = "sha256:2a12655efd36dab251e0a04dee894f2552adbed0bfd72731f65d493b68794d14" }, ] [[package]]