class JiraConfigureViewErrorsTest(JiraConfigureViewTestCase): @patch( "sentry.integrations.jira.configure.get_integration_from_request", side_effect=AtlassianConnectValidationError(), ) def test_atlassian_connect_validation_error_get( self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert PERMISSIONS_WARNING in response.content @patch( "sentry.integrations.jira.configure.get_integration_from_request", side_effect=ExpiredSignatureError(), ) def test_expired_signature_error_get(self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert REFRESH_REQUIRED in response.content @patch("sentry.integrations.jira.configure.get_integration_from_request") def test_user_not_logged_in_get(self, mock_get_integration_from_request): mock_get_integration_from_request.return_value = self.installation.model response = self.client.get(self.path) assert response.status_code == 200 assert LOGIN_REQUIRED in response.content assert absolute_uri( reverse("sentry-login")).encode("utf-8") in response.content @patch( "sentry.integrations.jira.configure.get_integration_from_request", side_effect=AtlassianConnectValidationError(), ) def test_atlassian_connect_validation_error_post( self, mock_get_integration_from_request): response = self.client.post(self.path) assert response.status_code == 200 assert PERMISSIONS_WARNING in response.content @patch( "sentry.integrations.jira.configure.get_integration_from_request", side_effect=ExpiredSignatureError(), ) def test_expired_signature_error_post(self, mock_get_integration_from_request): response = self.client.post(self.path) assert response.status_code == 200 assert REFRESH_REQUIRED in response.content @patch("sentry.integrations.jira.configure.get_integration_from_request") def test_user_not_logged_in_post(self, mock_get_integration_from_request): mock_get_integration_from_request.return_value = self.installation.model response = self.client.post(self.path) assert response.status_code == 200 assert LOGIN_REQUIRED in response.content assert absolute_uri( reverse("sentry-login")).encode("utf-8") in response.content
def refresh_token(access_token): validation_result, open_token = verify_token(access_token, verify=False) current_exp_date = datetime.utcfromtimestamp(open_token["exp"]) current_utc_date = datetime.utcnow() diff_in_seconds = (current_exp_date - current_utc_date).total_seconds() if 0 < diff_in_seconds <= app.config["JWT_EXPIRATION_IN_SECONDS"]: new_expiration = datetime.utcnow() + timedelta(seconds=app.config["JWT_EXPIRATION_IN_SECONDS"]) open_token["exp"] = new_expiration return jwt.encode(open_token, app.config["SECRET_KEY"]) else: raise ExpiredSignatureError("Token too old to be refreshed")
class JiraUiHookViewErrorsTest(JiraUiHookViewTestCase): @patch( "sentry.integrations.jira.ui_hook.get_integration_from_request", side_effect=ExpiredSignatureError(), ) def test_expired_signature_error(self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert REFRESH_REQUIRED in response.content @patch( "sentry.integrations.jira.ui_hook.get_integration_from_request", side_effect=AtlassianConnectValidationError(), ) def test_expired_invalid_installation_error(self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert UNABLE_TO_VERIFY_INSTALLATION in response.content
def get_token_from_header(): """Pulls the vLab Auth Token from the HTTP header, and decrypts it. :Returns: Dictionary :Raises: ExpiredSignatureError """ try: serialized_token = request.headers.get('X-Auth') except AttributeError: # no token in header raise ExpiredSignatureError('No auth token in HTTP header') else: return decode(serialized_token, const.AUTH_TOKEN_PUB_KEY, algorithms=const.AUTH_TOKEN_ALGORITHM, issuer=const.AUTH_TOKEN_ISSUER, leeway=10) # 10 Seconds fuzzy window for clock skewing
class JiraIssueHookTest(APITestCase): def setUp(self): super(JiraIssueHookTest, self).setUp() self.first_seen = datetime(2015, 8, 13, 3, 8, 25, tzinfo=timezone.utc) self.last_seen = datetime(2016, 1, 13, 3, 8, 25, tzinfo=timezone.utc) self.first_release = self.create_release(project=self.project, version="v1.0", date_added=self.first_seen) self.last_release = self.create_release(project=self.project, version="v1.1", date_added=self.last_seen) self.group = self.create_group( self.project, message="Sentry Error", first_seen=self.first_seen, last_seen=self.last_seen, first_release=self.first_release, ) group = self.group self.path = absolute_uri( u"extensions/jira/issue/{}/".format("APP-123")) + "?xdm_e=base_url" self.integration = Integration.objects.create( provider="jira", name="Example Jira", metadata={"base_url": "https://getsentry.atlassian.net"}, ) self.integration.add_organization(self.organization, self.user) external_issue = ExternalIssue.objects.create( organization_id=group.organization.id, integration_id=self.integration.id, key="APP-123") GroupLink.objects.create( group_id=group.id, project_id=group.project_id, linked_type=GroupLink.LinkedType.issue, linked_id=external_issue.id, relationship=GroupLink.Relationship.references, ) self.login_as(self.user) @patch( "sentry.integrations.jira.issue_hook.get_integration_from_request", side_effect=ExpiredSignatureError(), ) def test_expired_signature_error(self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert REFRESH_REQUIRED in response.content @patch( "sentry.integrations.jira.issue_hook.get_integration_from_request", side_effect=AtlassianConnectValidationError(), ) def test_expired_invalid_installation_error( self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert UNABLE_TO_VERIFY_INSTALLATION in response.content @patch.object(Group, "get_last_release") @patch("sentry.integrations.jira.issue_hook.get_integration_from_request") def test_simple_get(self, mock_get_integration_from_request, mock_get_last_release): mock_get_last_release.return_value = self.last_release.version mock_get_integration_from_request.return_value = self.integration response = self.client.get(self.path) assert response.status_code == 200 resp_content = six.text_type(response.content) assert self.group.title in resp_content assert self.first_seen.strftime("%b. %d, %Y") in resp_content assert self.last_seen.strftime("%b. %d, %Y") in resp_content assert self.first_release.version in resp_content assert self.last_release.version in resp_content @patch("sentry.integrations.jira.issue_hook.get_integration_from_request") def test_simple_not_linked(self, mock_get_integration_from_request): mock_get_integration_from_request.return_value = self.integration path = absolute_uri( u"extensions/jira/issue/{}/".format("bad-key")) + "?xdm_e=base_url" response = self.client.get(path) assert response.status_code == 200 assert b"This Sentry issue is not linked to a Jira issue" in response.content
def decode_jwt(encoded_token, secret, identity_claim_key, csrf_value=None, audience=None, allow_expired=False, issuer=None): """ Decodes an encoded JWT :param encoded_token: The encoded JWT string to decode :param secret: Secret key used to decode the JWT :param identity_claim_key: expected key that contains the identity :param csrf_value: Expected double submit csrf value :param audience: expected audience in the JWT :param issuer: expected issuer in the JWT :param allow_expired: Options to ignore exp claim validation in token :return: Dictionary containing contents of the JWT """ token = encoded_token headers = jwt.get_unverified_headers(token) kid = headers['kid'] # search for the kid in the downloaded public keys key_index = -1 for i in range(len(secret)): if kid == secret[i]['kid']: key_index = i break if key_index == -1: raise JWTDecodeError("Invalid key attribute: kid") # construct the public key public_key = jwk.construct(secret[key_index]) # get the last two sections of the token, # message and signature (encoded in base64) message, encoded_signature = str(token).rsplit('.', 1) # decode the signature decoded_signature = base64url_decode(encoded_signature.encode('utf-8')) # verify the signature if not public_key.verify(message.encode("utf8"), decoded_signature): raise JWTDecodeError("Signature verification failed") # since we passed the verification, we can now safely # use the unverified claims data = jwt.get_unverified_claims(token) if identity_claim_key not in data: raise JWTDecodeError("Missing claim: {}".format(identity_claim_key)) if not allow_expired and time.time() > data['exp']: ctx_stack.top.expired_jwt = token raise ExpiredSignatureError("Token has expired") # check iss if 'iss' not in data: data['iss'] = None if data['iss'] != issuer: raise JWTDecodeError("Missing or invalid issuer") # check aud if id_token if data['token_use'] == 'id': if 'aud' not in data: data['aud'] = None if data['aud'] != audience: raise JWTDecodeError("Missing or invalid audience") # check clientid if access_token if data['token_use'] == 'access': if 'client_id' not in data: data['client_id'] = None if data['client_id'] != audience: raise JWTDecodeError("Missing or invalid audience") # check csrf if csrf_value: if 'csrf' not in data: raise JWTDecodeError("Missing claim: csrf") if not safe_str_cmp(data['csrf'], csrf_value): raise CSRFError("CSRF double submit tokens do not match") return data
class JiraIssueHookTest(APITestCase): def setUp(self): super().setUp() self.first_seen = datetime(2015, 8, 13, 3, 8, 25, tzinfo=timezone.utc) self.last_seen = datetime(2016, 1, 13, 3, 8, 25, tzinfo=timezone.utc) self.first_release = self.create_release(project=self.project, version="v1.0", date_added=self.first_seen) self.last_release = self.create_release(project=self.project, version="v1.1", date_added=self.last_seen) self.group = self.create_group( self.project, message="Sentry Error", first_seen=self.first_seen, last_seen=self.last_seen, first_release=self.first_release, ) group = self.group self.issue_key = "APP-123" self.path = absolute_uri( f"extensions/jira/issue/{self.issue_key}/") + "?xdm_e=base_url" self.integration = Integration.objects.create( provider="jira", name="Example Jira", metadata={ "base_url": "https://getsentry.atlassian.net", "shared_secret": "a-super-secret-key-from-atlassian", }, ) self.integration.add_organization(self.organization, self.user) external_issue = ExternalIssue.objects.create( organization_id=group.organization.id, integration_id=self.integration.id, key=self.issue_key, ) GroupLink.objects.create( group_id=group.id, project_id=group.project_id, linked_type=GroupLink.LinkedType.issue, linked_id=external_issue.id, relationship=GroupLink.Relationship.references, ) self.login_as(self.user) self.properties_key = f"com.atlassian.jira.issue:{JIRA_KEY}:sentry-issues-glance:status" self.properties_url = "https://getsentry.atlassian.net/rest/api/3/issue/%s/properties/%s" @patch( "sentry.integrations.jira.views.issue_hook.get_integration_from_request", side_effect=ExpiredSignatureError(), ) def test_expired_signature_error(self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert REFRESH_REQUIRED in response.content @patch( "sentry.integrations.jira.views.issue_hook.get_integration_from_request", side_effect=AtlassianConnectValidationError(), ) def test_expired_invalid_installation_error( self, mock_get_integration_from_request): response = self.client.get(self.path) assert response.status_code == 200 assert UNABLE_TO_VERIFY_INSTALLATION.encode() in response.content @patch.object(Group, "get_last_release") @patch( "sentry.integrations.jira.views.issue_hook.get_integration_from_request" ) @responses.activate def test_simple_get(self, mock_get_integration_from_request, mock_get_last_release): responses.add(responses.PUT, self.properties_url % (self.issue_key, self.properties_key), json={}) mock_get_last_release.return_value = self.last_release.version mock_get_integration_from_request.return_value = self.integration response = self.client.get(self.path) assert response.status_code == 200 resp_content = str(response.content) assert self.group.title in resp_content assert self.first_seen.strftime("%b. %d, %Y") in resp_content assert self.last_seen.strftime("%b. %d, %Y") in resp_content assert self.first_release.version in resp_content assert self.last_release.version in resp_content @patch( "sentry.integrations.jira.views.issue_hook.get_integration_from_request" ) @responses.activate def test_simple_not_linked(self, mock_get_integration_from_request): issue_key = "bad-key" responses.add(responses.PUT, self.properties_url % (issue_key, self.properties_key), json={}) mock_get_integration_from_request.return_value = self.integration path = absolute_uri( "extensions/jira/issue/bad-key/") + "?xdm_e=base_url" response = self.client.get(path) assert response.status_code == 200 assert b"This Sentry issue is not linked to a Jira issue" in response.content