def test_policy_success(self): bomb = Exception('Boom!') callable = mock.MagicMock(side_effect=[bomb, mock.sentinel.OK]) retry = TimedRetryPolicy(0.3, delay=lambda i: 0.1) retry.clock = mock.Mock() retry.clock.sleep = mock.MagicMock() retry.clock.time = mock.MagicMock(side_effect=[0, 0.15]) assert retry(callable) is mock.sentinel.OK assert callable.call_count == 2
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)
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)
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)
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)
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)
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
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
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)
def test_policy_failure(self): bomb = Exception('Boom!') callable = mock.MagicMock(side_effect=bomb) retry = TimedRetryPolicy(0.3, delay=lambda i: 0.1) retry.clock = mock.Mock() retry.clock.sleep = mock.MagicMock() retry.clock.time = mock.MagicMock(side_effect=[0, 0.15, 0.25]) try: retry(callable) except RetryException as exception: assert exception.exception is bomb else: self.fail(u'Expected {!r}!'.format(RetryException)) assert callable.call_count == 2
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)
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)
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)
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
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)
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)
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()
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
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 _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})
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) ])
def get_or_create(cls, project, version, date_added): cache_key = cls.get_cache_key(project.id, version) release = cache.get(cache_key) if release in (None, -1): # TODO(dcramer): if the cache result is -1 we could attempt a # default create here instead of default get project_version = ('%s-%s' % (project.slug, version))[:64] releases = list( cls.objects.filter(organization_id=project.organization_id, version__in=[version, project_version], projects=project)) if releases: # TODO(jess): clean this up once all releases have been migrated try: release = [ r for r in releases if r.version == project_version ][0] except IndexError: release = releases[0] else: release = cls.objects.filter( organization_id=project.organization_id, version=version).first() if not release: lock_key = cls.get_lock_key(project.organization_id, version) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = cls.objects.get( organization_id=project.organization_id, version=version) except cls.DoesNotExist: release = cls.objects.create( organization_id=project.organization_id, version=version, date_added=date_added) release.add_project(project) # TODO(dcramer): upon creating a new release, check if it should be # the new "latest release" for this project cache.set(cache_key, release, 3600) return release
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('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
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()
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
def ensure_release_exists(instance, created, **kwargs): if instance.key != 'sentry:release': return if instance.data and instance.data.get('release_id'): return affected = Release.objects.filter( organization_id=instance.project.organization_id, version=instance.value, projects=instance.project ).update(date_added=instance.first_seen) if not affected: release = Release.objects.filter( organization_id=instance.project.organization_id, version=instance.value ).first() if release: release.update(date_added=instance.first_seen) else: lock_key = Release.get_lock_key(instance.project.organization_id, instance.value) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = Release.objects.get( organization_id=instance.project.organization_id, version=instance.value, ) except Release.DoesNotExist: release = Release.objects.create( organization_id=instance.project.organization_id, version=instance.value, date_added=instance.first_seen, ) instance.update(data={'release_id': release.id}) release.add_project(instance.project)
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)
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 from sentry.tasks.integrations import kick_off_status_syncs # 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): start = time() 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 u'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: author_data = {'name': data.get('author_name')} author, created = CommitAuthor.objects.create_or_update( organization_id=self.organization_id, email=author_email, values=author_data) if not created: author = CommitAuthor.objects.get( organization_id=self.organization_id, email=author_email) authors[author_email] = author else: author = authors[author_email] commit_data = {} defaults = {} # Update/set message and author if they are provided. if author is not None: commit_data['author'] = author if 'message' in data: commit_data['message'] = data['message'] if 'timestamp' in data: commit_data['date_added'] = data['timestamp'] else: defaults['date_added'] = timezone.now() commit, created = Commit.objects.create_or_update( organization_id=self.organization_id, repository_id=repo.id, key=data['id'], defaults=defaults, values=commit_data) if not created: commit = Commit.objects.get( organization_id=self.organization_id, repository_id=repo.id, key=data['id']) 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: try: with transaction.atomic(): CommitFileChange.objects.create( organization_id=self.organization.id, commit=commit, filename=patched_file['path'], type=patched_file['type'], ) except IntegrityError: pass try: with transaction.atomic(): ReleaseCommit.objects.create( organization_id=self.organization_id, release=self, commit=commit, order=idx, ) except IntegrityError: pass 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, ) metrics.timing('release.set_commits.duration', time() - start) # 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} commits_and_prs = list( itertools.chain(commit_group_authors, pull_request_group_authors), ) group_project_lookup = dict( Group.objects.filter(id__in=[ group_id for group_id, _ in commits_and_prs ], ).values_list('id', 'project_id')) for group_id, author in commits_and_prs: 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 = Group.objects.get(id=group_id, ) group.update(status=GroupStatus.RESOLVED) metrics.incr('group.resolved', instance='in_commit', skip_internal=True) issue_resolved.send_robust( organization_id=self.organization_id, user=actor, group=group, project=group.project, resolution_type='with_commit', sender=type(self), ) kick_off_status_syncs.apply_async( kwargs={ 'project_id': group_project_lookup[group_id], 'group_id': group_id, })
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 """ if not features.has("organizations:invite-members", organization, actor=request.user): return Response( { "organization": "Your organization is not allowed to invite members" }, status=403) _, allowed_roles = get_allowed_roles(request, organization) serializer = OrganizationMemberSerializer( data=request.data, context={ "organization": organization, "allowed_roles": allowed_roles, "allow_existing_invite_request": True, }, ) if not serializer.is_valid(): return Response(serializer.errors, status=400) result = serializer.validated_data with transaction.atomic(): # remove any invitation requests for this email before inviting OrganizationMember.objects.filter( Q(invite_status=InviteStatus.REQUESTED_TO_BE_INVITED.value) | Q(invite_status=InviteStatus.REQUESTED_TO_JOIN.value), email=result["email"], organization=organization, ).delete() om = OrganizationMember( organization=organization, email=result["email"], role=result["role"], inviter=request.user, ) if settings.SENTRY_ENABLE_INVITES: om.token = om.generate_token() om.save() 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"]) if settings.SENTRY_ENABLE_INVITES and result.get("sendInvite"): 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)
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( f"sso:auth:{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: # XXX(leedongwei): Workaround for migrating Okta instance if features.has("organizations:sso-migration", self.organization, actor=self.request.user) and ( auth_provider.provider == "okta" or auth_provider.provider == "saml2"): identity["email_verified"] = True logger.info( "sso.login-pipeline.okta-verified-workaround", extra={ "organization_id": self.organization.id, "user_id": self.request.user.id, "auth_provider_id": self.auth_provider.id, "idp_identity_id": identity["id"], "idp_identity_email": identity["email"], }, ) return handle_unknown_identity( self.request, self.organization, self.auth_provider, self.provider, self.state, 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 handle_unknown_identity( self.request, self.organization, self.auth_provider, self.provider, self.state, identity, ) auth_identity = handle_attach_identity(self.auth_provider, self.request, self.organization, self.provider, identity) return handle_existing_identity( self.auth_provider, self.provider, self.organization, self.request, self.state, auth_identity, identity, )