Beispiel #1
0
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"
Beispiel #2
0
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]
Beispiel #3
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)
Beispiel #4
0
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)
Beispiel #5
0
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'),
        )
Beispiel #6
0
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
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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"
Beispiel #10
0
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"), )
Beispiel #11
0
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'), )
Beispiel #13
0
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
Beispiel #14
0
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"), )
Beispiel #15
0
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()
Beispiel #17
0
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'), )
Beispiel #18
0
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'), )
Beispiel #19
0
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"),)
Beispiel #20
0
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()
Beispiel #21
0
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'))
Beispiel #22
0
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,
        }
Beispiel #23
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.
    """
    __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'),)
Beispiel #24
0
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')
Beispiel #25
0
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
        )
Beispiel #26
0
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}
Beispiel #27
0
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)
Beispiel #28
0
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)
Beispiel #29
0
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()
Beispiel #30
0
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