Example #1
0
    def _finish_login_pipeline(self, identity):
        """
        The login flow executes both with anonymous and authenticated users.

        Upon completion a few branches exist:

        If the identity is already linked, the user should be logged in
        and redirected immediately.

        Otherwise, the user is presented with a confirmation window. That window
        will show them the new account that will be created, and if they're
        already authenticated an optional button to associate the identity with
        their account.
        """
        auth_provider = self.auth_provider
        lock = locks.get(
            'sso:auth:{}:{}'.format(
                auth_provider.id,
                md5(unicode(identity['id'])).hexdigest(),
            ),
            duration=5,
        )
        with TimedRetryPolicy(5)(lock.acquire):
            try:
                auth_identity = AuthIdentity.objects.get(
                    auth_provider=auth_provider,
                    ident=identity['id'],
                )
            except AuthIdentity.DoesNotExist:
                return self._handle_unknown_identity(identity)
            return self._handle_existing_identity(auth_identity, identity)
Example #2
0
    def _finish_login_pipeline(self, identity):
        """
        The login flow executes both with anonymous and authenticated users.

        Upon completion a few branches exist:

        If the identity is already linked, the user should be logged in
        and redirected immediately.

        Otherwise, the user is presented with a confirmation window. That window
        will show them the new account that will be created, and if they're
        already authenticated an optional button to associate the identity with
        their account.
        """
        auth_provider = self.auth_provider
        user_id = identity['id']

        lock = locks.get(
            'sso:auth:{}:{}'.format(
                auth_provider.id,
                md5_text(user_id).hexdigest(),
            ),
            duration=5,
        )
        with TimedRetryPolicy(5)(lock.acquire):
            try:
                auth_identity = AuthIdentity.objects.select_related('user').get(
                    auth_provider=auth_provider,
                    ident=user_id,
                )
            except AuthIdentity.DoesNotExist:
                auth_identity = None

            # Handle migration of identity keys
            if not auth_identity and isinstance(user_id, MigratingIdentityId):
                try:
                    auth_identity = AuthIdentity.objects.select_related('user').get(
                        auth_provider=auth_provider,
                        ident=user_id.legacy_id,
                    )
                    auth_identity.update(ident=user_id.id)
                except AuthIdentity.DoesNotExist:
                    auth_identity = None

            if not auth_identity:
                return self._handle_unknown_identity(identity)

            # If the User attached to this AuthIdentity is not active,
            # we want to clobber the old account and take it over, rather than
            # getting logged into the inactive account.
            if not auth_identity.user.is_active:

                # Current user is also not logged in, so we have to
                # assume unknown.
                if not self.request.user.is_authenticated():
                    return self._handle_unknown_identity(identity)

                auth_identity = self._handle_attach_identity(identity)

            return self._handle_existing_identity(auth_identity, identity)
Example #3
0
    def test_commits_lock_conflict(self):
        user = self.create_user(is_staff=False, is_superuser=False)
        org = self.create_organization()
        org.flags.allow_joinleave = False
        org.save()

        team = self.create_team(organization=org)
        project = self.create_project(name="foo", organization=org, teams=[team])

        self.create_member(teams=[team], user=user, organization=org)
        self.login_as(user=user)

        release = self.create_release(project, self.user, version="1.2.1")
        release.add_project(project)

        # Simulate a concurrent request by using an existing release
        # that has its commit lock taken out.
        lock = locks.get(Release.get_lock_key(org.id, release.id), duration=10)
        lock.acquire()

        url = reverse(
            "sentry-api-0-organization-release-details",
            kwargs={"organization_slug": org.slug, "version": release.version},
        )
        response = self.client.put(url, data={"commits": [{"id": "a" * 40}, {"id": "b" * 40}]})
        assert response.status_code == 409, (response.status_code, response.content)
        assert "Release commits" in response.data["detail"]
Example #4
0
    def post(self, request, organization):
        """
        Add a invite request to Organization
        ````````````````````````````````````

        Creates an invite request given an email and sugested role / teams.

        :pparam string organization_slug: the slug of the organization the member will belong to
        :param string email: the email address to invite
        :param string role: the suggested role of the new member
        :param array teams: the suggested slugs of the teams the member should belong to.

        :auth: required
        """
        if not features.has("organizations:invite-members",
                            organization,
                            actor=request.user):
            return Response(
                {
                    "organization":
                    "Your organization is not allowed to invite members"
                },
                status=403)

        serializer = OrganizationMemberSerializer(
            data=request.data,
            context={
                "organization": organization,
                "allowed_roles": roles.get_all()
            },
        )

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data

        with transaction.atomic():
            om = OrganizationMember.objects.create(
                organization=organization,
                email=result["email"],
                role=result["role"],
                inviter=request.user,
                invite_status=InviteStatus.REQUESTED_TO_BE_INVITED.value,
            )

            if result["teams"]:
                lock = locks.get(u"org:member:{}".format(om.id), duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    save_team_assignments(om, result["teams"])

            self.create_audit_entry(
                request=request,
                organization_id=organization.id,
                target_object=om.id,
                data=om.get_audit_log_data(),
                event=AuditLogEntryEvent.INVITE_REQUEST_ADD,
            )

        return Response(serialize(om), status=201)
Example #5
0
    def _finish_login_pipeline(self, identity):
        """
        The login flow executes both with anonymous and authenticated users.

        Upon completion a few branches exist:

        If the identity is already linked, the user should be logged in
        and redirected immediately.

        Otherwise, the user is presented with a confirmation window. That window
        will show them the new account that will be created, and if they're
        already authenticated an optional button to associate the identity with
        their account.
        """
        auth_provider = self.auth_provider
        user_id = identity['id']

        lock = locks.get(
            'sso:auth:{}:{}'.format(
                auth_provider.id,
                md5_text(user_id).hexdigest(),
            ),
            duration=5,
        )
        with TimedRetryPolicy(5)(lock.acquire):
            try:
                auth_identity = AuthIdentity.objects.select_related('user').get(
                    auth_provider=auth_provider,
                    ident=user_id,
                )
            except AuthIdentity.DoesNotExist:
                auth_identity = None

            # Handle migration of identity keys
            if not auth_identity and isinstance(user_id, MigratingIdentityId):
                try:
                    auth_identity = AuthIdentity.objects.select_related('user').get(
                        auth_provider=auth_provider,
                        ident=user_id.legacy_id,
                    )
                    auth_identity.update(ident=user_id.id)
                except AuthIdentity.DoesNotExist:
                    auth_identity = None

            if not auth_identity:
                return self._handle_unknown_identity(identity)

            # If the User attached to this AuthIdentity is not active,
            # we want to clobber the old account and take it over, rather than
            # getting logged into the inactive account.
            if not auth_identity.user.is_active:

                # Current user is also not logged in, so we have to
                # assume unknown.
                if not self.request.user.is_authenticated():
                    return self._handle_unknown_identity(identity)

                auth_identity = self._handle_attach_identity(identity)

            return self._handle_existing_identity(auth_identity, identity)
Example #6
0
    def _ensure_blob(self, orm, file):
        from sentry.app import locks
        from sentry.utils.retries import TimedRetryPolicy

        File = orm['sentry.File']
        FileBlob = orm['sentry.FileBlob']

        lock = locks.get('fileblob:convert:{}'.format(file.checksum), duration=60)
        with TimedRetryPolicy(60 * 5)(lock.acquire):
            if not file.storage:
                return

            try:
                blob = FileBlob.objects.get(checksum=file.checksum)
            except FileBlob.DoesNotExist:
                blob = FileBlob.objects.create(
                    checksum=file.checksum,
                    storage=file.storage,
                    storage_options=file.storage_options,
                    path=file.path,
                    size=file.size,
                    timestamp=file.timestamp,
                )

            File.objects.filter(
                id=file.id,
            ).update(blob=blob)
            file.blob = blob
Example #7
0
    def _finish_login_pipeline(self, identity):
        """
        The login flow executes both with anonymous and authenticated users.

        Upon completion a few branches exist:

        If the identity is already linked, the user should be logged in
        and redirected immediately.

        Otherwise, the user is presented with a confirmation window. That window
        will show them the new account that will be created, and if they're
        already authenticated an optional button to associate the identity with
        their account.
        """
        auth_provider = self.auth_provider
        lock = locks.get(
            'sso:auth:{}:{}'.format(
                auth_provider.id,
                md5(unicode(identity['id'])).hexdigest(),
            ),
            duration=5,
        )
        with TimedRetryPolicy(5)(lock.acquire):
            try:
                auth_identity = AuthIdentity.objects.get(
                    auth_provider=auth_provider,
                    ident=identity['id'],
                )
            except AuthIdentity.DoesNotExist:
                return self._handle_unknown_identity(identity)
            return self._handle_existing_identity(auth_identity, identity)
Example #8
0
    def start_release(self, version, **values):
        values.setdefault('date_started', timezone.now())

        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id,
                                                version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id)
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values)

            release.add_project(self.project)
