예제 #1
0
    def test_linked_error_not_returned_if_project_does_not_exist(self):
        self.login_as(user=self.user)

        self.store_event(
            data={
                "event_id": self.event_id,
                "timestamp": iso_format(before_now(minutes=1))
            },
            project_id=self.project.id,
        )

        buffer = SentryAppWebhookRequestsBuffer(self.published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unpublished_app.webhook_url,
            error_id=self.event_id,
            project_id="1000",
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.published_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 1
        assert response.data[0]["organization"]["slug"] == self.org.slug
        assert response.data[0]["sentryAppSlug"] == self.published_app.slug
        assert "errorUrl" not in response.data[0]
예제 #2
0
def send_and_save_sentry_app_request(url, sentry_app, org_id, event, **kwargs):
    """
    Send a webhook request, and save the request into the Redis buffer for the app dashboard request log
    Returns the response of the request

    kwargs ends up being the arguments passed into safe_urlopen
    """

    buffer = SentryAppWebhookRequestsBuffer(sentry_app)

    slug = sentry_app.slug_for_metrics

    try:
        resp = safe_urlopen(url=url, **kwargs)
    except RequestException:
        track_response_code("timeout", slug, event)
        # Response code of 0 represents timeout
        buffer.add_request(response_code=0,
                           org_id=org_id,
                           event=event,
                           url=url)
        # Re-raise the exception because some of these tasks might retry on the exception
        raise

    track_response_code(resp.status_code, slug, event)
    buffer.add_request(
        response_code=resp.status_code,
        org_id=org_id,
        event=event,
        url=url,
        error_id=resp.headers.get("Sentry-Hook-Error"),
        project_id=resp.headers.get("Sentry-Hook-Project"),
    )

    return resp
예제 #3
0
def send_and_save_sentry_app_request(url, sentry_app, org_id, event, **kwargs):
    """
    Send a webhook request, and save the request into the Redis buffer for the app dashboard request log
    Returns the response of the request

    kwargs ends up being the arguments passed into safe_urlopen
    """

    buffer = SentryAppWebhookRequestsBuffer(sentry_app)

    try:
        resp = safe_urlopen(url=url, **kwargs)
    except RequestException:
        # Response code of 0 represents timeout
        buffer.add_request(response_code=0,
                           org_id=org_id,
                           event=event,
                           url=url)
        # Re-raise the exception because some of these tasks might retry on the exception
        raise

    buffer.add_request(response_code=resp.status_code,
                       org_id=org_id,
                       event=event,
                       url=url)

    return resp
예제 #4
0
    def test_errors_only_filter(self):
        self.login_as(user=self.user)
        buffer = SentryAppWebhookRequestsBuffer(self.published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.published_app.webhook_url,
        )
        buffer.add_request(
            response_code=500,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.published_app.webhook_url,
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.published_app.slug])
        errors_only_response = self.client.get(
            "{}?errorsOnly=true".format(url), format="json")
        assert errors_only_response.status_code == 200
        assert len(errors_only_response.data) == 1

        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 2
