Skip to content

Commit b0b411c

Browse files
committed
record fail for expired signature, add test cases
1 parent 0d2da12 commit b0b411c

File tree

2 files changed

+98
-5
lines changed

2 files changed

+98
-5
lines changed

src/sentry/integrations/jira/webhooks/installed.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,16 +67,26 @@ def post(self, request: Request, *args, **kwargs) -> Response:
6767
return self.respond(
6868
{"detail": "Invalid key id"}, status=status.HTTP_400_BAD_REQUEST
6969
)
70-
except (InvalidSignatureError, ExpiredSignatureError):
71-
lifecycle.record_halt(halt_reason="JWT contained invalid or expired signature")
70+
except ExpiredSignatureError as e:
71+
lifecycle.record_failure(e)
7272
return self.respond(
73-
{"detail": "Invalid or expired signature"}, status=status.HTTP_400_BAD_REQUEST
73+
{"detail": "Expired signature"}, status=status.HTTP_400_BAD_REQUEST
74+
)
75+
except InvalidSignatureError:
76+
lifecycle.record_halt(halt_reason="JWT contained invalid signature")
77+
return self.respond(
78+
{"detail": "Invalid signature"}, status=status.HTTP_400_BAD_REQUEST
7479
)
7580
except DecodeError:
7681
lifecycle.record_halt(halt_reason="Could not decode JWT token")
7782
return self.respond(
7883
{"detail": "Could not decode JWT token"}, status=status.HTTP_400_BAD_REQUEST
7984
)
85+
except Exception:
86+
lifecycle.record_halt("JWT authentication failed")
87+
return self.respond(
88+
{"detail": "JWT authentication failed"}, status=status.HTTP_400_BAD_REQUEST
89+
)
8090

8191
data = JiraIntegrationProvider().build_integration(state)
8292
integration = ensure_integration(self.provider, data)

tests/sentry/integrations/jira/test_installed.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import jwt
88
import responses
9+
from jwt import DecodeError, ExpiredSignatureError, InvalidSignatureError
910
from rest_framework import status
1011

1112
from sentry.constants import ObjectStatus
@@ -16,7 +17,11 @@
1617
AtlassianConnectValidationError,
1718
get_query_hash,
1819
)
19-
from sentry.testutils.asserts import assert_count_of_metric, assert_halt_metric
20+
from sentry.testutils.asserts import (
21+
assert_count_of_metric,
22+
assert_failure_metric,
23+
assert_halt_metric,
24+
)
2025
from sentry.testutils.cases import APITestCase
2126
from sentry.testutils.silo import control_silo_test
2227
from sentry.utils.http import absolute_uri
@@ -104,7 +109,85 @@ def test_no_claims(self, mock_authenticate_asymmetric_jwt: MagicMock) -> None:
104109
self.get_error_response(
105110
**self.body(),
106111
extra_headers=dict(HTTP_AUTHORIZATION="JWT " + self.jwt_token_cdn()),
107-
status_code=status.HTTP_409_CONFLICT,
112+
status_code=status.HTTP_400_BAD_REQUEST,
113+
)
114+
115+
@patch(
116+
"sentry.integrations.jira.webhooks.installed.authenticate_asymmetric_jwt",
117+
side_effect=ExpiredSignatureError(),
118+
)
119+
@patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
120+
@responses.activate
121+
def test_expired_signature(
122+
self, mock_record_event: MagicMock, mock_authenticate_asymmetric_jwt: MagicMock
123+
) -> None:
124+
self.add_response()
125+
126+
self.get_error_response(
127+
**self.body(),
128+
extra_headers=dict(HTTP_AUTHORIZATION="JWT " + self.jwt_token_cdn()),
129+
status_code=status.HTTP_400_BAD_REQUEST,
130+
)
131+
# SLO metric asserts
132+
# ENSURE_CONTROL_SILO (success) -> VERIFY_INSTALLATION (failure) -> GET_CONTROL_RESPONSE (success)
133+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.STARTED, 3)
134+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.FAILURE, 1)
135+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.SUCCESS, 2)
136+
assert_failure_metric(
137+
mock_record_event,
138+
ExpiredSignatureError(),
139+
)
140+
141+
@patch(
142+
"sentry.integrations.jira.webhooks.installed.authenticate_asymmetric_jwt",
143+
side_effect=InvalidSignatureError(),
144+
)
145+
@patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
146+
@responses.activate
147+
def test_invalid_signature(
148+
self, mock_record_event: MagicMock, mock_authenticate_asymmetric_jwt: MagicMock
149+
) -> None:
150+
self.add_response()
151+
152+
self.get_error_response(
153+
**self.body(),
154+
extra_headers=dict(HTTP_AUTHORIZATION="JWT " + self.jwt_token_cdn()),
155+
status_code=status.HTTP_400_BAD_REQUEST,
156+
)
157+
# SLO metric asserts
158+
# ENSURE_CONTROL_SILO (success) -> VERIFY_INSTALLATION (halt) -> GET_CONTROL_RESPONSE (success)
159+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.STARTED, 3)
160+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.HALTED, 1)
161+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.SUCCESS, 2)
162+
assert_halt_metric(
163+
mock_record_event,
164+
"JWT contained invalid signature",
165+
)
166+
167+
@patch(
168+
"sentry.integrations.jira.webhooks.installed.authenticate_asymmetric_jwt",
169+
side_effect=DecodeError(),
170+
)
171+
@patch("sentry.integrations.utils.metrics.EventLifecycle.record_event")
172+
@responses.activate
173+
def test_decode_error(
174+
self, mock_record_event: MagicMock, mock_authenticate_asymmetric_jwt: MagicMock
175+
) -> None:
176+
self.add_response()
177+
178+
self.get_error_response(
179+
**self.body(),
180+
extra_headers=dict(HTTP_AUTHORIZATION="JWT " + self.jwt_token_cdn()),
181+
status_code=status.HTTP_400_BAD_REQUEST,
182+
)
183+
# SLO metric asserts
184+
# ENSURE_CONTROL_SILO (success) -> VERIFY_INSTALLATION (halt) -> GET_CONTROL_RESPONSE (success)
185+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.STARTED, 3)
186+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.HALTED, 1)
187+
assert_count_of_metric(mock_record_event, EventLifecycleOutcome.SUCCESS, 2)
188+
assert_halt_metric(
189+
mock_record_event,
190+
"Could not decode JWT token",
108191
)
109192

110193
@patch("sentry_sdk.set_tag")

0 commit comments

Comments
 (0)