Example #9
0
    def test_release_locked(self):
        self.login_as(user=self.user)
        org = self.create_organization(owner=self.user, name="baz")
        repo = Repository.objects.create(name="example",
                                         provider="dummy",
                                         organization_id=org.id)

        old_release = Release.objects.create(organization_id=org.id,
                                             version="abcabcabc")
        commit = Commit.objects.create(organization_id=org.id,
                                       repository_id=repo.id,
                                       key="a" * 40)
        ReleaseHeadCommit.objects.create(organization_id=org.id,
                                         repository_id=repo.id,
                                         release=old_release,
                                         commit=commit)

        refs = [{"repository": repo.name, "commit": "b" * 40}]
        new_release = Release.objects.create(organization_id=org.id,
                                             version="12345678")

        lock = locks.get(Release.get_lock_key(org.id, new_release.id),
                         duration=10)
        lock.acquire()

        with self.tasks():
            fetch_commits(
                release_id=new_release.id,
                user_id=self.user.id,
                refs=refs,
                previous_release_id=old_release.id,
            )
        count_query = ReleaseHeadCommit.objects.filter(release=new_release)
        # No release commits should be made as the task should return early.
        assert count_query.count() == 0
Example #10
0
    def start_release(self, version, **values):
        values.setdefault('date_started', timezone.now())

        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id, version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values
                        )

            release.add_project(self.project)
Example #11
0
 def delete(self, *args, **kwargs):
     lock = locks.get('fileblob:upload:{}'.format(self.checksum),
                      duration=60 * 10)
     with TimedRetryPolicy(60)(lock.acquire):
         if self.path:
             self.deletefile(commit=False)
         super(FileBlob, self).delete(*args, **kwargs)
Example #12
0
 def delete(self, *args, **kwargs):
     lock = locks.get("fileblob:upload:{}".format(self.checksum), duration=UPLOAD_RETRY_TIME)
     with TimedRetryPolicy(UPLOAD_RETRY_TIME, metric_instance="lock.fileblob.delete")(
         lock.acquire
     ):
         super().delete(*args, **kwargs)
     if self.path:
         self.deletefile(commit=False)
