class SentryAppWebhookError(Model): __core__ = False date_added = models.DateTimeField(db_index=True, default=timezone.now) sentry_app = FlexibleForeignKey("sentry.SentryApp", related_name="webhook_errors") organization = FlexibleForeignKey( "sentry.Organization", related_name="sentry_app_webhook_errors" ) request_body = EncryptedJsonField() request_headers = EncryptedJsonField() event_type = models.CharField(max_length=64) # We need to store this rather than just fetch it from the related sentry app in case the URL is changed webhook_url = models.URLField() response_body = models.TextField() response_code = models.PositiveSmallIntegerField() class Meta: app_label = "sentry" db_table = "sentry_sentryappwebhookerror"
class IdentityProvider(Model): """ An IdentityProvider is an instance of a provider. The IdentityProvider is unique on the type of provider (eg gtihub, slack, google, etc) and the organization which has configured that provider for it's users. A SAML identity provide might look like this, type: onelogin, instance: acme-org.onelogin.com. """ __core__ = False type = models.CharField(max_length=64) organization = FlexibleForeignKey('sentry.Organization') config = EncryptedJsonField() class Meta: app_label = 'sentry' db_table = 'sentry_identityprovider' unique_together = (('type', 'organization'), ) @classmethod def get(cls, type, instance): # TODO(dcramer): add caching return cls.objects.get_or_create( type=type, instance=instance, )[0]
class IdentityProvider(Model): """ An IdentityProvider is an instance of a provider. The IdentityProvider is unique on the type of provider (eg github, slack, google, etc). A SAML identity provide might look like this, type: onelogin, instance: acme-org.onelogin.com. """ __include_in_export__ = False type = models.CharField(max_length=64) config = EncryptedJsonField() date_added = models.DateTimeField(default=timezone.now, null=True) external_id = models.CharField(max_length=64, null=True) class Meta: app_label = "sentry" db_table = "sentry_identityprovider" unique_together = (("type", "external_id"), ) def get_provider(self): from sentry.identity import get return get(self.type)
class Identity(Model): """ A verified link between a user and a third party identity. """ __include_in_export__ = False idp = FlexibleForeignKey("sentry.IdentityProvider") user = FlexibleForeignKey(settings.AUTH_USER_MODEL) external_id = models.TextField() data = EncryptedJsonField() status = BoundedPositiveIntegerField(default=IdentityStatus.UNKNOWN) scopes = ArrayField() date_verified = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) objects = IdentityManager() class Meta: app_label = "sentry" db_table = "sentry_identity" unique_together = (("idp", "external_id"), ("idp", "user")) def get_provider(self): from sentry.identity import get return get(self.idp.type)
class IdentityProvider(Model): """ An IdentityProvider is an instance of a provider. The IdentityProvider is unique on the type of provider (eg gtihub, slack, google, etc) and the organization which has configured that provider for it's users. A SAML identity provide might look like this, type: onelogin, instance: acme-org.onelogin.com. """ __core__ = False type = models.CharField(max_length=64) organization = FlexibleForeignKey('sentry.Organization') config = EncryptedJsonField() date_added = models.DateTimeField(default=timezone.now, null=True) external_id = models.CharField(max_length=64, null=True) class Meta: app_label = 'sentry' db_table = 'sentry_identityprovider' unique_together = ( ('type', 'organization'), ('type', 'organization', 'external_id'), )
class MonitorCheckIn(Model): __core__ = False guid = UUIDField(unique=True, auto_add=True) project_id = BoundedPositiveIntegerField(db_index=True) monitor = FlexibleForeignKey("sentry.Monitor") location = FlexibleForeignKey("sentry.MonitorLocation", null=True) status = BoundedPositiveIntegerField(default=0, choices=CheckInStatus.as_choices()) config = EncryptedJsonField(default=dict) duration = BoundedPositiveIntegerField(null=True) date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) objects = BaseManager(cache_fields=("guid", )) class Meta: app_label = "sentry" db_table = "sentry_monitorcheckin" __repr__ = sane_repr("guid", "project_id", "status") def save(self, *args, **kwargs): if not self.date_added: self.date_added = timezone.now() if not self.date_updated: self.date_updated = self.date_added return super(MonitorCheckIn, self).save(*args, **kwargs) # XXX(dcramer): BaseModel is trying to automatically set date_updated which is not # what we want to happen, so kill it here def _update_timestamps(self): pass
class Integration(Model): __core__ = False organizations = models.ManyToManyField( "sentry.Organization", related_name="integrations", through=OrganizationIntegration ) projects = models.ManyToManyField( "sentry.Project", related_name="integrations", through=ProjectIntegration ) provider = models.CharField(max_length=64) external_id = models.CharField(max_length=64) name = models.CharField(max_length=200) # metadata might be used to store things like credentials, but it should NOT # be used to store organization-specific information, as the Integration # instance is shared among multiple organizations metadata = EncryptedJsonField(default=dict) status = BoundedPositiveIntegerField( default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices(), null=True ) date_added = models.DateTimeField(default=timezone.now, null=True) class Meta: app_label = "sentry" db_table = "sentry_integration" unique_together = (("provider", "external_id"),) def get_provider(self): from sentry import integrations return integrations.get(self.provider) def get_installation(self, organization_id, **kwargs): return self.get_provider().get_installation(self, organization_id, **kwargs) def has_feature(self, feature): return feature in self.get_provider().features def add_organization(self, organization, user=None, default_auth_id=None): """ Add an organization to this integration. Returns False if the OrganizationIntegration was not created """ try: org_integration, created = OrganizationIntegration.objects.get_or_create( organization_id=organization.id, integration_id=self.id, defaults={"default_auth_id": default_auth_id, "config": {}}, ) if not created and default_auth_id: org_integration.update(default_auth_id=default_auth_id) except IntegrityError: return False else: integration_added.send_robust( integration=self, organization=organization, user=user, sender=self.__class__ ) return org_integration
class Identity(Model): """ A verified link between a user and a third party identity. """ __core__ = False idp = FlexibleForeignKey("sentry.IdentityProvider") user = FlexibleForeignKey(settings.AUTH_USER_MODEL) external_id = models.TextField() data = EncryptedJsonField() status = BoundedPositiveIntegerField(default=IdentityStatus.UNKNOWN) scopes = ArrayField() date_verified = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) class Meta: app_label = "sentry" db_table = "sentry_identity" unique_together = (("idp", "external_id"), ("idp", "user")) def get_provider(self): from sentry.identity import get return get(self.idp.type) @classmethod def reattach(cls, idp, external_id, user, defaults): """ Removes identities under `idp` associated with either `external_id` or `user` and creates a new identity linking them. """ lookup = Q(external_id=external_id) | Q(user=user) Identity.objects.filter(lookup, idp=idp).delete() logger.info( "deleted-identity", extra={ "external_id": external_id, "idp_id": idp.id, "user_id": user.id }, ) identity_model = Identity.objects.create(idp=idp, user=user, external_id=external_id, **defaults) logger.info( "created-identity", extra={ "idp_id": idp.id, "external_id": external_id, "object_id": identity_model.id, "user_id": user.id, }, ) return identity_model
class SentryAppComponent(Model): __core__ = True uuid = UUIDField(unique=True, auto_add=True) sentry_app = FlexibleForeignKey("sentry.SentryApp", related_name="components") type = models.CharField(max_length=64) schema = EncryptedJsonField() class Meta: app_label = "sentry" db_table = "sentry_sentryappcomponent"
class ProjectIntegration(Model): __core__ = False project = FlexibleForeignKey("sentry.Project") integration = FlexibleForeignKey("sentry.Integration") config = EncryptedJsonField(default=dict) class Meta: app_label = "sentry" db_table = "sentry_projectintegration" unique_together = (("project", "integration"), )
class ProjectIntegration(Model): __core__ = False project = FlexibleForeignKey('sentry.Project') integration = FlexibleForeignKey('sentry.Integration') config = EncryptedJsonField(default=dict) class Meta: app_label = 'sentry' db_table = 'sentry_projectintegration' unique_together = (('project', 'integration'), )
class OrganizationIntegration(Model): __core__ = False organization = FlexibleForeignKey('sentry.Organization') integration = FlexibleForeignKey('sentry.Integration') config = EncryptedJsonField(default=lambda: {}) default_auth_id = BoundedPositiveIntegerField(db_index=True, null=True) class Meta: app_label = 'sentry' db_table = 'sentry_organizationintegration' unique_together = (('organization', 'integration'), )
class Integration(Model): __core__ = False organizations = models.ManyToManyField('sentry.Organization', related_name='integrations', through=OrganizationIntegration) projects = models.ManyToManyField('sentry.Project', related_name='integrations', through=ProjectIntegration) provider = models.CharField(max_length=64) external_id = models.CharField(max_length=64) name = models.CharField(max_length=200) # metadata might be used to store things like credentials, but it should NOT # be used to store organization-specific information, as the Integration # instance is shared among multiple organizations metadata = EncryptedJsonField(default=lambda: {}) date_added = models.DateTimeField(default=timezone.now, null=True) class Meta: app_label = 'sentry' db_table = 'sentry_integration' unique_together = (('provider', 'external_id'),) def get_provider(self): from sentry import integrations return integrations.get(self.provider) def get_installation(self): return self.get_provider().get_installation(self) def has_feature(self, feature): return feature in self.get_provider().features def add_organization(self, organization_id, default_auth_id=None, config=None): """ Add an organization to this integration. Returns True if the OrganizationIntegration was created """ try: with transaction.atomic(): OrganizationIntegration.objects.create( organization_id=organization_id, integration_id=self.id, default_auth_id=default_auth_id, config=config or {}, ) except IntegrityError: return False else: return True
class OrganizationIntegration(DefaultFieldsModel): __core__ = False organization = FlexibleForeignKey("sentry.Organization") integration = FlexibleForeignKey("sentry.Integration") config = EncryptedJsonField(default=dict) default_auth_id = BoundedPositiveIntegerField(db_index=True, null=True) status = BoundedPositiveIntegerField(default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices()) class Meta: app_label = "sentry" db_table = "sentry_organizationintegration" unique_together = (("organization", "integration"), )
class ProjectIntegration(Model): """ TODO(epurkhiser): This is deprecated and will be removed soon. Do not use Project Integrations. """ __include_in_export__ = False project = FlexibleForeignKey("sentry.Project") integration = FlexibleForeignKey("sentry.Integration") config = EncryptedJsonField(default=dict) class Meta: app_label = "sentry" db_table = "sentry_projectintegration" unique_together = (("project", "integration"), )
class AuthIdentity(Model): __core__ = True user = FlexibleForeignKey(settings.AUTH_USER_MODEL) auth_provider = FlexibleForeignKey('sentry.AuthProvider') ident = models.CharField(max_length=128) data = EncryptedJsonField() last_verified = models.DateTimeField(default=timezone.now) last_synced = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_authidentity' unique_together = (('auth_provider', 'ident'), ('auth_provider', 'user')) __repr__ = sane_repr('user_id', 'auth_provider_id') def __unicode__(self): return self.ident def get_audit_log_data(self): return { 'user_id': self.user_id, 'data': self.data, } # TODO(dcramer): we'd like to abstract this so there's a central Role object # and it doesnt require two composite db objects to talk to each other def is_valid(self, member): if getattr(member.flags, 'sso:invalid'): return False if not getattr(member.flags, 'sso:linked'): return False if not self.last_verified: return False if self.last_verified < timezone.now() - timedelta(hours=24): return False return True def get_display_name(self): return self.user.get_display_name() def get_label(self): return self.user.get_label()
class Identity(Model): """ A unique identity with an external provider (e.g. GitHub). """ __core__ = False idp = FlexibleForeignKey('sentry.IdentityProvider') external_id = models.CharField(max_length=64) data = EncryptedJsonField() status = BoundedPositiveIntegerField(default=IdentityStatus.UNKNOWN, ) scopes = ArrayField() date_verified = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_identity' unique_together = (('idp', 'external_id'), )
class OrganizationIntegration(Model): __core__ = False organization = FlexibleForeignKey('sentry.Organization') integration = FlexibleForeignKey('sentry.Integration') config = EncryptedJsonField(default=dict) default_auth_id = BoundedPositiveIntegerField(db_index=True, null=True) date_added = models.DateTimeField(default=timezone.now, null=True) status = BoundedPositiveIntegerField( default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices(), ) class Meta: app_label = 'sentry' db_table = 'sentry_organizationintegration' unique_together = (('organization', 'integration'), )
class OrganizationIntegration(DefaultFieldsModel): __include_in_export__ = False organization = FlexibleForeignKey("sentry.Organization") integration = FlexibleForeignKey("sentry.Integration") config = EncryptedJsonField(default=dict) default_auth_id = BoundedPositiveIntegerField(db_index=True, null=True) status = BoundedPositiveIntegerField( default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices() ) # After the grace period, we will mark the status as disabled. grace_period_end = models.DateTimeField(null=True, blank=True, db_index=True) class Meta: app_label = "sentry" db_table = "sentry_organizationintegration" unique_together = (("organization", "integration"),)
class AuthIdentity(Model): __include_in_export__ = True user = FlexibleForeignKey(settings.AUTH_USER_MODEL) auth_provider = FlexibleForeignKey("sentry.AuthProvider") ident = models.CharField(max_length=128) data = EncryptedJsonField() last_verified = models.DateTimeField(default=timezone.now) last_synced = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) class Meta: app_label = "sentry" db_table = "sentry_authidentity" unique_together = (("auth_provider", "ident"), ("auth_provider", "user")) __repr__ = sane_repr("user_id", "auth_provider_id") def __str__(self): return self.ident def get_audit_log_data(self): return {"user_id": self.user_id, "data": self.data} # TODO(dcramer): we'd like to abstract this so there's a central Role object # and it doesnt require two composite db objects to talk to each other def is_valid(self, member): if getattr(member.flags, "sso:invalid"): return False if not getattr(member.flags, "sso:linked"): return False if not self.last_verified: return False if self.last_verified < timezone.now() - timedelta(hours=24): return False return True def get_display_name(self): return self.user.get_display_name() def get_label(self): return self.user.get_label()
class Identity(Model): """ A verified link between a user and a third party identity. """ __core__ = False idp = FlexibleForeignKey('sentry.IdentityProvider') user = FlexibleForeignKey(settings.AUTH_USER_MODEL) external_id = models.CharField(max_length=64) data = EncryptedJsonField() status = BoundedPositiveIntegerField(default=IdentityStatus.UNKNOWN) scopes = ArrayField() date_verified = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_identity' unique_together = (('idp', 'external_id'), ('idp', 'user'))
class AuthProvider(Model): __core__ = True organization = FlexibleForeignKey('sentry.Organization', unique=True) provider = models.CharField(max_length=128) config = EncryptedJsonField() date_added = models.DateTimeField(default=timezone.now) sync_time = BoundedPositiveIntegerField(null=True) last_sync = models.DateTimeField(null=True) default_role = BoundedPositiveIntegerField(default=50) default_global_access = models.BooleanField(default=True) # TODO(dcramer): ManyToMany has the same issue as ForeignKey and we need # to either write our own which works w/ BigAuto or switch this to use # through. default_teams = models.ManyToManyField('sentry.Team', blank=True) flags = BitField(flags=( ('allow_unlinked', 'Grant access to members who have not linked SSO accounts.'), ), default=0) class Meta: app_label = 'sentry' db_table = 'sentry_authprovider' __repr__ = sane_repr('organization_id', 'provider') def __unicode__(self): return self.provider def get_provider(self): from sentry.auth import manager return manager.get(self.provider, **self.config) def get_audit_log_data(self): return { 'provider': self.provider, 'config': self.config, }
class IdentityProvider(Model): """ An IdentityProvider is an instance of a provider. The IdentityProvider is unique on the type of provider (eg github, slack, google, etc). A SAML identity provide might look like this, type: onelogin, instance: acme-org.onelogin.com. """ __core__ = False type = models.CharField(max_length=64) config = EncryptedJsonField() date_added = models.DateTimeField(default=timezone.now, null=True) external_id = models.CharField(max_length=64, null=True) class Meta: app_label = 'sentry' db_table = 'sentry_identityprovider' unique_together = (('type', 'external_id'),)
class MonitorCheckIn(Model): __core__ = True guid = UUIDField(unique=True, auto_add=True) project_id = BoundedPositiveIntegerField(db_index=True) monitor = FlexibleForeignKey('sentry.Monitor') location = FlexibleForeignKey('sentry.MonitorLocation', null=True) status = BoundedPositiveIntegerField( default=0, choices=CheckInStatus.as_choices(), ) config = EncryptedJsonField(default=dict) duration = BoundedPositiveIntegerField(null=True) date_added = models.DateTimeField(default=timezone.now) date_updated = models.DateTimeField(default=timezone.now) objects = BaseManager(cache_fields=('guid', )) class Meta: app_label = 'sentry' db_table = 'sentry_monitorcheckin' __repr__ = sane_repr('guid', 'project_id', 'status')
class Identity(Model): """ A verified link between a user and a third party identity. """ __core__ = False idp = FlexibleForeignKey('sentry.IdentityProvider') user = FlexibleForeignKey(settings.AUTH_USER_MODEL) external_id = models.CharField(max_length=64) data = EncryptedJsonField() status = BoundedPositiveIntegerField(default=IdentityStatus.UNKNOWN) scopes = ArrayField() date_verified = models.DateTimeField(default=timezone.now) date_added = models.DateTimeField(default=timezone.now) class Meta: app_label = 'sentry' db_table = 'sentry_identity' unique_together = (('idp', 'external_id'), ('idp', 'user')) def get_provider(self): from sentry.identity import get return get(self.idp.type) @classmethod def reattach(cls, idp, external_id, user, defaults): """ Removes identities under `idp` associated with either `external_id` or `user` and creates a new identity linking them. """ lookup = Q(external_id=external_id) | Q(user=user) Identity.objects.filter(lookup, idp=idp).delete() return Identity.objects.create( idp=idp, user=user, external_id=external_id, **defaults )
class AuthProvider(Model): __include_in_export__ = True organization = FlexibleForeignKey("sentry.Organization", unique=True) provider = models.CharField(max_length=128) config = EncryptedJsonField() date_added = models.DateTimeField(default=timezone.now) sync_time = BoundedPositiveIntegerField(null=True) last_sync = models.DateTimeField(null=True) default_role = BoundedPositiveIntegerField(default=50) default_global_access = models.BooleanField(default=True) # TODO(dcramer): ManyToMany has the same issue as ForeignKey and we need # to either write our own which works w/ BigAuto or switch this to use # through. default_teams = models.ManyToManyField("sentry.Team", blank=True) flags = BitField( flags=( ("allow_unlinked", "Grant access to members who have not linked SSO accounts."), ("scim_enabled", "Enable SCIM for member and team provisioning and syncing"), ), default=0, ) class Meta: app_label = "sentry" db_table = "sentry_authprovider" __repr__ = sane_repr("organization_id", "provider") def __str__(self): return self.provider def get_provider(self): from sentry.auth import manager return manager.get(self.provider, **self.config) def get_scim_token(self): from sentry.models import SentryAppInstallationForProvider if self.flags.scim_enabled: return SentryAppInstallationForProvider.get_token( self.organization, f"{self.provider}_scim" ) else: logger.warning( "SCIM disabled but tried to access token", extra={"organization_id": self.organization.id}, ) return None def get_scim_url(self): if self.flags.scim_enabled: url_prefix = options.get("system.url-prefix") return f"{url_prefix}/api/0/organizations/{self.organization.slug}/scim/v2/" else: return None def enable_scim(self, user): from sentry.mediators.sentry_apps import InternalCreator from sentry.models import SentryAppInstallation, SentryAppInstallationForProvider if ( not self.get_provider().can_use_scim(self.organization, user) or self.flags.scim_enabled is True ): logger.warning( "SCIM already enabled", extra={"organization_id": self.organization.id}, ) return # check if we have a scim app already if SentryAppInstallationForProvider.objects.filter( organization=self.organization, provider="okta_scim" ).exists(): logger.warning( "SCIM installation already exists", extra={"organization_id": self.organization.id}, ) return data = { "name": "SCIM Internal Integration", "author": "Auto-generated by Sentry", "organization": self.organization, "overview": SCIM_INTERNAL_INTEGRATION_OVERVIEW, "user": user, "scopes": [ "member:read", "member:write", "member:admin", "team:write", "team:admin", ], } # create the internal integration and link it to the join table sentry_app = InternalCreator.run(**data) sentry_app_installation = SentryAppInstallation.objects.get(sentry_app=sentry_app) SentryAppInstallationForProvider.objects.create( sentry_app_installation=sentry_app_installation, organization=self.organization, provider=f"{self.provider}_scim", ) self.flags.scim_enabled = True def disable_scim(self, user): from sentry.mediators.sentry_apps import Destroyer from sentry.models import SentryAppInstallationForProvider if self.flags.scim_enabled: install = SentryAppInstallationForProvider.objects.get( organization=self.organization, provider=f"{self.provider}_scim" ) Destroyer.run(sentry_app=install.sentry_app_installation.sentry_app, user=user) self.flags.scim_enabled = False def get_audit_log_data(self): return {"provider": self.provider, "config": self.config}
class Integration(DefaultFieldsModel): __core__ = False organizations = models.ManyToManyField("sentry.Organization", related_name="integrations", through=OrganizationIntegration) projects = models.ManyToManyField("sentry.Project", related_name="integrations", through=ProjectIntegration) provider = models.CharField(max_length=64) external_id = models.CharField(max_length=64) name = models.CharField(max_length=200) # metadata might be used to store things like credentials, but it should NOT # be used to store organization-specific information, as the Integration # instance is shared among multiple organizations metadata = EncryptedJsonField(default=dict) status = BoundedPositiveIntegerField(default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices(), null=True) class Meta: app_label = "sentry" db_table = "sentry_integration" unique_together = (("provider", "external_id"), ) def get_provider(self): from sentry import integrations return integrations.get(self.provider) def get_installation(self, organization_id, **kwargs): return self.get_provider().get_installation(self, organization_id, **kwargs) def has_feature(self, feature): return feature in self.get_provider().features def add_organization(self, organization, user=None, default_auth_id=None): """ Add an organization to this integration. Returns False if the OrganizationIntegration was not created """ try: org_integration, created = OrganizationIntegration.objects.get_or_create( organization_id=organization.id, integration_id=self.id, defaults={ "default_auth_id": default_auth_id, "config": {} }, ) # TODO(Steve): add audit log if created if not created and default_auth_id: org_integration.update(default_auth_id=default_auth_id) except IntegrityError: logger.info( "add-organization-integrity-error", extra={ "organization_id": organization.id, "integration_id": self.id, "default_auth_id": default_auth_id, }, ) return False else: integration_added.send_robust(integration=self, organization=organization, user=user, sender=self.__class__) return org_integration def reauthorize(self, data): """ The structure of `data` depends on the `build_integration` method on the integration provider. Each provider may have their own way of reauthorizing the integration. """ if self.provider == "slack": metadata = data.get("metadata", {}) metadata["old_access_token"] = self.metadata["access_token"] self.update(metadata=metadata)
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 Integration(Model): __core__ = False organizations = models.ManyToManyField('sentry.Organization', related_name='integrations', through=OrganizationIntegration) projects = models.ManyToManyField('sentry.Project', related_name='integrations', through=ProjectIntegration) provider = models.CharField(max_length=64) external_id = models.CharField(max_length=64) name = models.CharField(max_length=200) # metadata might be used to store things like credentials, but it should NOT # be used to store organization-specific information, as the Integration # instance is shared among multiple organizations metadata = EncryptedJsonField(default=dict) status = BoundedPositiveIntegerField( default=ObjectStatus.VISIBLE, choices=ObjectStatus.as_choices(), null=True, ) date_added = models.DateTimeField(default=timezone.now, null=True) class Meta: app_label = 'sentry' db_table = 'sentry_integration' unique_together = (('provider', 'external_id'), ) def get_provider(self): from sentry import integrations return integrations.get(self.provider) def get_installation(self, organization_id, **kwargs): return self.get_provider().get_installation(self, organization_id, **kwargs) def has_feature(self, feature): return feature in self.get_provider().features def add_organization(self, organization, user=None, default_auth_id=None): """ Add an organization to this integration. Returns False if the OrganizationIntegration was not created """ # TODO(adhiraj): Remove when callsites in sentry-plugins are updated. if isinstance(organization, int): from sentry.models import Organization organization = Organization.objects.get(id=organization) try: with transaction.atomic(): integration = OrganizationIntegration.objects.create( organization_id=organization.id, integration_id=self.id, default_auth_id=default_auth_id, config={}, ) except IntegrityError: return False else: integration_added.send_robust( integration=self, organization=organization, user=user, sender=self.__class__, ) return integration