예제 #5
0
    def test_superuser_sees_unowned_published_requests(self):
        self.login_as(user=self.superuser, superuser=True)

        buffer = SentryAppWebhookRequestsBuffer(self.unowned_published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unowned_published_app.webhook_url,
        )
        buffer.add_request(
            response_code=500,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unowned_published_app.webhook_url,
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.unowned_published_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 2
        assert response.data[0]["organization"]["slug"] == self.org.slug
        assert response.data[0][
            "sentryAppSlug"] == self.unowned_published_app.slug
        assert response.data[0]["responseCode"] == 500
예제 #6
0
파일: sentry_apps.py 프로젝트: wyfaq/sentry
def send_and_save_webhook_request(url, sentry_app, app_platform_event):
    buffer = SentryAppWebhookRequestsBuffer(sentry_app)

    org_id = app_platform_event.install.organization_id
    event = "{}.{}".format(app_platform_event.resource,
                           app_platform_event.action)

    try:
        resp = safe_urlopen(url=url,
                            data=app_platform_event.body,
                            headers=app_platform_event.headers,
                            timeout=5)
    except RequestException:
        # Response code of 0 represents timeout
        buffer.add_request(response_code=0,
                           org_id=org_id,
                           event=event,
                           url=url)
        # Re-raise the exception because some of these tasks might retry on the exception
        raise

    buffer.add_request(
        response_code=resp.status_code,
        org_id=org_id,
        event=event,
        url=url,
        error_id=resp.headers.get("Sentry-Hook-Error"),
        project_id=resp.headers.get("Sentry-Hook-Project"),
    )

    return resp
예제 #7
0
class TestSentryAppWebhookRequests(TestCase):
    def setUp(self):
        self.sentry_app = self.create_sentry_app(
            name="Test App", events=["issue.resolved", "issue.ignored", "issue.assigned"]
        )
        self.project = self.create_project()

        self.buffer = SentryAppWebhookRequestsBuffer(self.sentry_app)

    def test_only_100_entries_in_buffer(self):
        for i in range(100):
            self.buffer.add_request(200, i, "issue.assigned", "https://example.com/hook")

        requests = self.buffer.get_requests()
        assert len(requests) == 100
        assert requests[0]["organization_id"] == 99
        assert requests[99]["organization_id"] == 0

        self.buffer.add_request(500, 100, "issue.assigned", "https://test.com/hook")

        requests = self.buffer.get_requests()
        assert len(requests) == 100
        assert requests[0]["organization_id"] == 100
        assert requests[0]["response_code"] == 500
        assert requests[99]["organization_id"] == 1
        assert requests[99]["response_code"] == 200

    def test_error_added(self):
        self.buffer.add_request(
            200,
            1,
            "issue.assigned",
            "https://example.com/hook",
            error_id="d5111da2c28645c5889d072017e3445d",
            project_id=1,
        )
        requests = self.buffer.get_requests()
        assert len(requests) == 1
        assert requests[0]["error_id"] == "d5111da2c28645c5889d072017e3445d"
        assert requests[0]["project_id"] == 1

    def test_error_not_added_if_project_id_missing(self):
        self.buffer.add_request(
            200,
            1,
            "issue.assigned",
            "https://example.com/hook",
            error_id="d5111da2c28645c5889d072017e3445d",
        )
        requests = self.buffer.get_requests()
        assert len(requests) == 1
        assert "error_id" not in requests[0]
        assert "project_id" not in requests[0]

    def test_error_not_added_if_error_id_missing(self):
        self.buffer.add_request(200, 1, "issue.assigned", "https://example.com/hook", project_id=1)
        requests = self.buffer.get_requests()
        assert len(requests) == 1
        assert "error_id" not in requests[0]
        assert "project_id" not in requests[0]
class TestSentryAppWebhookRequests(TestCase):
    def setUp(self):
        self.sentry_app = self.create_sentry_app(
            name="Test App", events=["issue.resolved", "issue.ignored", "issue.assigned"]
        )

        self.buffer = SentryAppWebhookRequestsBuffer(self.sentry_app)

    def test_only_100_entries_in_buffer(self):
        for i in range(100):
            self.buffer.add_request(200, i, "issue.assigned", "https://example.com/hook")

        requests = self.buffer.get_requests()
        assert len(requests) == 100
        assert requests[0]["organization_id"] == 99
        assert requests[99]["organization_id"] == 0

        self.buffer.add_request(500, 100, "issue.assigned", "https://test.com/hook")

        requests = self.buffer.get_requests()
        assert len(requests) == 100
        assert requests[0]["organization_id"] == 100
        assert requests[0]["response_code"] == 500
        assert requests[99]["organization_id"] == 1
        assert requests[99]["response_code"] == 200
예제 #9
0
def send_and_save_webhook_request(sentry_app, app_platform_event, url=None):
    """
    Notify a SentryApp's webhook about an incident and log response on redis.

    :param sentry_app: The SentryApp to notify via a webhook.
    :param app_platform_event: Incident data. See AppPlatformEvent.
    :param url: The URL to hit for this webhook if it is different from `sentry_app.webhook_url`.
    :return: Webhook response
    """
    buffer = SentryAppWebhookRequestsBuffer(sentry_app)

    org_id = app_platform_event.install.organization_id
    event = f"{app_platform_event.resource}.{app_platform_event.action}"
    slug = sentry_app.slug_for_metrics
    url = url or sentry_app.webhook_url

    try:
        resp = safe_urlopen(
            url=url, data=app_platform_event.body, headers=app_platform_event.headers, timeout=5
        )

    except (Timeout, ConnectionError) as e:
        error_type = e.__class__.__name__.lower()
        logger.info(
            "send_and_save_webhook_request.timeout",
            extra={
                "error_type": error_type,
                "organization_id": org_id,
                "integration_slug": sentry_app.slug,
            },
        )
        track_response_code(error_type, slug, event)
        # Response code of 0 represents timeout
        buffer.add_request(response_code=0, org_id=org_id, event=event, url=url)
        # Re-raise the exception because some of these tasks might retry on the exception
        raise

    else:
        track_response_code(resp.status_code, slug, event)
        buffer.add_request(
            response_code=resp.status_code,
            org_id=org_id,
            event=event,
            url=url,
            error_id=resp.headers.get("Sentry-Hook-Error"),
            project_id=resp.headers.get("Sentry-Hook-Project"),
        )

        if resp.status_code == 503:
            raise ApiHostError.from_request(resp.request)

        elif resp.status_code == 504:
            raise ApiTimeoutError.from_request(resp.request)

        if 400 <= resp.status_code < 500:
            raise ClientError(resp.status_code, url, response=resp)

        resp.raise_for_status()

        return resp
예제 #10
0
def send_webhooks(installation, event, **kwargs):
    try:
        servicehook = ServiceHook.objects.get(
            organization_id=installation.organization_id, actor_id=installation.id
        )
    except ServiceHook.DoesNotExist:
        return

    if event not in servicehook.events:
        return

    # The service hook applies to all projects if there are no
    # ServiceHookProject records. Otherwise we want check if
    # the event is within the allowed projects.
    project_limited = ServiceHookProject.objects.filter(service_hook_id=servicehook.id).exists()

    if not project_limited:
        resource, action = event.split(".")

        kwargs["resource"] = resource
        kwargs["action"] = action
        kwargs["install"] = installation

        request_data = AppPlatformEvent(**kwargs)

        buffer = SentryAppWebhookRequestsBuffer(installation.sentry_app)

        try:
            resp = safe_urlopen(
                url=servicehook.sentry_app.webhook_url,
                data=request_data.body,
                headers=request_data.headers,
                timeout=5,
            )
        except RequestException:
            # Response code of 0 represents timeout
            buffer.add_request(
                response_code=0,
                org_id=installation.organization_id,
                event=event,
                url=servicehook.sentry_app.webhook_url,
            )
            # Re-raise the exception because some of these tasks might retry on the exception
            raise

        buffer.add_request(
            response_code=resp.status_code,
            org_id=installation.organization_id,
            event=event,
            url=servicehook.sentry_app.webhook_url,
        )
    def test_user_does_not_see_unowned_published_requests(self):
        self.login_as(user=self.user)

        buffer = SentryAppWebhookRequestsBuffer(self.unowned_published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unowned_published_app.webhook_url,
        )

        url = reverse("sentry-api-0-sentry-app-requests", args=[self.unowned_published_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 403
        assert response.data["detail"] == "You do not have permission to perform this action."
    def test_user_sees_owned_unpublished_requests(self):
        self.login_as(user=self.user)

        buffer = SentryAppWebhookRequestsBuffer(self.unpublished_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unpublished_app.webhook_url,
        )

        url = reverse("sentry-api-0-sentry-app-requests", args=[self.unpublished_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 1
예제 #13
0
def send_and_save_sentry_app_request(url, sentry_app, org_id, event, **kwargs):
    """
    Send a webhook request, and save the request into the Redis buffer for the app dashboard request log
    Returns the response of the request

    kwargs ends up being the arguments passed into safe_urlopen
    """

    buffer = SentryAppWebhookRequestsBuffer(sentry_app)

    slug = sentry_app.slug_for_metrics

    try:
        resp = safe_urlopen(url=url, **kwargs)

    except (Timeout, ConnectionError) as e:
        error_type = e.__class__.__name__.lower()
        logger.info(
            "send_and_save_sentry_app_request.timeout",
            extra={
                "error_type": error_type,
                "organization_id": org_id,
                "integration_slug": sentry_app.slug,
            },
        )
        track_response_code(error_type, slug, event)
        # Response code of 0 represents timeout
        buffer.add_request(response_code=0,
                           org_id=org_id,
                           event=event,
                           url=url)
        # Re-raise the exception because some of these tasks might retry on the exception
        raise

    else:
        track_response_code(resp.status_code, slug, event)
        buffer.add_request(
            response_code=resp.status_code,
            org_id=org_id,
            event=event,
            url=url,
            error_id=resp.headers.get("Sentry-Hook-Error"),
            project_id=resp.headers.get("Sentry-Hook-Project"),
        )
        resp.raise_for_status()
        return resp
    def test_internal_app_requests_does_not_have_organization_field(self):
        self.login_as(user=self.user)
        buffer = SentryAppWebhookRequestsBuffer(self.internal_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.internal_app.webhook_url,
        )

        url = reverse("sentry-api-0-sentry-app-requests", args=[self.internal_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 1
        assert "organization" not in response.data[0]
        assert response.data[0]["sentryAppSlug"] == self.internal_app.slug
        assert response.data[0]["responseCode"] == 200
예제 #15
0
    def test_linked_error_not_returned_if_project_does_not_exist(self):
        self.login_as(user=self.user)

        buffer = SentryAppWebhookRequestsBuffer(self.published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unpublished_app.webhook_url,
            error_id="d5111da2c28645c5889d072017e3445d",
            project_id="1000",
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.published_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 1
        assert response.data[0]["organization"]["slug"] == self.org.slug
        assert response.data[0]["sentryAppSlug"] == self.published_app.slug
        assert "errorUrl" not in response.data[0]
예제 #16
0
    def test_event_type_filter(self):
        self.login_as(user=self.user)
        buffer = SentryAppWebhookRequestsBuffer(self.published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.published_app.webhook_url,
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.published_app.slug])
        response1 = self.client.get("{}?eventType=issue.created".format(url),
                                    format="json")
        assert response1.status_code == 200
        assert len(response1.data) == 0

        response2 = self.client.get("{}?eventType=issue.assigned".format(url),
                                    format="json")
        assert response2.status_code == 200
        assert len(response2.data) == 1
예제 #17
0
def send_and_save_webhook_request(url, sentry_app, app_platform_event):
    buffer = SentryAppWebhookRequestsBuffer(sentry_app)

    org_id = app_platform_event.install.organization_id
    event = "{}.{}".format(app_platform_event.resource, app_platform_event.action)
    slug = sentry_app.slug_for_metrics

    try:
        resp = safe_urlopen(
            url=url, data=app_platform_event.body, headers=app_platform_event.headers, timeout=5
        )

    except (Timeout, ConnectionError) as e:
        track_response_code(e.__class__.__name__.lower(), slug, event)
        # Response code of 0 represents timeout
        buffer.add_request(response_code=0, org_id=org_id, event=event, url=url)
        # Re-raise the exception because some of these tasks might retry on the exception
        raise

    else:
        track_response_code(resp.status_code, slug, event)
        buffer.add_request(
            response_code=resp.status_code,
            org_id=org_id,
            event=event,
            url=url,
            error_id=resp.headers.get("Sentry-Hook-Error"),
            project_id=resp.headers.get("Sentry-Hook-Project"),
        )

        if resp.status_code == 503:
            raise ApiHostError.from_request(resp.request)

        elif resp.status_code == 504:
            raise ApiTimeoutError.from_request(resp.request)

        resp.raise_for_status()

        return resp
예제 #18
0
    def test_linked_error_not_returned_if_project_doesnt_belong_to_org(self):
        self.login_as(user=self.user)
        unowned_project = self.create_project(
            organization=self.create_organization())

        buffer = SentryAppWebhookRequestsBuffer(self.published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unpublished_app.webhook_url,
            error_id=self.event_id,
            project_id=unowned_project.id,
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.published_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 1
        assert response.data[0]["organization"]["slug"] == self.org.slug
        assert response.data[0]["sentryAppSlug"] == self.published_app.slug
        assert "errorUrl" not in response.data[0]
예제 #19
0
    def test_linked_error_not_returned_if_event_does_not_exist(self):
        self.login_as(user=self.user)

        # event_id doesn't correspond to an existing event because we didn't call store_event

        buffer = SentryAppWebhookRequestsBuffer(self.published_app)
        buffer.add_request(
            response_code=200,
            org_id=self.org.id,
            event="issue.assigned",
            url=self.unpublished_app.webhook_url,
            error_id=self.event_id,
            project_id=self.project.id,
        )

        url = reverse("sentry-api-0-sentry-app-requests",
                      args=[self.published_app.slug])
        response = self.client.get(url, format="json")
        assert response.status_code == 200
        assert len(response.data) == 1
        assert response.data[0]["organization"]["slug"] == self.org.slug
        assert response.data[0]["sentryAppSlug"] == self.published_app.slug
        assert "errorUrl" not in response.data[0]