def test_no_actor(self): result = AppPlatformEvent( resource='event_alert', action='triggered', install=self.install, data={}, ) assert result.body == json.dumps({ 'action': 'triggered', 'installation': { 'uuid': self.install.uuid, }, 'data': {}, 'actor': { 'type': 'application', 'id': 'sentry', 'name': 'Sentry', } }) signature = self.sentry_app.build_signature(result.body) assert result.headers['Content-Type'] == 'application/json' assert result.headers['Sentry-Hook-Resource'] == 'event_alert' assert result.headers['Sentry-Hook-Signature'] == signature
def send_alert_event(event, rule, sentry_app_id): group = event.group project = Project.objects.get_from_cache(id=group.project_id) organization = Organization.objects.get_from_cache(id=project.organization_id) extra = { "sentry_app_id": sentry_app_id, "project": project.slug, "organization": organization.slug, "rule": rule, } try: sentry_app = SentryApp.objects.get(id=sentry_app_id) except SentryApp.DoesNotExist: logger.info("event_alert_webhook.missing_sentry_app", extra=extra) return try: install = SentryAppInstallation.objects.get( organization=organization.id, sentry_app=sentry_app ) except SentryAppInstallation.DoesNotExist: logger.info("event_alert_webhook.missing_installation", extra=extra) return event_context = _webhook_event_data(event, group.id, project.id) data = {"event": event_context, "triggered_rule": rule} request_data = AppPlatformEvent( resource="event_alert", action="triggered", install=install, data=data ) send_and_save_webhook_request(sentry_app.webhook_url, sentry_app, request_data)
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) safe_urlopen( url=servicehook.sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, )
def send_webhooks(installation, event, **kwargs): project_ids = Project.objects.filter( organization_id=installation.organization_id, ).values_list('id', flat=True) servicehooks = ServiceHook.objects.filter(project_id__in=project_ids, ) for servicehook in filter(lambda s: event in s.events, servicehooks): if not servicehook.created_by_sentry_app: continue if servicehook.sentry_app != installation.sentry_app: continue resource, action = event.split('.') kwargs['resource'] = resource kwargs['action'] = action kwargs['install'] = installation request_data = AppPlatformEvent(**kwargs) safe_urlopen( url=servicehook.sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, )
def send_alert_event(event, rule, sentry_app_id, additional_payload_key=None, additional_payload=None): """ When an incident alert is triggered, send incident data to the SentryApp's webhook. :param event: The `Event` for which to build a payload. :param rule: The AlertRule that was triggered. :param sentry_app_id: The SentryApp to notify. :param additional_payload_key: The key used to attach additional data to the webhook payload :param additional_payload: The extra data attached to the payload body at the key specified by `additional_payload_key`. :return: """ group = event.group project = Project.objects.get_from_cache(id=group.project_id) organization = Organization.objects.get_from_cache( id=project.organization_id) extra = { "sentry_app_id": sentry_app_id, "project_slug": project.slug, "organization_slug": organization.slug, "rule": rule, } try: sentry_app = SentryApp.objects.get(id=sentry_app_id) except SentryApp.DoesNotExist: logger.info("event_alert_webhook.missing_sentry_app", extra=extra) return try: install = SentryAppInstallation.objects.get( organization=organization.id, sentry_app=sentry_app, status=SentryAppInstallationStatus.INSTALLED, ) except SentryAppInstallation.DoesNotExist: logger.info("event_alert_webhook.missing_installation", extra=extra) return event_context = _webhook_event_data(event, group.id, project.id) data = {"event": event_context, "triggered_rule": rule} # Attach extra payload to the webhook if additional_payload_key and additional_payload: data[additional_payload_key] = additional_payload request_data = AppPlatformEvent(resource="event_alert", action="triggered", install=install, data=data) send_and_save_webhook_request(sentry_app, request_data)
def process_resource_change(action, sender, instance_id, *args, **kwargs): model = None name = None # Previous method signature. if inspect.isclass(sender): model = sender else: model = TYPES[sender] name = RESOURCE_RENAMES.get(model.__name__, model.__name__.lower()) # We may run into a race condition where this task executes before the # transaction that creates the Group has committed. try: instance = model.objects.get(id=instance_id) except model.DoesNotExist as e: # Explicitly requeue the task, so we don't report this to Sentry until # we hit the max number of retries. return current.retry(exc=e) event = '{}.{}'.format(name, action) if event not in ALLOWED_EVENTS: return project = None if isinstance(instance, Group): project = instance.project if not project: return servicehooks = ServiceHook.objects.filter(project_id=project.id, ) for servicehook in filter(lambda s: event in s.events, servicehooks): # For now, these ``post_save`` callbacks are only valid for service # hooks created by a Sentry App. if not servicehook.created_by_sentry_app: continue request_data = AppPlatformEvent( resource=name, action=action, install=SentryAppInstallation.objects.get(id=servicehook.actor_id), data=serialize(instance), ) safe_urlopen( url=servicehook.url, data=request_data.body, headers=request_data.headers, timeout=5, )
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) potential_error_kwargs = { "sentry_app_id": installation.sentry_app_id, "organization_id": installation.organization_id, "request_body": request_data.body, "request_headers": request_data.headers, "event_type": event, "webhook_url": servicehook.sentry_app.webhook_url, } try: resp = safe_urlopen( url=servicehook.sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, ) except RequestException as exc: potential_error_kwargs["response_body"] = repr(exc) _save_webhook_error(**potential_error_kwargs) # Re-raise the exception because some of these tasks might retry on the exception raise if not resp.ok: body = safe_urlread(resp) potential_error_kwargs["response_body"] = body potential_error_kwargs["response_code"] = resp.status_code _save_webhook_error(**potential_error_kwargs)
def send_webhooks(installation, event, **kwargs): try: servicehook = ServiceHook.objects.get( organization_id=installation.organization_id, actor_id=installation.id) except ServiceHook.DoesNotExist: logger.info( "send_webhooks.missing_servicehook", extra={ "installation_id": installation.id, "event": event }, ) 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() # TODO(nola): This is disabled for now, because it could potentially affect internal integrations w/ error.created # # If the event is error.created & the request is going out to the Org that owns the Sentry App, # # Make sure we don't send the request, to prevent potential infinite loops # if ( # event == "error.created" # and installation.organization_id == installation.sentry_app.owner_id # ): # # We just want to exclude error.created from the project that the integration lives in # # Need to first implement project mapping for integration partners # metrics.incr( # "webhook_request.dropped", # tags={"sentry_app": installation.sentry_app.id, "event": event}, # ) # return if not project_limited: resource, action = event.split(".") kwargs["resource"] = resource kwargs["action"] = action kwargs["install"] = installation request_data = AppPlatformEvent(**kwargs) send_and_save_webhook_request( installation.sentry_app, request_data, servicehook.sentry_app.webhook_url, )
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 request(self): data = SentryAppInstallationSerializer().serialize( self.install, attrs={'code': self.api_grant.code}, user=self.user, ) return AppPlatformEvent( resource='installation', action='created', install=self.install, data=data, actor=self.user, )
def send_alert_event(event, rule, sentry_app_id): group = event.group project = Project.objects.get_from_cache(id=group.project_id) organization = Organization.objects.get_from_cache( id=project.organization_id) extra = { 'sentry_app_id': sentry_app_id, 'project': project.slug, 'organization': organization.slug, 'rule': rule, } try: sentry_app = SentryApp.objects.get(id=sentry_app_id) except SentryApp.DoesNotExist: logger.info('event_alert_webhook.missing_sentry_app', extra=extra) return try: install = SentryAppInstallation.objects.get( organization=organization.id, sentry_app=sentry_app, ) except SentryAppInstallation.DoesNotExist: logger.info('event_alert_webhook.missing_installation', extra=extra) return event_context = _webhook_event_data(event, group.id, project.id) data = { 'event': event_context, 'triggered_rule': rule, } request_data = AppPlatformEvent( resource='event_alert', action='triggered', install=install, data=data, ) safe_urlopen( url=sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, )
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) resp = safe_urlopen( url=servicehook.sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, ) # Track any webhook errors for these event types if not resp.ok and event in [ "issue.assigned", "issue.ignored", "issue.resolved" ]: body = safe_urlread(resp) SentryAppWebhookError.objects.create( sentry_app_id=installation.sentry_app_id, organization_id=installation.organization_id, request_body=request_data.body, request_headers=request_data.headers, event_type=event, webhook_url=servicehook.sentry_app.webhook_url, response_body=body, response_code=resp.status_code, )
def request(self): attrs = {} if self.action == "created" and self.api_grant: attrs["code"] = self.api_grant.code data = SentryAppInstallationSerializer().serialize(self.install, attrs=attrs, user=self.user) return AppPlatformEvent( resource="installation", action=self.action, install=self.install, data={"installation": data}, actor=self.user, )
def test_user_actor(self): result = AppPlatformEvent( resource='installation', action='created', install=self.install, data={}, actor=self.user, ) assert result.body['actor'] == { 'type': 'user', 'id': self.user.id, 'name': self.user.name, } body = json.dumps(result.body) signature = self.sentry_app.build_signature(body) assert result.headers['Content-Type'] == 'application/json' assert result.headers['Sentry-Hook-Resource'] == 'installation' assert result.headers['Sentry-Hook-Signature'] == signature
def test_no_actor(self): result = AppPlatformEvent( resource="event_alert", action="triggered", install=self.install, data={} ) assert result.body == json.dumps( { "action": "triggered", "installation": {"uuid": self.install.uuid}, "data": {}, "actor": {"type": "application", "id": "sentry", "name": "Sentry"}, } ) signature = self.sentry_app.build_signature(result.body) assert result.headers["Content-Type"] == "application/json" assert result.headers["Sentry-Hook-Resource"] == "event_alert" assert result.headers["Sentry-Hook-Signature"] == signature
def request(self): attrs = {} if self.install.is_new and self.api_grant: attrs['code'] = self.api_grant.code data = SentryAppInstallationSerializer().serialize( self.install, attrs=attrs, user=self.user, ) return AppPlatformEvent( resource='installation', action=self.action, install=self.install, data={'installation': data}, actor=self.user, )
def test_user_actor(self): result = AppPlatformEvent( resource="installation", action="created", install=self.install, data={}, actor=self.user, ) assert json.loads(result.body)["actor"] == { "type": "user", "id": self.user.id, "name": self.user.name, } signature = self.sentry_app.build_signature(result.body) assert result.headers["Content-Type"] == "application/json" assert result.headers["Sentry-Hook-Resource"] == "installation" assert result.headers["Sentry-Hook-Signature"] == signature
def test_sentry_app_actor(self): result = AppPlatformEvent( resource='issue', action='assigned', install=self.install, data={}, actor=self.sentry_app.proxy_user, ) assert json.loads(result.body)['actor'] == { 'type': 'application', 'id': self.sentry_app.uuid, 'name': self.sentry_app.name, } signature = self.sentry_app.build_signature(result.body) assert result.headers['Content-Type'] == 'application/json' assert result.headers['Sentry-Hook-Resource'] == 'issue' assert result.headers['Sentry-Hook-Signature'] == signature
def send_alert_event(event, rule, sentry_app_id): group = event.group project = group.project try: sentry_app = SentryApp.objects.get(id=sentry_app_id) except SentryApp.DoesNotExist: logger.info( 'event_alert_webhook.missing_sentry_app', extra={ 'sentry_app_id': sentry_app_id, 'project': project.slug, 'organization': project.organization.slug, 'rule': rule, }, ) return try: install = SentryAppInstallation.objects.get( organization=event.project.organization_id, sentry_app=sentry_app, ) except SentryAppInstallation.DoesNotExist: logger.info( 'event_alert_webhook.missing_installation', extra={ 'sentry_app': sentry_app.slug, 'project': project.slug, 'organization': project.organization.slug, 'rule': rule, }, ) return event_context = event.as_dict() event_context['url'] = absolute_uri( reverse('sentry-api-0-project-event-details', args=[ project.organization.slug, project.slug, event.id, ])) event_context['web_url'] = absolute_uri( reverse('sentry-group-event', args=[ project.organization.slug, project.slug, group.id, event.id, ])) event_context['issue_url'] = absolute_uri( '/api/0/issues/{}/'.format(group.id), ) data = { 'event': event_context, } data['triggered_rule'] = rule request_data = AppPlatformEvent( resource='event_alert', action='triggered', install=install, data=data, ) safe_urlopen( url=sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, )
def send_alert_event(event, rule, sentry_app_id): group = event.group project = group.project extra = { 'sentry_app_id': sentry_app_id, 'project': project.slug, 'organization': project.organization.slug, 'rule': rule, } try: sentry_app = SentryApp.objects.get(id=sentry_app_id) except SentryApp.DoesNotExist: logger.info('event_alert_webhook.missing_sentry_app', extra=extra) return try: install = SentryAppInstallation.objects.get( organization=event.project.organization_id, sentry_app=sentry_app, ) except SentryAppInstallation.DoesNotExist: logger.info('event_alert_webhook.missing_installation', extra=extra) return event_context = event.as_dict() event_context['url'] = absolute_uri( reverse('sentry-api-0-project-event-details', args=[ project.organization.slug, project.slug, event.id, ])) event_context['web_url'] = absolute_uri( reverse('sentry-group-event', args=[ project.organization.slug, project.slug, group.id, event.id, ])) # The URL has a regex OR in it ("|") which means `reverse` cannot generate # a valid URL (it can't know which option to pick). We have to manually # create this URL for, that reason. event_context['issue_url'] = absolute_uri( '/api/0/issues/{}/'.format(group.id), ) data = { 'event': event_context, 'triggered_rule': rule, } request_data = AppPlatformEvent( resource='event_alert', action='triggered', install=install, data=data, ) safe_urlopen( url=sentry_app.webhook_url, data=request_data.body, headers=request_data.headers, timeout=5, )