def build_action_response(registered_type, integration=None, organization=None, sentry_app=None): """ Build the "available action" objects for the API. Each one can have different fields. :param registered_type: One of the registered AlertRuleTriggerAction types. :param integration: Optional. The Integration if this action uses a one. :param organization: Optional. If this is a PagerDuty action, we need the organization to look up services. :param sentry_app: Optional. The SentryApp if this action uses a one. :return: The available action object. """ action_response = { "type": registered_type.slug, "allowedTargetTypes": [ action_target_type_to_string.get(target_type) for target_type in registered_type.supported_target_types ], } if integration: action_response["integrationName"] = integration.name action_response["integrationId"] = integration.id if registered_type.type == AlertRuleTriggerAction.Type.PAGERDUTY: action_response["options"] = [ {"value": service["id"], "label": service["service_name"]} for service in get_pagerduty_services(organization, integration.id) ] elif sentry_app: action_response["sentryAppName"] = sentry_app.name action_response["sentryAppId"] = sentry_app.id action_response["status"] = SentryAppStatus.as_str(sentry_app.status) return action_response
def test_creates_internal_integration(self): self.create_project(organization=self.org) self.login_as(self.user) response = self._post(isInternal=True) assert response.data['slug'] == 'myapp' assert response.data['status'] == SentryAppStatus.as_str( SentryAppStatus.INTERNAL)
def test_creates_internal_integration(self): self.create_project(organization=self.org) self.login_as(self.user) response = self._post(isInternal=True) assert re.match(r'myapp\-[0-9a-zA-Z]+', response.data['slug']) assert response.data['status'] == SentryAppStatus.as_str( SentryAppStatus.INTERNAL) assert not response.data['verifyInstall']
def build_action_response(registered_type, integration=None, organization=None, sentry_app_installation=None): """ Build the "available action" objects for the API. Each one can have different fields. :param registered_type: One of the registered AlertRuleTriggerAction types. :param integration: Optional. The Integration if this action uses a one. :param organization: Optional. If this is a PagerDuty action, we need the organization to look up services. :param sentry_app: Optional. The SentryApp if this action uses a one. :return: The available action object. """ action_response = { "type": registered_type.slug, "allowedTargetTypes": [ ACTION_TARGET_TYPE_TO_STRING.get(target_type) for target_type in registered_type.supported_target_types ], } if integration: action_response["integrationName"] = integration.name action_response["integrationId"] = integration.id if registered_type.type == AlertRuleTriggerAction.Type.PAGERDUTY: action_response["options"] = [{ "value": service["id"], "label": service["service_name"] } for service in get_pagerduty_services(organization, integration.id)] elif sentry_app_installation: action_response[ "sentryAppName"] = sentry_app_installation.sentry_app.name action_response["sentryAppId"] = sentry_app_installation.sentry_app_id action_response[ "sentryAppInstallationUuid"] = sentry_app_installation.uuid action_response["status"] = SentryAppStatus.as_str( sentry_app_installation.sentry_app.status) # Sentry Apps can be alertable but not have an Alert Rule UI Component component = sentry_app_installation.prepare_sentry_app_components( "alert-rule-action") if component: action_response["settings"] = component.schema.get("settings", {}) return action_response
class SentryApp(ParanoidModel, HasApiScopes): __core__ = True application = models.OneToOneField('sentry.ApiApplication', related_name='sentry_app') # Much of the OAuth system in place currently depends on a User existing. # This "proxy user" represents the SentryApp in those cases. proxy_user = models.OneToOneField('sentry.User', related_name='sentry_app') # The owner is an actual Sentry User who created the SentryApp. Used to # determine who can manage the SentryApp itself. owner = FlexibleForeignKey('sentry.User', related_name='owned_sentry_apps') name = models.TextField() slug = models.CharField(max_length=64, unique=True) status = BoundedPositiveIntegerField( default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True, ) uuid = models.CharField(max_length=64, default=default_uuid) webhook_url = models.TextField() date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_sentryapp' def save(self, *args, **kwargs): self._set_slug() return super(SentryApp, self).save(*args, **kwargs) def _set_slug(self): """ Matches ``name``, but in lowercase, dash form. >>> self._set_slug('My Cool App') >>> self.slug my-cool-app """ if not self.slug: self.slug = slugify(self.name)
def test_creates_internal_integration(self): self.create_project(organization=self.org) self.login_as(self.user) response = self._post(isInternal=True) assert re.match(r'myapp\-[0-9a-zA-Z]+', response.data['slug']) assert response.data['status'] == SentryAppStatus.as_str(SentryAppStatus.INTERNAL) assert not response.data['verifyInstall'] # verify tokens are created properly sentry_app = SentryApp.objects.get(slug=response.data['slug']) sentry_app_installation = SentryAppInstallation.objects.get(sentry_app=sentry_app) sentry_app_installation_token = SentryAppInstallationToken.objects.get( sentry_app_installation=sentry_app_installation) # Below line will fail once we stop assigning api_token on the sentry_app_installation assert sentry_app_installation_token.api_token == sentry_app_installation.api_token
def test_creates_internal_integration(self): self.create_project(organization=self.organization) response = self.get_success_response(**self.get_data(isInternal=True)) assert re.match(r"myapp\-[0-9a-zA-Z]+", response.data["slug"]) assert response.data["status"] == SentryAppStatus.as_str( SentryAppStatus.INTERNAL) assert not response.data["verifyInstall"] # Verify tokens are created properly. sentry_app = SentryApp.objects.get(slug=response.data["slug"]) sentry_app_installation = SentryAppInstallation.objects.get( sentry_app=sentry_app) sentry_app_installation_token = SentryAppInstallationToken.objects.get( sentry_app_installation=sentry_app_installation) # Below line will fail once we stop assigning api_token on the sentry_app_installation. assert sentry_app_installation_token.api_token == sentry_app_installation.api_token
class SentryApp(ParanoidModel, HasApiScopes): __core__ = True application = models.OneToOneField("sentry.ApiApplication", null=True, on_delete=models.SET_NULL, related_name="sentry_app") # Much of the OAuth system in place currently depends on a User existing. # This "proxy user" represents the SentryApp in those cases. proxy_user = models.OneToOneField("sentry.User", null=True, on_delete=models.SET_NULL, related_name="sentry_app") # The Organization the Sentry App was created in "owns" it. Members of that # Org have differing access, dependent on their role within the Org. owner = FlexibleForeignKey("sentry.Organization", related_name="owned_sentry_apps") name = models.TextField() slug = models.CharField(max_length=SENTRY_APP_SLUG_MAX_LENGTH, unique=True) author = models.TextField(null=True) status = BoundedPositiveIntegerField(default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True) uuid = models.CharField(max_length=64, default=default_uuid) redirect_url = models.URLField(null=True) webhook_url = models.URLField(null=True) # does the application subscribe to `event.alert`, # meaning can it be used in alert rules as a {service} ? is_alertable = models.BooleanField(default=False) # does the application need to wait for verification # on behalf of the external service to know if its installations # are successfully installed ? verify_install = models.BooleanField(default=True) events = ArrayField(of=models.TextField, null=True) overview = models.TextField(null=True) schema = EncryptedJsonField(default=dict) date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) date_published = models.DateTimeField(null=True, blank=True) class Meta: app_label = "sentry" db_table = "sentry_sentryapp" @classmethod def visible_for_user(cls, request): from sentry.auth.superuser import is_active_superuser if is_active_superuser(request): return cls.objects.all() return cls.objects.filter(status=SentryAppStatus.PUBLISHED) @property def is_published(self): return self.status == SentryAppStatus.PUBLISHED @property def is_unpublished(self): return self.status == SentryAppStatus.UNPUBLISHED @property def is_internal(self): return self.status == SentryAppStatus.INTERNAL @property def slug_for_metrics(self): if self.is_internal: return "internal" if self.is_unpublished: return "unpublished" return self.slug def save(self, *args, **kwargs): self.date_updated = timezone.now() return super(SentryApp, self).save(*args, **kwargs) def is_installed_on(self, organization): return SentryAppInstallation.objects.filter( organization=organization).exists() def build_signature(self, body): secret = self.application.client_secret return hmac.new(key=secret.encode("utf-8"), msg=body.encode("utf-8"), digestmod=sha256).hexdigest() def show_auth_info(self, access): encoded_scopes = set({u"%s" % scope for scope in list(access.scopes)}) return set(self.scope_list).issubset(encoded_scopes)
class SentryApp(ParanoidModel, HasApiScopes): __core__ = True application = models.OneToOneField("sentry.ApiApplication", null=True, on_delete=models.SET_NULL, related_name="sentry_app") # Much of the OAuth system in place currently depends on a User existing. # This "proxy user" represents the SentryApp in those cases. proxy_user = models.OneToOneField("sentry.User", null=True, on_delete=models.SET_NULL, related_name="sentry_app") # The Organization the Sentry App was created in "owns" it. Members of that # Org have differing access, dependent on their role within the Org. owner = FlexibleForeignKey("sentry.Organization", related_name="owned_sentry_apps") name = models.TextField() slug = models.CharField(max_length=SENTRY_APP_SLUG_MAX_LENGTH, unique=True) author = models.TextField(null=True) status = BoundedPositiveIntegerField(default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True) uuid = models.CharField(max_length=64, default=default_uuid) redirect_url = models.URLField(null=True) webhook_url = models.URLField() # does the application subscribe to `event.alert`, # meaning can it be used in alert rules as a {service} ? is_alertable = models.BooleanField(default=False) # does the application need to wait for verification # on behalf of the external service to know if its installations # are successully installed ? verify_install = models.BooleanField(default=True) events = ArrayField(of=models.TextField, null=True) overview = models.TextField(null=True) schema = EncryptedJsonField(default=dict) date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) class Meta: app_label = "sentry" db_table = "sentry_sentryapp" @classmethod def visible_for_user(cls, request): from sentry.auth.superuser import is_active_superuser if is_active_superuser(request): return cls.objects.all() return cls.objects.filter(status=SentryAppStatus.PUBLISHED) @property def organizations(self): if not self.pk: return Organization.objects.none() return Organization.objects.select_related( "sentry_app_installations").filter( sentry_app_installations__sentry_app_id=self.id) @property def teams(self): from sentry.models import Team if not self.pk: return Team.objects.none() return Team.objects.filter(organization__in=self.organizations) @property def is_published(self): return self.status == SentryAppStatus.PUBLISHED @property def is_unpublished(self): return self.status == SentryAppStatus.UNPUBLISHED @property def is_internal(self): return self.status == SentryAppStatus.INTERNAL def save(self, *args, **kwargs): self._set_slug() self.date_updated = timezone.now() return super(SentryApp, self).save(*args, **kwargs) def is_installed_on(self, organization): return self.organizations.filter(pk=organization.pk).exists() def _set_slug(self): """ Matches ``name``, but in lowercase, dash form. >>> self._set_slug('My Cool App') >>> self.slug my-cool-app """ if not self.slug: self.slug = slugify(self.name) if self.is_internal and not self._has_internal_slug(): self.slug = u"{}-{}".format( self.slug, hashlib.sha1(self.owner.slug).hexdigest()[0:6]) def _has_internal_slug(self): return re.match(r"\w+-[0-9a-zA-Z]+", self.slug) def build_signature(self, body): secret = self.application.client_secret return hmac.new(key=secret.encode("utf-8"), msg=body.encode("utf-8"), digestmod=sha256).hexdigest()
class SentryApp(ParanoidModel, HasApiScopes): __core__ = True application = models.OneToOneField( 'sentry.ApiApplication', null=True, on_delete=models.SET_NULL, related_name='sentry_app', ) # Much of the OAuth system in place currently depends on a User existing. # This "proxy user" represents the SentryApp in those cases. proxy_user = models.OneToOneField( 'sentry.User', null=True, on_delete=models.SET_NULL, related_name='sentry_app' ) # The Organization the Sentry App was created in "owns" it. Members of that # Org have differing access, dependent on their role within the Org. owner = FlexibleForeignKey('sentry.Organization', related_name='owned_sentry_apps') name = models.TextField() slug = models.CharField(max_length=64, unique=True) status = BoundedPositiveIntegerField( default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True, ) uuid = models.CharField(max_length=64, default=default_uuid) webhook_url = models.TextField() date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_sentryapp' @property def organizations(self): if not self.pk: return Organization.objects.none() return Organization \ .objects \ .select_related('sentry_app_installations') \ .filter(sentry_app_installations__sentry_app_id=self.id) @property def teams(self): from sentry.models import Team if not self.pk: return Team.objects.none() return Team.objects.filter(organization__in=self.organizations) def save(self, *args, **kwargs): self._set_slug() return super(SentryApp, self).save(*args, **kwargs) def is_installed_on(self, organization): return self.organizations.filter(pk=organization.pk).exists() def _set_slug(self): """ Matches ``name``, but in lowercase, dash form. >>> self._set_slug('My Cool App') >>> self.slug my-cool-app """ if not self.slug: self.slug = slugify(self.name)
class SentryApp(ParanoidModel, HasApiScopes): __core__ = True application = models.OneToOneField( 'sentry.ApiApplication', null=True, on_delete=models.SET_NULL, related_name='sentry_app', ) # Much of the OAuth system in place currently depends on a User existing. # This "proxy user" represents the SentryApp in those cases. proxy_user = models.OneToOneField('sentry.User', null=True, on_delete=models.SET_NULL, related_name='sentry_app') # The Organization the Sentry App was created in "owns" it. Members of that # Org have differing access, dependent on their role within the Org. owner = FlexibleForeignKey('sentry.Organization', related_name='owned_sentry_apps') name = models.TextField() slug = models.CharField(max_length=SENTRY_APP_SLUG_MAX_LENGTH, unique=True) status = BoundedPositiveIntegerField( default=SentryAppStatus.UNPUBLISHED, choices=SentryAppStatus.as_choices(), db_index=True, ) uuid = models.CharField(max_length=64, default=default_uuid) redirect_url = models.URLField(null=True) webhook_url = models.URLField() # does the application subscribe to `event.alert`, # meaning can it be used in alert rules as a {service} ? is_alertable = models.BooleanField(default=False) events = ArrayField(of=models.TextField, null=True) overview = models.TextField(null=True) date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_sentryapp' @classmethod def visible_for_user(cls, user): if user.is_superuser: return cls.objects.all() return cls.objects.filter( Q(status=SentryAppStatus.PUBLISHED) | Q(owner__in=user.get_orgs()), ) @property def organizations(self): if not self.pk: return Organization.objects.none() return Organization \ .objects \ .select_related('sentry_app_installations') \ .filter(sentry_app_installations__sentry_app_id=self.id) @property def teams(self): from sentry.models import Team if not self.pk: return Team.objects.none() return Team.objects.filter(organization__in=self.organizations) @property def is_published(self): return self.status == SentryAppStatus.PUBLISHED def save(self, *args, **kwargs): self._set_slug() return super(SentryApp, self).save(*args, **kwargs) def is_installed_on(self, organization): return self.organizations.filter(pk=organization.pk).exists() def _set_slug(self): """ Matches ``name``, but in lowercase, dash form. >>> self._set_slug('My Cool App') >>> self.slug my-cool-app """ if not self.slug: self.slug = slugify(self.name) def build_signature(self, body): secret = self.application.client_secret return hmac.new( key=secret.encode('utf-8'), msg=body.encode('utf-8'), digestmod=sha256, ).hexdigest()