Example #13
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get('slug:project', duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(self, self.name, organization=self.organization)
         super(Project, self).save(*args, **kwargs)
     else:
         super(Project, self).save(*args, **kwargs)
Example #14
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get("slug:team", duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(self,
                              self.name,
                              organization=self.organization)
     super().save(*args, **kwargs)
Example #15
0
 def get_security_token(self):
     lock = locks.get(self.get_lock_key(), duration=5)
     with TimedRetryPolicy(10)(lock.acquire):
         security_token = self.get_option('sentry:token', None)
         if security_token is None:
             security_token = uuid1().hex
             self.update_option('sentry:token', security_token)
         return security_token
Example #16
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get('slug:organization', duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(self, self.name, reserved=RESERVED_ORGANIZATION_SLUGS)
         super(Organization, self).save(*args, **kwargs)
     else:
         super(Organization, self).save(*args, **kwargs)
Example #17
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get('slug:project', duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(self, self.name, organization=self.organization)
         super(Project, self).save(*args, **kwargs)
     else:
         super(Project, self).save(*args, **kwargs)
Example #18
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get('slug:organization', duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(self, self.name, reserved=RESERVED_ORGANIZATION_SLUGS)
         super(Organization, self).save(*args, **kwargs)
     else:
         super(Organization, self).save(*args, **kwargs)
Example #19
0
 def get_security_token(self):
     lock = locks.get(self.get_lock_key(), duration=5)
     with TimedRetryPolicy(10)(lock.acquire):
         security_token = self.get_option('sentry:token', None)
         if security_token is None:
             security_token = uuid1().hex
             self.update_option('sentry:token', security_token)
         return security_token
Example #20
0
def delete_file(path, checksum, **kwargs):
    from sentry.models.file import get_storage, FileBlob
    from sentry.app import locks
    from sentry.utils.retries import TimedRetryPolicy

    lock = locks.get(u'fileblob:upload:{}'.format(checksum), duration=60 * 10)
    with TimedRetryPolicy(60)(lock.acquire):
        if not FileBlob.objects.filter(checksum=checksum).exists():
            get_storage().delete(path)
Example #21
0
def delete_file(path, checksum, **kwargs):
    from sentry.models.file import get_storage, FileBlob
    from sentry.app import locks
    from sentry.utils.retries import TimedRetryPolicy

    lock = locks.get(f"fileblob:upload:{checksum}", duration=60 * 10)
    with TimedRetryPolicy(60)(lock.acquire):
        if not FileBlob.objects.filter(checksum=checksum).exists():
            get_storage().delete(path)
Example #22
0
def _locked_blob(checksum):
    lock = locks.get(u'fileblob:upload:{}'.format(checksum), duration=UPLOAD_RETRY_TIME)
    with TimedRetryPolicy(UPLOAD_RETRY_TIME)(lock.acquire):
        # test for presence
        try:
            existing = FileBlob.objects.get(checksum=checksum)
        except FileBlob.DoesNotExist:
            existing = None
        yield existing
Example #23
0
def process_suspect_commits(event_id, event_platform, event_frames, group_id,
                            project_id, **kwargs):
    lock = locks.get(f"process-suspect-commits:{group_id}", duration=10)
    try:
        with lock.acquire():
            _process_suspect_commits(event_id, event_platform, event_frames,
                                     group_id, project_id, **kwargs)
    except UnableToAcquireLock:
        pass
Example #24
0
    def notify_if_ready(cls, deploy_id, fetch_complete=False):
        """
        create activity and send deploy notifications
        if they haven't been sent
        """
        from sentry.models import Activity, Environment, ReleaseCommit, ReleaseHeadCommit

        lock_key = cls.get_lock_key(deploy_id)
        lock = locks.get(lock_key, duration=30)
        with TimedRetryPolicy(10)(lock.acquire):
            deploy = cls.objects.filter(
                id=deploy_id,
            ).select_related('release').get()
            if deploy.notified:
                return

            release = deploy.release
            environment = Environment.objects.get(
                organization_id=deploy.organization_id,
                id=deploy.environment_id,
            )

            if not fetch_complete:
                release_has_commits = ReleaseCommit.objects.filter(
                    organization_id=release.organization_id,
                    release=release,
                ).exists()

                if not release_has_commits:
                    # check if we have head commits, which
                    # would indicate that we're waiting for
                    # fetch_commits to complete
                    if ReleaseHeadCommit.objects.filter(
                        organization_id=release.organization_id,
                        release=release,
                    ).exists():
                        return

            activity = None
            for project in deploy.release.projects.all():
                activity = Activity.objects.create(
                    type=Activity.DEPLOY,
                    project=project,
                    ident=release.version,
                    data={
                        'version': release.version,
                        'deploy_id': deploy.id,
                        'environment': environment.name,
                    },
                    datetime=deploy.date_finished,
                )
            # Somewhat hacky, only send notification for one
            # Deploy Activity record because it will cover all projects
            if activity is not None:
                activity.send_notification()
                deploy.update(notified=True)
Example #25
0
    def notify_if_ready(cls, deploy_id, fetch_complete=False):
        """
        create activity and send deploy notifications
        if they haven't been sent
        """
        from sentry.models import Activity, Environment, ReleaseCommit, ReleaseHeadCommit

        lock_key = cls.get_lock_key(deploy_id)
        lock = locks.get(lock_key, duration=30)
        with TimedRetryPolicy(10)(lock.acquire):
            deploy = cls.objects.filter(
                id=deploy_id,
            ).select_related('release').get()
            if deploy.notified:
                return

            release = deploy.release
            environment = Environment.objects.get(
                organization_id=deploy.organization_id,
                id=deploy.environment_id,
            )

            if not fetch_complete:
                release_has_commits = ReleaseCommit.objects.filter(
                    organization_id=release.organization_id,
                    release=release,
                ).exists()

                if not release_has_commits:
                    # check if we have head commits, which
                    # would indicate that we're waiting for
                    # fetch_commits to complete
                    if ReleaseHeadCommit.objects.filter(
                        organization_id=release.organization_id,
                        release=release,
                    ).exists():
                        return

            activity = None
            for project in deploy.release.projects.all():
                activity = Activity.objects.create(
                    type=Activity.DEPLOY,
                    project=project,
                    ident=release.version,
                    data={
                        'version': release.version,
                        'deploy_id': deploy.id,
                        'environment': environment.name,
                    },
                    datetime=deploy.date_finished,
                )
            # Somewhat hacky, only send notification for one
            # Deploy Activity record because it will cover all projects
            if activity is not None:
                activity.send_notification()
                deploy.update(notified=True)
Example #26
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get("slug:project", duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(
                 self, self.name, organization=self.organization, reserved=RESERVED_PROJECT_SLUGS
             )
         super(Project, self).save(*args, **kwargs)
     else:
         super(Project, self).save(*args, **kwargs)
     self.update_rev_for_option()
Example #27
0
 def get_webhook_secret(self, organization):
     lock = locks.get("bitbucket:webhook-secret:{}".format(organization.id), duration=60)
     with lock.acquire():
         secret = OrganizationOption.objects.get_value(
             organization=organization, key="bitbucket:webhook_secret"
         )
         if secret is None:
             secret = uuid4().hex + uuid4().hex
             OrganizationOption.objects.set_value(
                 organization=organization, key="bitbucket:webhook_secret", value=secret
             )
     return secret
Example #28
0
def _locked_blob(checksum, logger=nooplogger):
    logger.debug("_locked_blob.start", extra={"checksum": checksum})
    lock = locks.get("fileblob:upload:{}".format(checksum), duration=UPLOAD_RETRY_TIME)
    with TimedRetryPolicy(UPLOAD_RETRY_TIME, metric_instance="lock.fileblob.upload")(lock.acquire):
        logger.debug("_locked_blob.acquired", extra={"checksum": checksum})
        # test for presence
        try:
            existing = FileBlob.objects.get(checksum=checksum)
        except FileBlob.DoesNotExist:
            existing = None
        yield existing
    logger.debug("_locked_blob.end", extra={"checksum": checksum})
Example #29
0
def handle_group_owners(project, group, owners):
    """
    Stores group owners generated by `ProjectOwnership.get_autoassign_owners` in the
    `GroupOwner` model, and handles any diffing/changes of which owners we're keeping.
    :return:
    """
    from sentry.models.groupowner import GroupOwner, GroupOwnerType
    from sentry.models.team import Team
    from sentry.models.user import User

    lock = locks.get(f"groupowner-bulk:{group.id}", duration=10)
    try:
        with metrics.timer(
                "post_process.handle_group_owners"), sentry_sdk.start_span(
                    op="post_process.handle_group_owners"), lock.acquire():
            current_group_owners = GroupOwner.objects.filter(
                group=group, type=GroupOwnerType.OWNERSHIP_RULE.value)
            new_owners = {(type(owner), owner.id) for owner in owners}
            # Owners already in the database that we'll keep
            keeping_owners = set()
            for owner in current_group_owners:
                lookup_key = ((Team,
                               owner.team_id) if owner.team_id is not None else
                              (User, owner.user_id))
                if lookup_key not in new_owners:
                    owner.delete()
                else:
                    keeping_owners.add(lookup_key)

            new_group_owners = []

            for key in new_owners:
                if key not in keeping_owners:
                    owner_type, owner_id = key
                    user_id = None
                    team_id = None
                    if owner_type is User:
                        user_id = owner_id
                    if owner_type is Team:
                        team_id = owner_id
                    new_group_owners.append(
                        GroupOwner(
                            group=group,
                            type=GroupOwnerType.OWNERSHIP_RULE.value,
                            user_id=user_id,
                            team_id=team_id,
                            project=project,
                            organization=project.organization,
                        ))
            if new_group_owners:
                GroupOwner.objects.bulk_create(new_group_owners)
    except UnableToAcquireLock:
        pass
Example #30
0
    def get_or_create(cls, project, release, environment, datetime, **kwargs):
        cache_key = cls.get_cache_key(project.id, release.id, environment.id)

        instance = cache.get(cache_key)
        if instance is None:
            release_envs = list(
                cls.objects.filter(
                    release_id=release.id,
                    organization_id=project.organization_id,
                    environment_id=environment.id,
                ))
            if release_envs:
                instance = release_envs[0]
                for re in release_envs:
                    if re.project_id == project.id:
                        instance = re
                created = False
            else:
                lock_key = cls.get_lock_key(project.organization_id,
                                            release.id, environment.id)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        instance, created = cls.objects.get(
                            release_id=release.id,
                            organization_id=project.organization_id,
                            environment_id=environment.id,
                        ), False
                    except cls.DoesNotExist:
                        instance, created = cls.objects.create(
                            release_id=release.id,
                            project_id=project.id,
                            organization_id=project.organization_id,
                            environment_id=environment.id,
                            first_seen=datetime,
                            last_seen=datetime,
                        ), True
            cache.set(cache_key, instance, 3600)
        else:
            created = False

        # TODO(dcramer): this would be good to buffer, but until then we minimize
        # updates to once a minute, and allow Postgres to optimistically skip
        # it even if we can't
        if not created and instance.last_seen < datetime - timedelta(
                seconds=60):
            cls.objects.filter(
                id=instance.id,
                last_seen__lt=datetime - timedelta(seconds=60),
            ).update(last_seen=datetime, )
            instance.last_seen = datetime
            cache.set(cache_key, instance, 3600)
        return instance
Example #31
0
def _locked_blob(checksum, logger=nooplogger):
    logger.info('_locked_blob.start', extra={'checksum': checksum})
    lock = locks.get(u'fileblob:upload:{}'.format(checksum), duration=UPLOAD_RETRY_TIME)
    with TimedRetryPolicy(UPLOAD_RETRY_TIME, metric_instance='lock.fileblob.upload')(lock.acquire):
        logger.info('_locked_blob.acquired', extra={'checksum': checksum})
        # test for presence
        try:
            existing = FileBlob.objects.get(checksum=checksum)
        except FileBlob.DoesNotExist:
            existing = None
        yield existing
    logger.info('_locked_blob.end', extra={'checksum': checksum})
    def get_or_create(cls, project, release, environment, datetime, **kwargs):
        cache_key = cls.get_cache_key(project.id, release.id, environment.id)

        instance = cache.get(cache_key)
        if instance is None:
            release_envs = list(cls.objects.filter(
                release_id=release.id,
                organization_id=project.organization_id,
                environment_id=environment.id,
            ))
            if release_envs:
                instance = release_envs[0]
                for re in release_envs:
                    if re.project_id == project.id:
                        instance = re
                created = False
            else:
                lock_key = cls.get_lock_key(project.organization_id, release.id, environment.id)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        instance, created = cls.objects.get(
                            release_id=release.id,
                            organization_id=project.organization_id,
                            environment_id=environment.id,
                        ), False
                    except cls.DoesNotExist:
                        instance, created = cls.objects.create(
                            release_id=release.id,
                            project_id=project.id,
                            organization_id=project.organization_id,
                            environment_id=environment.id,
                            first_seen=datetime,
                            last_seen=datetime,
                        ), True
            cache.set(cache_key, instance, 3600)
        else:
            created = False

        # TODO(dcramer): this would be good to buffer, but until then we minimize
        # updates to once a minute, and allow Postgres to optimistically skip
        # it even if we can't
        if not created and instance.last_seen < datetime - timedelta(seconds=60):
            cls.objects.filter(
                id=instance.id,
                last_seen__lt=datetime - timedelta(seconds=60),
            ).update(
                last_seen=datetime,
            )
            instance.last_seen = datetime
            cache.set(cache_key, instance, 3600)
        return instance
Example #33
0
 def get_webhook_secret(self, organization):
     # TODO(LB): Revisit whether Integrations V3 should be using OrganizationOption for storage
     lock = locks.get(u"bitbucket:webhook-secret:{}".format(organization.id), duration=60)
     with lock.acquire():
         secret = OrganizationOption.objects.get_value(
             organization=organization, key="bitbucket:webhook_secret"
         )
         if secret is None:
             secret = generate_token()
             OrganizationOption.objects.set_value(
                 organization=organization, key="bitbucket:webhook_secret", value=secret
             )
     return secret
Example #34
0
def calculate_incident_suspects(incident_id):
    from sentry.incidents.logic import get_incident_suspect_commits

    lock = locks.get(u'incident:suspects:{}'.format(incident_id), duration=60 * 10)
    with TimedRetryPolicy(60)(lock.acquire):
        incident = Incident.objects.get(id=incident_id)
        suspect_commits = get_incident_suspect_commits(incident)
        with transaction.atomic():
            IncidentSuspectCommit.objects.filter(incident=incident).delete()
            IncidentSuspectCommit.objects.bulk_create([
                IncidentSuspectCommit(incident=incident, commit_id=commit_id, order=i)
                for i, commit_id in enumerate(suspect_commits)
            ])
Example #35
0
def process_pending():
    """
    Process pending buffers.
    """
    from sentry import buffer
    from sentry.app import locks

    lock = locks.get('buffer:process_pending', duration=60)
    try:
        with lock.acquire():
            buffer.process_pending()
    except UnableToAcquireLock as error:
        logger.warning('process_pending.fail', extra={'error': error})
Example #36
0
def process_pending():
    """
    Process pending buffers.
    """
    from sentry import buffer
    from sentry.app import locks

    lock = locks.get('buffer:process_pending', duration=60)
    try:
        with lock.acquire():
            buffer.process_pending()
    except UnableToAcquireLock as error:
        logger.warning('process_pending.fail', extra={'error': error})
Example #37
0
 def get_webhook_secret(self, organization):
     lock = locks.get("github:webhook-secret:{}".format(organization.id), duration=60)
     with lock.acquire():
         # TODO(dcramer): get_or_create would be a useful native solution
         secret = OrganizationOption.objects.get_value(
             organization=organization, key="github:webhook_secret"
         )
         if secret is None:
             secret = uuid4().hex + uuid4().hex
             OrganizationOption.objects.set_value(
                 organization=organization, key="github:webhook_secret", value=secret
             )
     return secret
Example #38
0
 def save(self, *args, **kwargs):
     if not self.slug:
         lock = locks.get('slug:project', duration=5)
         with TimedRetryPolicy(10)(lock.acquire):
             slugify_instance(
                 self,
                 self.name,
                 organization=self.organization,
                 reserved=RESERVED_PROJECT_SLUGS)
         super(Project, self).save(*args, **kwargs)
     else:
         super(Project, self).save(*args, **kwargs)
     self.update_rev_for_option()
Example #39
0
def upgrade(ctx, verbosity, traceback, noinput, lock, no_repair):
    "Perform any pending database migrations and upgrades."

    if lock:
        from sentry.app import locks
        from sentry.utils.locking import UnableToAcquireLock
        lock = locks.get('upgrade', duration=0)
        try:
            with lock.acquire():
                _upgrade(not noinput, traceback, verbosity, not no_repair)
        except UnableToAcquireLock:
            raise click.ClickException('Unable to acquire `upgrade` lock.')
    else:
        _upgrade(not noinput, traceback, verbosity, not no_repair)
Example #40
0
def upgrade(ctx, verbosity, traceback, noinput, lock, no_repair):
    "Perform any pending database migrations and upgrades."

    if lock:
        from sentry.app import locks
        from sentry.utils.locking import UnableToAcquireLock
        lock = locks.get('upgrade', duration=0)
        try:
            with lock.acquire():
                _upgrade(not noinput, traceback, verbosity, not no_repair)
        except UnableToAcquireLock:
            raise click.ClickException('Unable to acquire `upgrade` lock.')
    else:
        _upgrade(not noinput, traceback, verbosity, not no_repair)
Example #41
0
def enqueue_scheduled_jobs(**kwargs):
    from sentry.celery import app

    with locks.get('scheduler.process', duration=60).acquire():
        job_list = list(ScheduledJob.objects.filter(
            date_scheduled__lte=timezone.now(),
        )[:101])

        if len(job_list) > 100:
            logger.debug('More than 100 ScheduledJobs found.')

        for job in job_list:
            logger.debug('Sending scheduled job %s with payload %r', job.name, job.payload)
            app.send_task(job.name, kwargs=job.payload)
            job.delete()
 def get_webhook_secret(self, organization):
     lock = locks.get('bitbucket:webhook-secret:{}'.format(organization.id), duration=60)
     with lock.acquire():
         secret = OrganizationOption.objects.get_value(
             organization=organization,
             key='bitbucket:webhook_secret',
         )
         if secret is None:
             secret = uuid4().hex + uuid4().hex
             OrganizationOption.objects.set_value(
                 organization=organization,
                 key='bitbucket:webhook_secret',
                 value=secret,
             )
     return secret
Example #43
0
def enqueue_scheduled_jobs(**kwargs):
    from sentry.celery import app

    with locks.get("scheduler.process", duration=60).acquire():
        job_list = list(
            ScheduledJob.objects.filter(
                date_scheduled__lte=timezone.now())[:101])

        if len(job_list) > 100:
            logger.debug("More than 100 ScheduledJobs found.")

        for job in job_list:
            logger.debug("Sending scheduled job %s with payload %r", job.name,
                         job.payload)
            app.send_task(job.name, kwargs=job.payload)
            job.delete()
Example #44
0
 def get_webhook_secret(self, organization):
     lock = locks.get('github:webhook-secret:{}'.format(organization.id), duration=60)
     with lock.acquire():
         # TODO(dcramer): get_or_create would be a useful native solution
         secret = OrganizationOption.objects.get_value(
             organization=organization,
             key='github:webhook_secret',
         )
         if secret is None:
             secret = uuid4().hex + uuid4().hex
             OrganizationOption.objects.set_value(
                 organization=organization,
                 key='github:webhook_secret',
                 value=secret,
             )
     return secret
Example #45
0
 def get_webhook_secret(self, organization):
     # TODO(LB): Revisit whether Integrations V3 should be using OrganizationOption for storage
     lock = locks.get('bitbucket:webhook-secret:{}'.format(organization.id), duration=60)
     with lock.acquire():
         secret = OrganizationOption.objects.get_value(
             organization=organization,
             key='bitbucket:webhook_secret',
         )
         if secret is None:
             secret = uuid4().hex + uuid4().hex
             OrganizationOption.objects.set_value(
                 organization=organization,
                 key='bitbucket:webhook_secret',
                 value=secret,
             )
     return secret
Example #46
0
 def get_webhook_secret(self, organization):
     # TODO(LB): Revisit whether Integrations V3 should be using OrganizationOption for storage
     lock = locks.get(u'bitbucket:webhook-secret:{}'.format(organization.id), duration=60)
     with lock.acquire():
         secret = OrganizationOption.objects.get_value(
             organization=organization,
             key='bitbucket:webhook_secret',
         )
         if secret is None:
             secret = generate_token()
             OrganizationOption.objects.set_value(
                 organization=organization,
                 key='bitbucket:webhook_secret',
                 value=secret,
             )
     return secret
Example #47
0
def process_pending(partition=None):
    """
    Process pending buffers.
    """
    from sentry import buffer
    from sentry.app import locks

    if partition is None:
        lock_key = 'buffer:process_pending'
    else:
        lock_key = 'buffer:process_pending:%d' % partition

    lock = locks.get(lock_key, duration=60)

    try:
        with lock.acquire():
            buffer.process_pending(partition=partition)
    except UnableToAcquireLock as error:
        logger.warning('process_pending.fail', extra={'error': error, 'partition': partition})
Example #48
0
    def from_file(cls, fileobj):
        """
        Retrieve a list of FileBlobIndex instances for the given file.

        If not already present, this will cause it to be stored.

        >>> blobs = FileBlob.from_file(fileobj)
        """
        size = 0

        checksum = sha1(b'')
        for chunk in fileobj:
            size += len(chunk)
            checksum.update(chunk)
        checksum = checksum.hexdigest()

        # TODO(dcramer): the database here is safe, but if this lock expires
        # and duplicate files are uploaded then we need to prune one
        lock = locks.get(u'fileblob:upload:{}'.format(checksum), duration=60 * 10)
        with TimedRetryPolicy(60)(lock.acquire):
            # test for presence
            try:
                existing = FileBlob.objects.get(checksum=checksum)
            except FileBlob.DoesNotExist:
                pass
            else:
                return existing

            blob = cls(
                size=size,
                checksum=checksum,
            )

            blob.path = cls.generate_unique_path(blob.timestamp)

            storage = get_storage()
            storage.save(blob.path, fileobj)
            blob.save()

        metrics.timing('filestore.blob-size', size)
        return blob
Example #49
0
    def finish_release(self, version, **values):
        values.setdefault('date_released', timezone.now())
        affected = Release.objects.filter(
            version=version,
            organization_id=self.project.organization_id,
            projects=self.project,
        ).update(**values)
        if not affected:
            release = Release.objects.filter(
                version=version,
                organization_id=self.project.organization_id,
            ).first()
            if release:
                release.update(**values)
            else:
                lock_key = Release.get_lock_key(self.project.organization_id, version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            version=version,
                            organization_id=self.project.organization_id,
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            version=version,
                            organization_id=self.project.organization_id,
                            **values
                        )
            release.add_project(self.project)

        activity = Activity.objects.create(
            type=Activity.RELEASE,
            project=self.project,
            ident=version,
            data={'version': version},
            datetime=values['date_released'],
        )
        activity.send_notification()
Example #50
0
    def get_or_create(cls, project, name):
        name = name or ''

        cache_key = cls.get_cache_key(project.id, name)

        env = cache.get(cache_key)
        if env is None:
            try:
                env = cls.objects.get(
                    projects=project,
                    organization_id=project.organization_id,
                    name=name,
                )
            except cls.DoesNotExist:
                env = cls.objects.filter(
                    organization_id=project.organization_id,
                    name=name,
                ).order_by('date_added').first()
                if not env:
                    lock_key = cls.get_lock_key(project.organization_id, name)
                    lock = locks.get(lock_key, duration=5)
                    with TimedRetryPolicy(10)(lock.acquire):
                        try:
                            env = cls.objects.get(
                                organization_id=project.organization_id,
                                name=name,
                            )
                        except cls.DoesNotExist:
                            env = cls.objects.create(
                                project_id=project.id,
                                name=name,
                                organization_id=project.organization_id
                            )
                env.add_project(project)

            cache.set(cache_key, env, 3600)

        return env
Example #51
0
    def post(self, request, project):
        """
        Create a New Release
        ````````````````````

        Create a new release and/or associate a project with a release.
        Release versions that are the same across multiple projects
        within an Organization will be treated as the same release in Sentry.

        Releases are used by Sentry to improve its error reporting abilities
        by correlating first seen events with the release that might have
        introduced the problem.

        Releases are also necessary for sourcemaps and other debug features
        that require manual upload for functioning well.

        :pparam string organization_slug: the slug of the organization the
                                          release belongs to.
        :pparam string project_slug: the slug of the project to create a
                                     release for.
        :param string version: a version identifier for this release.  Can
                               be a version number, a commit hash etc.
        :param string ref: an optional commit reference.  This is useful if
                           a tagged version has been provided.
        :param url url: a URL that points to the release.  This can be the
                        path to an online interface to the sourcecode
                        for instance.
        :param datetime dateStarted: an optional date that indicates when the
                                     release process started.
        :param datetime dateReleased: an optional date that indicates when
                                      the release went live.  If not provided
                                      the current time is assumed.
        :auth: required
        """
        serializer = ReleaseSerializer(data=request.DATA)

        if serializer.is_valid():
            result = serializer.object

            # release creation is idempotent to simplify user
            # experiences
            release = Release.objects.filter(
                organization_id=project.organization_id,
                version=result['version'],
                projects=project
            ).first()
            created = False
            if release:
                was_released = bool(release.date_released)
            else:
                release = Release.objects.filter(
                    organization_id=project.organization_id,
                    version=result['version'],
                ).first()
                if not release:
                    lock_key = Release.get_lock_key(project.organization_id, result['version'])
                    lock = locks.get(lock_key, duration=5)
                    with TimedRetryPolicy(10)(lock.acquire):
                        try:
                            release, created = Release.objects.get(
                                version=result['version'],
                                organization_id=project.organization_id
                            ), False
                        except Release.DoesNotExist:
                            release, created = Release.objects.create(
                                organization_id=project.organization_id,
                                version=result['version'],
                                ref=result.get('ref'),
                                url=result.get('url'),
                                owner=result.get('owner'),
                                date_started=result.get('dateStarted'),
                                date_released=result.get('dateReleased'),
                            ), True
                was_released = False
                try:
                    with transaction.atomic():
                        ReleaseProject.objects.create(project=project, release=release)
                    created = True
                except IntegrityError:
                    pass

            commit_list = result.get('commits')
            if commit_list:
                hook = ReleaseHook(project)
                # TODO(dcramer): handle errors with release payloads
                hook.set_commits(release.version, commit_list)

            if (not was_released and release.date_released):
                activity = Activity.objects.create(
                    type=Activity.RELEASE,
                    project=project,
                    ident=result['version'],
                    data={'version': result['version']},
                    datetime=release.date_released,
                )
                activity.send_notification()

            if not created:
                # This is the closest status code that makes sense, and we want
                # a unique 2xx response code so people can understand when
                # behavior differs.
                #   208 Already Reported (WebDAV; RFC 5842)
                status = 208
            else:
                status = 201

            return Response(serialize(release, request.user), status=status)
        return Response(serializer.errors, status=400)
Example #52
0
    def set_commits(self, version, commit_list):
        """
        Commits should be ordered oldest to newest.

        Calling this method will remove all existing commit history.
        """
        project = self.project
        release = Release.objects.filter(
            organization_id=project.organization_id,
            version=version,
            projects=self.project
        ).first()
        if not release:
            release = Release.objects.filter(
                organization_id=project.organization_id,
                version=version,
            ).first()
            if not release:
                lock_key = Release.get_lock_key(project.organization_id, version)
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        release = Release.objects.get(
                            organization_id=project.organization_id,
                            version=version
                        )
                    except Release.DoesNotExist:
                        release = Release.objects.create(
                            organization_id=project.organization_id,
                            version=version
                        )
            release.add_project(project)

        with transaction.atomic():
            # TODO(dcramer): would be good to optimize the logic to avoid these
            # deletes but not overly important
            ReleaseCommit.objects.filter(
                release=release,
            ).delete()

            authors = {}
            repos = {}
            for idx, data in enumerate(commit_list):
                repo_name = data.get('repository') or 'project-{}'.format(project.id)
                if repo_name not in repos:
                    repos[repo_name] = repo = Repository.objects.get_or_create(
                        organization_id=project.organization_id,
                        name=repo_name,
                    )[0]
                else:
                    repo = repos[repo_name]

                author_email = data.get('author_email')
                if author_email is None and data.get('author_name'):
                    author_email = self._to_email(data['author_name'])

                if not author_email:
                    author = None
                elif author_email not in authors:
                    authors[author_email] = author = CommitAuthor.objects.get_or_create(
                        organization_id=project.organization_id,
                        email=author_email,
                        defaults={
                            'name': data.get('author_name'),
                        }
                    )[0]
                    if data.get('author_name') and author.name != data['author_name']:
                        author.update(name=data['author_name'])
                else:
                    author = authors[author_email]

                commit = Commit.objects.get_or_create(
                    organization_id=project.organization_id,
                    repository_id=repo.id,
                    key=data['id'],
                    defaults={
                        'message': data.get('message'),
                        'author': author,
                        'date_added': data.get('timestamp') or timezone.now(),
                    }
                )[0]

                ReleaseCommit.objects.create(
                    organization_id=project.organization_id,
                    release=release,
                    commit=commit,
                    order=idx,
                )
Example #53
0
    def post(self, request, organization, version):
        """
        Create a Deploy
        ```````````````

        Create a deploy for a given release.

        :pparam string organization_slug: the organization short name
        :pparam string version: the version identifier of the release.
        :param string environment: the environment you're deploying to
        :param string name: the optional name of the deploy
        :param url url: the optional url that points to the deploy
        :param datetime dateStarted: an optional date that indicates when
                                     the deploy started
        :param datetime dateFinished: an optional date that indicates when
                                      the deploy ended. If not provided, the
                                      current time is used.
        """
        try:
            release = Release.objects.get(
                version=version,
                organization=organization,
            )
        except Release.DoesNotExist:
            raise ResourceDoesNotExist

        if not self.has_release_permission(request, organization, release):
            raise PermissionDenied

        serializer = DeploySerializer(data=request.DATA)

        if serializer.is_valid():
            result = serializer.object
            try:
                env = Environment.objects.get(
                    organization_id=organization.id,
                    name=result['environment'],
                )
            except Environment.DoesNotExist:
                # TODO(jess): clean up when changing unique constraint
                lock_key = Environment.get_lock_key(organization.id, result['environment'])
                lock = locks.get(lock_key, duration=5)
                with TimedRetryPolicy(10)(lock.acquire):
                    try:
                        env = Environment.objects.get(
                            organization_id=organization.id,
                            name=result['environment'],
                        )
                    except Environment.DoesNotExist:
                        env = Environment.objects.create(
                            organization_id=organization.id,
                            name=result['environment'],
                        )

            try:
                with transaction.atomic():
                    deploy, created = Deploy.objects.create(
                        organization_id=organization.id,
                        release=release,
                        environment_id=env.id,
                        date_finished=result.get('dateFinished', timezone.now()),
                        date_started=result.get('dateStarted'),
                        name=result.get('name'),
                        url=result.get('url'),
                    ), True
            except IntegrityError:
                deploy, created = Deploy.objects.get(
                    organization_id=organization.id,
                    release=release,
                    environment_id=env.id,
                ), False
                deploy.update(
                    date_finished=result.get('dateFinished', timezone.now()),
                    date_started=result.get('dateStarted'),
                )

            activity = None
            for project in release.projects.all():
                activity = Activity.objects.create(
                    type=Activity.DEPLOY,
                    project=project,
                    ident=release.version,
                    data={
                        'version': release.version,
                        'deploy_id': deploy.id,
                        'environment': env.name
                    },
                    datetime=deploy.date_finished,
                )
            # Somewhat hacky, only send notification for one
            # Deploy Activity record because it will cover all projects
            if activity is not None:
                activity.send_notification()

            # This is the closest status code that makes sense, and we want
            # a unique 2xx response code so people can understand when
            # behavior differs.
            #   208 Already Reported (WebDAV; RFC 5842)
            status = 201 if created else 208

            return Response(serialize(deploy, request.user), status=status)

        return Response(serializer.errors, status=400)
Example #54
0
 def delete(self, *args, **kwargs):
     lock = locks.get(u'fileblob:upload:{}'.format(self.checksum), duration=60 * 10)
     with TimedRetryPolicy(60)(lock.acquire):
         super(FileBlob, self).delete(*args, **kwargs)
     if self.path:
         self.deletefile(commit=False)
Example #55
0
    def set_commits(self, commit_list):
        """
        Bind a list of commits to this release.

        This will clear any existing commit log and replace it with the given
        commits.
        """

        # Sort commit list in reverse order
        commit_list.sort(key=lambda commit: commit.get('timestamp'), reverse=True)

        # TODO(dcramer): this function could use some cleanup/refactoring as its a bit unwieldly
        from sentry.models import (
            Commit, CommitAuthor, Group, GroupLink, GroupResolution, GroupStatus,
            ReleaseCommit, ReleaseHeadCommit, Repository, PullRequest
        )
        from sentry.plugins.providers.repository import RepositoryProvider
        # todo(meredith): implement for IntegrationRepositoryProvider
        commit_list = [
            c for c in commit_list
            if not RepositoryProvider.should_ignore_commit(c.get('message', ''))
        ]
        lock_key = type(self).get_lock_key(self.organization_id, self.id)
        lock = locks.get(lock_key, duration=10)
        with TimedRetryPolicy(10)(lock.acquire):
            with transaction.atomic():
                # TODO(dcramer): would be good to optimize the logic to avoid these
                # deletes but not overly important
                ReleaseCommit.objects.filter(
                    release=self,
                ).delete()

                authors = {}
                repos = {}
                commit_author_by_commit = {}
                head_commit_by_repo = {}
                latest_commit = None
                for idx, data in enumerate(commit_list):
                    repo_name = data.get('repository'
                                         ) or 'organization-{}'.format(self.organization_id)
                    if repo_name not in repos:
                        repos[repo_name] = repo = Repository.objects.get_or_create(
                            organization_id=self.organization_id,
                            name=repo_name,
                        )[0]
                    else:
                        repo = repos[repo_name]

                    author_email = data.get('author_email')
                    if author_email is None and data.get('author_name'):
                        author_email = (
                            re.sub(r'[^a-zA-Z0-9\-_\.]*', '', data['author_name']).lower() +
                            '@localhost'
                        )

                    if not author_email:
                        author = None
                    elif author_email not in authors:
                        authors[author_email] = author = CommitAuthor.objects.get_or_create(
                            organization_id=self.organization_id,
                            email=author_email,
                            defaults={
                                'name': data.get('author_name'),
                            }
                        )[0]
                        if data.get('author_name') and author.name != data['author_name']:
                            author.update(name=data['author_name'])
                    else:
                        author = authors[author_email]

                    defaults = {
                        'message': data.get('message'),
                        'author': author,
                        'date_added': data.get('timestamp') or timezone.now(),
                    }
                    commit, created = Commit.objects.get_or_create(
                        organization_id=self.organization_id,
                        repository_id=repo.id,
                        key=data['id'],
                        defaults=defaults,
                    )
                    if author is None:
                        author = commit.author

                    commit_author_by_commit[commit.id] = author

                    patch_set = data.get('patch_set', [])

                    for patched_file in patch_set:
                        CommitFileChange.objects.get_or_create(
                            organization_id=self.organization.id,
                            commit=commit,
                            filename=patched_file['path'],
                            type=patched_file['type'],
                        )

                    if not created:
                        update_kwargs = {}
                        if commit.message is None and defaults['message'] is not None:
                            update_kwargs['message'] = defaults['message']
                        if commit.author_id is None and defaults['author'] is not None:
                            update_kwargs['author'] = defaults['author']
                        if update_kwargs:
                            commit.update(**update_kwargs)

                    ReleaseCommit.objects.create(
                        organization_id=self.organization_id,
                        release=self,
                        commit=commit,
                        order=idx,
                    )
                    if latest_commit is None:
                        latest_commit = commit

                    head_commit_by_repo.setdefault(repo.id, commit.id)

                self.update(
                    commit_count=len(commit_list),
                    authors=[
                        six.text_type(a_id)
                        for a_id in ReleaseCommit.objects.filter(
                            release=self,
                            commit__author_id__isnull=False,
                        ).values_list('commit__author_id', flat=True).distinct()
                    ],
                    last_commit_id=latest_commit.id if latest_commit else None,
                )

        # fill any missing ReleaseHeadCommit entries
        for repo_id, commit_id in six.iteritems(head_commit_by_repo):
            try:
                with transaction.atomic():
                    ReleaseHeadCommit.objects.create(
                        organization_id=self.organization_id,
                        release_id=self.id,
                        repository_id=repo_id,
                        commit_id=commit_id,
                    )
            except IntegrityError:
                pass

        release_commits = list(ReleaseCommit.objects.filter(release=self)
                               .select_related('commit').values('commit_id', 'commit__key'))

        commit_resolutions = list(
            GroupLink.objects.filter(
                linked_type=GroupLink.LinkedType.commit,
                linked_id__in=[rc['commit_id'] for rc in release_commits],
            ).values_list('group_id', 'linked_id')
        )

        commit_group_authors = [
            (cr[0],  # group_id
             commit_author_by_commit.get(cr[1])) for cr in commit_resolutions]

        pr_ids_by_merge_commit = list(PullRequest.objects.filter(
            merge_commit_sha__in=[rc['commit__key'] for rc in release_commits],
            organization_id=self.organization_id,
        ).values_list('id', flat=True))

        pull_request_resolutions = list(
            GroupLink.objects.filter(
                relationship=GroupLink.Relationship.resolves,
                linked_type=GroupLink.LinkedType.pull_request,
                linked_id__in=pr_ids_by_merge_commit,
            ).values_list('group_id', 'linked_id')
        )

        pr_authors = list(PullRequest.objects.filter(
            id__in=[prr[1] for prr in pull_request_resolutions],
        ).select_related('author'))

        pr_authors_dict = {
            pra.id: pra.author for pra in pr_authors
        }

        pull_request_group_authors = [(prr[0], pr_authors_dict.get(prr[1]))
                                      for prr in pull_request_resolutions]

        user_by_author = {None: None}

        for group_id, author in itertools.chain(commit_group_authors, pull_request_group_authors):
            if author not in user_by_author:
                try:
                    user_by_author[author] = author.find_users()[0]
                except IndexError:
                    user_by_author[author] = None
            actor = user_by_author[author]

            with transaction.atomic():
                GroupResolution.objects.create_or_update(
                    group_id=group_id,
                    values={
                        'release': self,
                        'type': GroupResolution.Type.in_release,
                        'status': GroupResolution.Status.resolved,
                        'actor_id': actor.id if actor else None,
                    },
                )
                Group.objects.filter(
                    id=group_id,
                ).update(status=GroupStatus.RESOLVED)
    def post(self, request, organization):
        """
        Add a Member to Organization
        ````````````````````````````

        Invite a member to the organization.

        :pparam string organization_slug: the slug of the organization the member will belong to
        :param string email: the email address to invite
        :param string role: the role of the new member
        :param array teams: the slugs of the teams the member should belong to.

        :auth: required
        """
        # TODO: If the member already exists, should this still update the role and team?
        # For now, it doesn't, but simply returns the existing object

        if not features.has('organizations:invite-members', organization, actor=request.user):
            return Response(
                {'organization': 'Your organization is not allowed to invite members'}, status=403)

        serializer = OrganizationMemberSerializer(data=request.DATA)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        _, allowed_roles = get_allowed_roles(request, organization)

        # ensure listed teams are real teams
        teams = list(Team.objects.filter(
            organization=organization,
            status=TeamStatus.VISIBLE,
            slug__in=result['teams'],
        ))

        if len(set(result['teams'])) != len(teams):
            return Response({'teams': 'Invalid team'}, 400)

        if not result['role'] in {r.id for r in allowed_roles}:
            return Response({'role': 'You do not have permission to invite that role.'}, 403)

        # This is needed because `email` field is case sensitive, but from a user perspective,
        # Sentry treats email as case-insensitive ([email protected] equals [email protected]).

        existing = OrganizationMember.objects.filter(
            organization=organization,
            user__email__iexact=result['email'],
            user__is_active=True,
        ).exists()

        if existing:
            return Response({'email': 'The user %s is already a member' % result['email']}, 409)

        om = OrganizationMember(
            organization=organization,
            email=result['email'],
            role=result['role'])

        if settings.SENTRY_ENABLE_INVITES:
            om.token = om.generate_token()

        try:
            with transaction.atomic():
                om.save()
        except IntegrityError:
            return Response({'email': 'The user %s is already a member' % result['email']}, 409)

        lock = locks.get(u'org:member:{}'.format(om.id), duration=5)
        with TimedRetryPolicy(10)(lock.acquire):
            self.save_team_assignments(om, teams)

        if settings.SENTRY_ENABLE_INVITES:
            om.send_invite_email()
            member_invited.send_robust(member=om, user=request.user, sender=self,
                                       referrer=request.DATA.get('referrer'))
        self.create_audit_entry(
            request=request,
            organization_id=organization.id,
            target_object=om.id,
            data=om.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )

        return Response(serialize(om), status=201)
Example #57
0
    def set_commits(self, commit_list):
        """
        Bind a list of commits to this release.

        These should be ordered from newest to oldest.

        This will clear any existing commit log and replace it with the given
        commits.
        """
        from sentry.models import (
            Commit, CommitAuthor, Group, GroupLink, GroupResolution, GroupStatus,
            ReleaseCommit, Repository
        )
        from sentry.plugins.providers.repository import RepositoryProvider

        commit_list = [
            c for c in commit_list
            if not RepositoryProvider.should_ignore_commit(c.get('message', ''))
        ]

        lock_key = type(self).get_lock_key(self.organization_id, self.id)
        lock = locks.get(lock_key, duration=10)
        with TimedRetryPolicy(10)(lock.acquire):
            with transaction.atomic():
                # TODO(dcramer): would be good to optimize the logic to avoid these
                # deletes but not overly important
                ReleaseCommit.objects.filter(
                    release=self,
                ).delete()

                authors = {}
                repos = {}
                commit_author_by_commit = {}
                latest_commit = None
                for idx, data in enumerate(commit_list):
                    repo_name = data.get('repository'
                                         ) or 'organization-{}'.format(self.organization_id)
                    if repo_name not in repos:
                        repos[repo_name] = repo = Repository.objects.get_or_create(
                            organization_id=self.organization_id,
                            name=repo_name,
                        )[0]
                    else:
                        repo = repos[repo_name]

                    author_email = data.get('author_email')
                    if author_email is None and data.get('author_name'):
                        author_email = (
                            re.sub(r'[^a-zA-Z0-9\-_\.]*', '', data['author_name']).lower() +
                            '@localhost'
                        )

                    if not author_email:
                        author = None
                    elif author_email not in authors:
                        authors[author_email] = author = CommitAuthor.objects.get_or_create(
                            organization_id=self.organization_id,
                            email=author_email,
                            defaults={
                                'name': data.get('author_name'),
                            }
                        )[0]
                        if data.get('author_name') and author.name != data['author_name']:
                            author.update(name=data['author_name'])
                    else:
                        author = authors[author_email]

                    defaults = {
                        'message': data.get('message'),
                        'author': author,
                        'date_added': data.get('timestamp') or timezone.now(),
                    }
                    commit, created = Commit.objects.get_or_create(
                        organization_id=self.organization_id,
                        repository_id=repo.id,
                        key=data['id'],
                        defaults=defaults,
                    )
                    if author is None:
                        author = commit.author

                    commit_author_by_commit[commit.id] = author

                    patch_set = data.get('patch_set', [])

                    for patched_file in patch_set:
                        CommitFileChange.objects.get_or_create(
                            organization_id=self.organization.id,
                            commit=commit,
                            filename=patched_file['path'],
                            type=patched_file['type'],
                        )

                    if not created:
                        update_kwargs = {}
                        if commit.message is None and defaults['message'] is not None:
                            update_kwargs['message'] = defaults['message']
                        if commit.author_id is None and defaults['author'] is not None:
                            update_kwargs['author'] = defaults['author']
                        if update_kwargs:
                            commit.update(**update_kwargs)

                    ReleaseCommit.objects.create(
                        organization_id=self.organization_id,
                        release=self,
                        commit=commit,
                        order=idx,
                    )
                    if latest_commit is None:
                        latest_commit = commit

                self.update(
                    commit_count=len(commit_list),
                    authors=[
                        six.text_type(a_id)
                        for a_id in ReleaseCommit.objects.filter(
                            release=self,
                            commit__author_id__isnull=False,
                        ).values_list('commit__author_id', flat=True).distinct()
                    ],
                    last_commit_id=latest_commit.id if latest_commit else None,
                )

        commit_resolutions = list(
            GroupLink.objects.filter(
                linked_type=GroupLink.LinkedType.commit,
                linked_id__in=ReleaseCommit.objects.filter(release=self)
                .values_list('commit_id', flat=True),
            ).values_list('group_id', 'linked_id')
        )

        user_by_author = {None: None}
        for group_id, linked_id in commit_resolutions:
            author = commit_author_by_commit.get(linked_id)
            if author not in user_by_author:
                try:
                    user_by_author[author] = author.find_users()[0]
                except IndexError:
                    user_by_author[author] = None
            actor = user_by_author[author]

            with transaction.atomic():
                GroupResolution.objects.create_or_update(
                    group_id=group_id,
                    values={
                        'release': self,
                        'type': GroupResolution.Type.in_release,
                        'status': GroupResolution.Status.resolved,
                        'actor_id': actor.id if actor else None,
                    },
                )
                Group.objects.filter(
                    id=group_id,
                ).update(status=GroupStatus.RESOLVED)