예제 #1
0
 def test_extract_low(self):
     """Extract low 3 bits"""
     low_bits = BitField(0, 3)
     self.assertEqual(low_bits.extract(0b10101010101), 0b101)
예제 #2
0
class Project(Model):
    """
    Projects are permission based namespaces which generally
    are the top level entry point for all data.
    """
    __core__ = True

    slug = models.SlugField(null=True)
    name = models.CharField(max_length=200)
    forced_color = models.CharField(max_length=6, null=True, blank=True)
    organization = FlexibleForeignKey('sentry.Organization')
    team = FlexibleForeignKey('sentry.Team')
    teams = models.ManyToManyField('sentry.Team',
                                   related_name='teams',
                                   through=ProjectTeam)
    public = models.BooleanField(default=False)
    date_added = models.DateTimeField(default=timezone.now)
    status = BoundedPositiveIntegerField(
        default=0,
        choices=(
            (ObjectStatus.VISIBLE, _('Active')),
            (ObjectStatus.PENDING_DELETION, _('Pending Deletion')),
            (ObjectStatus.DELETION_IN_PROGRESS, _('Deletion in Progress')),
        ),
        db_index=True)
    # projects that were created before this field was present
    # will have their first_event field set to date_added
    first_event = models.DateTimeField(null=True)
    flags = BitField(flags=(('has_releases',
                             'This Project has sent release data'), ),
                     default=0,
                     null=True)

    objects = ProjectManager(cache_fields=[
        'pk',
        'slug',
    ])
    platform = models.CharField(max_length=64, null=True)

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_project'
        unique_together = (('team', 'slug'), ('organization', 'slug'))

    __repr__ = sane_repr('team_id', 'name', 'slug')

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.slug)

    def next_short_id(self):
        from sentry.models import Counter
        return Counter.increment(self)

    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 get_absolute_url(self):
        return absolute_uri('/{}/{}/'.format(self.organization.slug,
                                             self.slug))

    def is_internal_project(self):
        for value in (settings.SENTRY_FRONTEND_PROJECT,
                      settings.SENTRY_PROJECT):
            if six.text_type(self.id) == six.text_type(value) or six.text_type(
                    self.slug) == six.text_type(value):
                return True
        return False

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        from sentry.models import ProjectOption

        return ProjectOption.objects.set_value(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        from sentry.models import ProjectOption

        return ProjectOption.objects.get_value(self, *args, **kwargs)

    def delete_option(self, *args, **kwargs):
        from sentry.models import ProjectOption

        return ProjectOption.objects.unset_value(self, *args, **kwargs)

    @property
    def callsign(self):
        return self.slug.upper()

    @property
    def color(self):
        if self.forced_color is not None:
            return '#%s' % self.forced_color
        return get_hashed_color(self.callsign or self.slug)

    @property
    def member_set(self):
        from sentry.models import OrganizationMember
        return self.organization.member_set.filter(
            id__in=OrganizationMember.objects.filter(
                organizationmemberteam__is_active=True,
                organizationmemberteam__team=self.team,
            ).values('id'),
            user__is_active=True,
        ).distinct()

    def has_access(self, user, access=None):
        from sentry.models import AuthIdentity, OrganizationMember

        warnings.warn('Project.has_access is deprecated.', DeprecationWarning)

        queryset = self.member_set.filter(user=user)

        if access is not None:
            queryset = queryset.filter(type__lte=access)

        try:
            member = queryset.get()
        except OrganizationMember.DoesNotExist:
            return False

        try:
            auth_identity = AuthIdentity.objects.get(
                auth_provider__organization=self.organization_id,
                user=member.user_id,
            )
        except AuthIdentity.DoesNotExist:
            return True

        return auth_identity.is_valid(member)

    def get_audit_log_data(self):
        return {
            'id': self.id,
            'slug': self.slug,
            'name': self.name,
            'status': self.status,
            'public': self.public,
        }

    def get_full_name(self):
        if self.team.name not in self.name:
            return '%s %s' % (self.team.name, self.name)
        return self.name

    def get_notification_recipients(self, user_option):
        from sentry.models import UserOption
        alert_settings = dict((o.user_id, int(o.value))
                              for o in UserOption.objects.filter(
                                  project=self,
                                  key=user_option,
                              ))

        disabled = set(u for u, v in six.iteritems(alert_settings) if v == 0)

        member_set = set(
            self.member_set.exclude(user__in=disabled, ).values_list(
                'user', flat=True))

        # determine members default settings
        members_to_check = set(u for u in member_set
                               if u not in alert_settings)
        if members_to_check:
            disabled = set((uo.user_id for uo in UserOption.objects.filter(
                key='subscribe_by_default',
                user__in=members_to_check,
            ) if uo.value == '0'))
            member_set = [x for x in member_set if x not in disabled]

        return member_set

    def get_mail_alert_subscribers(self):
        user_ids = self.get_notification_recipients('mail:alert')
        if not user_ids:
            return []
        from sentry.models import User
        return list(User.objects.filter(id__in=user_ids))

    def is_user_subscribed_to_mail_alerts(self, user):
        from sentry.models import UserOption
        is_enabled = UserOption.objects.get_value(user,
                                                  'mail:alert',
                                                  project=self)
        if is_enabled is None:
            is_enabled = UserOption.objects.get_value(user,
                                                      'subscribe_by_default',
                                                      '1') == '1'
        else:
            is_enabled = bool(is_enabled)
        return is_enabled

    def transfer_to(self, team):
        from sentry.models import ProjectTeam, ReleaseProject

        organization = team.organization
        from_team_id = self.team_id

        # We only need to delete ReleaseProjects when moving to a different
        # Organization. Releases are bound to Organization, so it's not realistic
        # to keep this link unless we say, copied all Releases as well.
        if self.organization_id != organization.id:
            ReleaseProject.objects.filter(project_id=self.id, ).delete()

        self.organization = organization
        self.team = team

        try:
            with transaction.atomic():
                self.update(
                    organization=organization,
                    team=team,
                )
        except IntegrityError:
            slugify_instance(self, self.name, organization=organization)
            self.update(
                slug=self.slug,
                organization=organization,
                team=team,
            )

        ProjectTeam.objects.filter(project=self,
                                   team_id=from_team_id).update(team=team)

    def add_team(self, team):
        try:
            with transaction.atomic():
                ProjectTeam.objects.create(project=self, team=team)
        except IntegrityError:
            return False
        else:
            return True

    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 get_lock_key(self):
        return 'project_token:%s' % self.id
예제 #3
0
class Project(Model):
    """
    Projects are permission based namespaces which generally
    are the top level entry point for all data.
    """
    __core__ = True

    slug = models.SlugField(null=True)
    name = models.CharField(max_length=200)
    forced_color = models.CharField(max_length=6, null=True, blank=True)
    organization = FlexibleForeignKey('sentry.Organization')
    teams = models.ManyToManyField(
        'sentry.Team', related_name='teams', through=ProjectTeam
    )
    public = models.BooleanField(default=False)
    date_added = models.DateTimeField(default=timezone.now)
    status = BoundedPositiveIntegerField(
        default=0,
        choices=(
            (ObjectStatus.VISIBLE,
             _('Active')), (ObjectStatus.PENDING_DELETION, _('Pending Deletion')),
            (ObjectStatus.DELETION_IN_PROGRESS, _('Deletion in Progress')),
        ),
        db_index=True
    )
    # projects that were created before this field was present
    # will have their first_event field set to date_added
    first_event = models.DateTimeField(null=True)
    flags = BitField(
        flags=(('has_releases', 'This Project has sent release data'), ), default=0, null=True
    )

    objects = ProjectManager(cache_fields=[
        'pk',
        'slug',
    ])
    platform = models.CharField(max_length=64, null=True)

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_project'
        unique_together = (('organization', 'slug'),)

    __repr__ = sane_repr('team_id', 'name', 'slug')

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.slug)

    def next_short_id(self):
        from sentry.models import Counter
        return Counter.increment(self)

    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)

    def get_absolute_url(self):
        return absolute_uri('/{}/{}/'.format(self.organization.slug, self.slug))

    def is_internal_project(self):
        for value in (settings.SENTRY_FRONTEND_PROJECT, settings.SENTRY_PROJECT):
            if six.text_type(self.id) == six.text_type(value) or six.text_type(
                self.slug
            ) == six.text_type(value):
                return True
        return False

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        from sentry.models import ProjectOption

        return ProjectOption.objects.set_value(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        from sentry.models import ProjectOption

        return ProjectOption.objects.get_value(self, *args, **kwargs)

    def delete_option(self, *args, **kwargs):
        from sentry.models import ProjectOption

        return ProjectOption.objects.unset_value(self, *args, **kwargs)

    @property
    def callsign(self):
        warnings.warn(
            'Project.callsign is deprecated. Use Group.get_short_id() instead.',
            DeprecationWarning)
        return self.slug.upper()

    @property
    def color(self):
        if self.forced_color is not None:
            return '#%s' % self.forced_color
        return get_hashed_color(self.callsign or self.slug)

    @property
    def member_set(self):
        from sentry.models import OrganizationMember
        return self.organization.member_set.filter(
            id__in=OrganizationMember.objects.filter(
                organizationmemberteam__is_active=True,
                organizationmemberteam__team__in=self.teams.all(),
            ).values('id'),
            user__is_active=True,
        ).distinct()

    def has_access(self, user, access=None):
        from sentry.models import AuthIdentity, OrganizationMember

        warnings.warn('Project.has_access is deprecated.', DeprecationWarning)

        queryset = self.member_set.filter(user=user)

        if access is not None:
            queryset = queryset.filter(type__lte=access)

        try:
            member = queryset.get()
        except OrganizationMember.DoesNotExist:
            return False

        try:
            auth_identity = AuthIdentity.objects.get(
                auth_provider__organization=self.organization_id,
                user=member.user_id,
            )
        except AuthIdentity.DoesNotExist:
            return True

        return auth_identity.is_valid(member)

    def get_audit_log_data(self):
        return {
            'id': self.id,
            'slug': self.slug,
            'name': self.name,
            'status': self.status,
            'public': self.public,
        }

    def get_full_name(self):
        return self.slug

    def get_notification_recipients(self, user_option):
        from sentry.models import UserOption
        alert_settings = dict(
            (o.user_id, int(o.value))
            for o in UserOption.objects.filter(
                project=self,
                key=user_option,
            )
        )

        disabled = set(u for u, v in six.iteritems(alert_settings) if v == 0)

        member_set = set(
            self.member_set.exclude(
                user__in=disabled,
            ).values_list('user', flat=True)
        )

        # determine members default settings
        members_to_check = set(u for u in member_set if u not in alert_settings)
        if members_to_check:
            disabled = set(
                (
                    uo.user_id
                    for uo in UserOption.objects.filter(
                        key='subscribe_by_default',
                        user__in=members_to_check,
                    ) if uo.value == '0'
                )
            )
            member_set = [x for x in member_set if x not in disabled]

        return member_set

    def get_mail_alert_subscribers(self):
        user_ids = self.get_notification_recipients('mail:alert')
        if not user_ids:
            return []
        from sentry.models import User
        return list(User.objects.filter(id__in=user_ids))

    def is_user_subscribed_to_mail_alerts(self, user):
        from sentry.models import UserOption
        is_enabled = UserOption.objects.get_value(user, 'mail:alert', project=self)
        if is_enabled is None:
            is_enabled = UserOption.objects.get_value(user, 'subscribe_by_default', '1') == '1'
        else:
            is_enabled = bool(is_enabled)
        return is_enabled

    def transfer_to(self, team=None, organization=None):

        # TODO(jess): remove this when new-teams is live for everyone
        # only support passing team or org not both
        assert not (team and organization)

        # NOTE: this will only work properly if the new team is in a different
        # org than the existing one, which is currently the only use case in
        # production
        # TODO(jess): refactor this to make it an org transfer only
        from sentry.models import (
            Environment,
            EnvironmentProject,
            ProjectTeam,
            ReleaseProject,
            ReleaseProjectEnvironment,
            Rule,
        )

        if organization is None:
            organization = team.organization

        old_org_id = self.organization_id
        org_changed = old_org_id != organization.id

        self.organization = organization

        try:
            with transaction.atomic():
                self.update(
                    organization=organization,
                )
        except IntegrityError:
            slugify_instance(self, self.name, organization=organization)
            self.update(
                slug=self.slug,
                organization=organization,
            )

        # Both environments and releases are bound at an organization level.
        # Due to this, when you transfer a project into another org, we have to
        # handle this behavior somehow. We really only have two options here:
        # * Copy over all releases/environments into the new org and handle de-duping
        # * Delete the bindings and let them reform with new data.
        # We're generally choosing to just delete the bindings since new data
        # flowing in will recreate links correctly. The tradeoff is that
        # historical data is lost, but this is a compromise we're willing to
        # take and a side effect of allowing this feature. There are exceptions
        # to this however, such as rules, which should maintain their
        # configuration when moved across organizations.
        if org_changed:
            for model in ReleaseProject, ReleaseProjectEnvironment, EnvironmentProject:
                model.objects.filter(
                    project_id=self.id,
                ).delete()
            # this is getting really gross, but make sure there aren't lingering associations
            # with old orgs or teams
            ProjectTeam.objects.filter(project=self, team__organization_id=old_org_id).delete()

        rules_by_environment_id = defaultdict(set)
        for rule_id, environment_id in Rule.objects.filter(
                project_id=self.id,
                environment_id__isnull=False).values_list('id', 'environment_id'):
            rules_by_environment_id[environment_id].add(rule_id)

        environment_names = dict(
            Environment.objects.filter(
                id__in=rules_by_environment_id,
            ).values_list('id', 'name')
        )

        for environment_id, rule_ids in rules_by_environment_id.items():
            Rule.objects.filter(id__in=rule_ids).update(
                environment_id=Environment.get_or_create(
                    self,
                    environment_names[environment_id],
                ).id,
            )

        # ensure this actually exists in case from team was null
        if team is not None:
            self.add_team(team)

    def add_team(self, team):
        try:
            with transaction.atomic():
                ProjectTeam.objects.create(project=self, team=team)
        except IntegrityError:
            return False
        else:
            return True

    def remove_team(self, team):
        ProjectTeam.objects.filter(
            project=self,
            team=team,
        ).delete()

    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 get_lock_key(self):
        return 'project_token:%s' % self.id
예제 #4
0
class ProjectKey(Model):
    project = FlexibleForeignKey('sentry.Project', related_name='key_set')
    label = models.CharField(max_length=64, blank=True, null=True)
    public_key = models.CharField(max_length=32, unique=True, null=True)
    secret_key = models.CharField(max_length=32, unique=True, null=True)
    roles = BitField(
        flags=(
            # access to post events to the store endpoint
            ('store', 'Event API access'),

            # read/write access to rest API
            ('api', 'Web API access'),
        ),
        default=['store'])
    status = BoundedPositiveIntegerField(default=0,
                                         choices=(
                                             (ProjectKeyStatus.ACTIVE,
                                              _('Active')),
                                             (ProjectKeyStatus.INACTIVE,
                                              _('Inactive')),
                                         ),
                                         db_index=True)
    date_added = models.DateTimeField(default=timezone.now, null=True)

    objects = BaseManager(cache_fields=(
        'public_key',
        'secret_key',
    ))

    # support legacy project keys in API
    scopes = (
        'project:read',
        'project:write',
        'project:delete',
        'event:read',
        'event:write',
        'event:delete',
    )

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_projectkey'

    __repr__ = sane_repr('project_id', 'public_key')

    def __unicode__(self):
        return six.text_type(self.public_key)

    @classmethod
    def generate_api_key(cls):
        return uuid4().hex

    @classmethod
    def from_dsn(cls, dsn):
        urlparts = urlparse(dsn)

        public_key = urlparts.username
        project_id = urlparts.path.rsplit('/', 1)[-1]

        try:
            return ProjectKey.objects.get(
                public_key=public_key,
                project=project_id,
            )
        except ValueError:
            # ValueError would come from a non-integer project_id,
            # which is obviously a DoesNotExist. We catch and rethrow this
            # so anything downstream expecting DoesNotExist works fine
            raise ProjectKey.DoesNotExist(
                'ProjectKey matching query does not exist.')

    @classmethod
    def get_default(cls, project):
        try:
            return cls.objects.filter(project=project,
                                      roles=cls.roles.store,
                                      status=ProjectKeyStatus.ACTIVE)[0]
        except IndexError:
            return None

    @property
    def is_active(self):
        return self.status == ProjectKeyStatus.ACTIVE

    def save(self, *args, **kwargs):
        if not self.public_key:
            self.public_key = ProjectKey.generate_api_key()
        if not self.secret_key:
            self.secret_key = ProjectKey.generate_api_key()
        if not self.label:
            self.label = petname.Generate(2, ' ').title()
        super(ProjectKey, self).save(*args, **kwargs)

    def get_dsn(self, domain=None, secure=True, public=False):
        if not public:
            key = '%s:%s' % (self.public_key, self.secret_key)
            url = settings.SENTRY_ENDPOINT
        else:
            key = self.public_key
            url = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT

        if url:
            urlparts = urlparse(url)
        else:
            urlparts = urlparse(options.get('system.url-prefix'))

        return '%s://%s@%s/%s' % (
            urlparts.scheme,
            key,
            urlparts.netloc + urlparts.path,
            self.project_id,
        )

    @property
    def dsn_private(self):
        return self.get_dsn(public=False)

    @property
    def dsn_public(self):
        return self.get_dsn(public=True)

    @property
    def csp_endpoint(self):
        endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT
        if not endpoint:
            endpoint = options.get('system.url-prefix')

        return '%s%s?sentry_key=%s' % (
            endpoint,
            reverse('sentry-api-csp-report', args=[self.project_id]),
            self.public_key,
        )

    def get_allowed_origins(self):
        from sentry.utils.http import get_origins
        return get_origins(self.project)

    def get_audit_log_data(self):
        return {
            'label': self.label,
            'public_key': self.public_key,
            'secret_key': self.secret_key,
            'roles': int(self.roles),
            'status': self.status,
        }

    def get_scopes(self):
        return self.scopes
예제 #5
0
class OrganizationMember(Model):
    """
    Identifies relationships between teams and users.

    Users listed as team members are considered to have access to all projects
    and could be thought of as team owners (though their access level may not)
    be set to ownership.
    """
    __core__ = True

    organization = FlexibleForeignKey('sentry.Organization',
                                      related_name="member_set")

    user = FlexibleForeignKey(settings.AUTH_USER_MODEL,
                              null=True,
                              blank=True,
                              related_name="sentry_orgmember_set")
    email = models.EmailField(null=True, blank=True)
    role = models.CharField(
        choices=roles.get_choices(),
        max_length=32,
        default=roles.get_default().id,
    )
    flags = BitField(flags=(
        ('sso:linked', 'sso:linked'),
        ('sso:invalid', 'sso:invalid'),
    ),
                     default=0)
    token = models.CharField(max_length=64, null=True, blank=True, unique=True)
    date_added = models.DateTimeField(default=timezone.now)
    has_global_access = models.BooleanField(default=True)
    teams = models.ManyToManyField('sentry.Team',
                                   blank=True,
                                   through='sentry.OrganizationMemberTeam')

    # Deprecated -- no longer used
    type = BoundedPositiveIntegerField(default=50, blank=True)

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_organizationmember'
        unique_together = (
            ('organization', 'user'),
            ('organization', 'email'),
        )

    __repr__ = sane_repr(
        'organization_id',
        'user_id',
        'role',
    )

    @transaction.atomic
    def save(self, *args, **kwargs):
        assert self.user_id or self.email, \
            'Must set user or email'
        super(OrganizationMember, self).save(*args, **kwargs)

    @property
    def is_pending(self):
        return self.user_id is None

    @property
    def legacy_token(self):
        checksum = md5()
        checksum.update(six.text_type(self.organization_id).encode('utf-8'))
        checksum.update(self.get_email().encode('utf-8'))
        checksum.update(force_bytes(settings.SECRET_KEY))
        return checksum.hexdigest()

    def generate_token(self):
        return uuid4().hex + uuid4().hex

    def get_invite_link(self):
        if not self.is_pending:
            return None
        return absolute_uri(
            reverse('sentry-accept-invite',
                    kwargs={
                        'member_id': self.id,
                        'token': self.token or self.legacy_token,
                    }))

    def send_invite_email(self):
        from sentry.utils.email import MessageBuilder

        context = {
            'email': self.email,
            'organization': self.organization,
            'url': self.get_invite_link(),
        }

        msg = MessageBuilder(
            subject='Join %s in using Sentry' % self.organization.name,
            template='sentry/emails/member-invite.txt',
            html_template='sentry/emails/member-invite.html',
            type='organization.invite',
            context=context,
        )

        try:
            msg.send_async([self.get_email()])
        except Exception as e:
            logger = get_logger(name='sentry.mail')
            logger.exception(e)

    def send_sso_link_email(self, actor, provider):
        from sentry.utils.email import MessageBuilder

        link_args = {'organization_slug': self.organization.slug}

        context = {
            'organization':
            self.organization,
            'actor':
            actor,
            'provider':
            provider,
            'url':
            absolute_uri(reverse('sentry-auth-organization',
                                 kwargs=link_args)),
        }

        msg = MessageBuilder(
            subject='Action Required for %s' % (self.organization.name, ),
            template='sentry/emails/auth-link-identity.txt',
            html_template='sentry/emails/auth-link-identity.html',
            type='organization.auth_link',
            context=context,
        )
        msg.send_async([self.get_email()])

    def send_sso_unlink_email(self, actor, provider):
        from sentry.utils.email import MessageBuilder
        from sentry.models import LostPasswordHash

        email = self.get_email()

        recover_uri = u'{path}?{query}'.format(
            path=reverse('sentry-account-recover'),
            query=urlencode({'email': email}),
        )

        # Nothing to send if this member isn't associated to a user
        if not self.user_id:
            return

        context = {
            'email': email,
            'recover_url': absolute_uri(recover_uri),
            'has_password': self.user.password,
            'organization': self.organization,
            'actor': actor,
            'provider': provider,
        }

        if not self.user.password:
            password_hash = LostPasswordHash.for_user(self.user)
            context['set_password_url'] = password_hash.get_absolute_url(
                mode='set_password')

        msg = MessageBuilder(
            subject='Action Required for %s' % (self.organization.name, ),
            template='sentry/emails/auth-sso-disabled.txt',
            html_template='sentry/emails/auth-sso-disabled.html',
            type='organization.auth_sso_disabled',
            context=context,
        )
        msg.send_async([email])

    def get_display_name(self):
        if self.user_id:
            return self.user.get_display_name()
        return self.email

    def get_label(self):
        if self.user_id:
            return self.user.get_label()
        return self.email or self.id

    def get_email(self):
        if self.user_id and self.user.email:
            return self.user.email
        return self.email

    def get_avatar_type(self):
        if self.user_id:
            return self.user.get_avatar_type()
        return 'letter_avatar'

    def get_audit_log_data(self):
        from sentry.models import Team
        teams = list(
            Team.objects.filter(id__in=OrganizationMemberTeam.objects.filter(
                organizationmember=self,
                is_active=True,
            ).values_list('team', flat=True)).values('id', 'slug'))

        return {
            'email': self.get_email(),
            'user': self.user_id,
            'teams': [t['id'] for t in teams],
            'teams_slugs': [t['slug'] for t in teams],
            'has_global_access': self.has_global_access,
            'role': self.role,
        }

    def get_teams(self):
        from sentry.models import Team

        if roles.get(self.role).is_global:
            return self.organization.team_set.all()

        return Team.objects.filter(
            id__in=OrganizationMemberTeam.objects.filter(
                organizationmember=self,
                is_active=True,
            ).values('team'))

    def get_scopes(self):
        return roles.get(self.role).scopes
예제 #6
0
class Organization(Model):
    """
    An organization represents a group of individuals which maintain ownership of projects.
    """
    __core__ = True

    name = models.CharField(max_length=64)
    slug = models.SlugField(unique=True)
    status = BoundedPositiveIntegerField(choices=(
        (OrganizationStatus.VISIBLE, _('Visible')),
        (OrganizationStatus.PENDING_DELETION, _('Pending Deletion')),
        (OrganizationStatus.DELETION_IN_PROGRESS, _('Deletion in Progress')),
    ),
                                         default=OrganizationStatus.VISIBLE)
    date_added = models.DateTimeField(default=timezone.now)
    members = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                     through='sentry.OrganizationMember',
                                     related_name='org_memberships')
    default_role = models.CharField(
        choices=roles.get_choices(),
        max_length=32,
        default=roles.get_default().id,
    )

    flags = BitField(flags=(
        ('allow_joinleave',
         'Allow members to join and leave teams without requiring approval.'),
        ('enhanced_privacy',
         'Enable enhanced privacy controls to limit personally identifiable information (PII) as well as source code in things like notifications.'
         ),
        ('disable_shared_issues',
         'Disable sharing of limited details on issues to anonymous users.'),
        ('early_adopter',
         'Enable early adopter status, gaining access to features prior to public release.'
         ),
    ),
                     default=1)

    objects = OrganizationManager(cache_fields=(
        'pk',
        'slug',
    ))

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_organization'

    __repr__ = sane_repr('owner_id', 'name', 'slug')

    @classmethod
    def get_default(cls):
        """
        Return the organization used in single organization mode.
        """
        return cls.objects.filter(status=OrganizationStatus.VISIBLE, )[0]

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.slug)

    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 delete(self):
        if self.is_default:
            raise Exception('You cannot delete the the default organization.')
        return super(Organization, self).delete()

    @cached_property
    def is_default(self):
        if not settings.SENTRY_SINGLE_ORGANIZATION:
            return False

        return self == type(self).get_default()

    def has_access(self, user, access=None):
        queryset = self.member_set.filter(user=user)
        if access is not None:
            queryset = queryset.filter(type__lte=access)

        return queryset.exists()

    def get_audit_log_data(self):
        return {
            'id': self.id,
            'slug': self.slug,
            'name': self.name,
            'status': self.status,
            'flags': self.flags,
            'default_role': self.default_role,
        }

    def get_owners(self):
        from sentry.models import User
        return User.objects.filter(
            sentry_orgmember_set__role=roles.get_top_dog().id,
            sentry_orgmember_set__organization=self,
            is_active=True,
        )

    def get_default_owner(self):
        if not hasattr(self, '_default_owner'):
            self._default_owner = self.get_owners()[0]
        return self._default_owner

    def has_single_owner(self):
        from sentry.models import OrganizationMember
        count = OrganizationMember.objects.filter(
            organization=self,
            role=roles.get_top_dog().id,
            user__isnull=False,
            user__is_active=True,
        )[:2].count()
        return count == 1

    def merge_to(from_org, to_org):
        from sentry.models import (ApiKey, AuditLogEntry, Commit,
                                   OrganizationMember, OrganizationMemberTeam,
                                   Project, Release, ReleaseCommit,
                                   ReleaseEnvironment, ReleaseFile, Repository,
                                   Team)

        for from_member in OrganizationMember.objects.filter(
                organization=from_org):
            try:
                to_member = OrganizationMember.objects.get(
                    organization=to_org,
                    user=from_member.user,
                )
            except OrganizationMember.DoesNotExist:
                from_member.update(organization=to_org)
                to_member = from_member
            else:
                qs = OrganizationMemberTeam.objects.filter(
                    organizationmember=from_member,
                    is_active=True,
                ).select_related()
                for omt in qs:
                    OrganizationMemberTeam.objects.create_or_update(
                        organizationmember=to_member,
                        team=omt.team,
                        defaults={
                            'is_active': True,
                        },
                    )

        for team in Team.objects.filter(organization=from_org):
            try:
                with transaction.atomic():
                    team.update(organization=to_org)
            except IntegrityError:
                slugify_instance(team, team.name, organization=to_org)
                team.update(
                    organization=to_org,
                    slug=team.slug,
                )

        for project in Project.objects.filter(organization=from_org):
            try:
                with transaction.atomic():
                    project.update(organization=to_org)
            except IntegrityError:
                slugify_instance(project, project.name, organization=to_org)
                project.update(
                    organization=to_org,
                    slug=project.slug,
                )

        # TODO(jess): update this when adding unique constraint
        # on version, organization for releases
        for release in Release.objects.filter(organization=from_org):
            try:
                to_release = Release.objects.get(version=release.version,
                                                 organization=to_org)
            except Release.DoesNotExist:
                Release.objects.filter(id=release.id).update(
                    organization=to_org)
            else:
                Release.merge(to_release, [release])

        for model in (ApiKey, AuditLogEntry, ReleaseFile):
            model.objects.filter(
                organization=from_org, ).update(organization=to_org)

        for model in (Commit, ReleaseCommit, ReleaseEnvironment, Repository):
            model.objects.filter(organization_id=from_org.id, ).update(
                organization_id=to_org.id)

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.set_value(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.get_value(self, *args, **kwargs)

    def delete_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.unset_value(self, *args, **kwargs)

    def send_delete_confirmation(self, audit_log_entry, countdown):
        from sentry import options
        from sentry.utils.email import MessageBuilder

        owners = self.get_owners()

        context = {
            'organization':
            self,
            'audit_log_entry':
            audit_log_entry,
            'eta':
            timezone.now() + timedelta(seconds=countdown),
            'url':
            absolute_uri(
                reverse(
                    'sentry-restore-organization',
                    args=[self.slug],
                )),
        }

        MessageBuilder(
            subject='%sOrganization Queued for Deletion' %
            (options.get('mail.subject-prefix'), ),
            template='sentry/emails/org_delete_confirm.txt',
            html_template='sentry/emails/org_delete_confirm.html',
            type='org.confirm_delete',
            context=context,
        ).send_async([o.email for o in owners])
예제 #7
0
class Organization(Model):
    """
    An organization represents a group of individuals which maintain ownership of projects.
    """
    name = models.CharField(max_length=64)
    slug = models.SlugField(unique=True)
    status = BoundedPositiveIntegerField(choices=(
        (OrganizationStatus.VISIBLE, _('Visible')),
        (OrganizationStatus.PENDING_DELETION, _('Pending Deletion')),
        (OrganizationStatus.DELETION_IN_PROGRESS, _('Deletion in Progress')),
    ),
                                         default=OrganizationStatus.VISIBLE)
    date_added = models.DateTimeField(default=timezone.now)
    members = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                     through='sentry.OrganizationMember',
                                     related_name='org_memberships')
    default_role = models.CharField(
        choices=roles.get_choices(),
        max_length=32,
        default=roles.get_default().id,
    )

    flags = BitField(flags=(
        ('allow_joinleave',
         'Allow members to join and leave teams without requiring approval.'),
        ('enhanced_privacy',
         'Enable enhanced privacy controls to limit personally identifiable information (PII) as well as source code in things like notifications.'
         ),
        ('disable_shared_issues',
         'Disable sharing of limited details on issues to anonymous users.'),
    ),
                     default=1)

    objects = OrganizationManager(cache_fields=(
        'pk',
        'slug',
    ))

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_organization'

    __repr__ = sane_repr('owner_id', 'name', 'slug')

    @classmethod
    def get_default(cls):
        """
        Return the organization used in single organization mode.
        """
        return cls.objects.filter(status=OrganizationStatus.VISIBLE, )[0]

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.slug)

    def save(self, *args, **kwargs):
        if not self.slug:
            lock_key = 'slug:organization'
            with Lock(lock_key):
                slugify_instance(self,
                                 self.name,
                                 reserved=RESERVED_ORGANIZATION_SLUGS)
            super(Organization, self).save(*args, **kwargs)
        else:
            super(Organization, self).save(*args, **kwargs)

    def delete(self):
        if self.is_default:
            raise Exception('You cannot delete the the default organization.')
        return super(Organization, self).delete()

    @cached_property
    def is_default(self):
        if not settings.SENTRY_SINGLE_ORGANIZATION:
            return False

        return self == type(self).get_default()

    def has_access(self, user, access=None):
        queryset = self.member_set.filter(user=user)
        if access is not None:
            queryset = queryset.filter(type__lte=access)

        return queryset.exists()

    def get_audit_log_data(self):
        return {
            'id': self.id,
            'slug': self.slug,
            'name': self.name,
            'status': self.status,
            'flags': self.flags,
            'default_role': self.default_role,
        }

    def get_default_owner(self):
        if not hasattr(self, '_default_owner'):
            from sentry.models import User

            self._default_owner = User.objects.filter(
                sentry_orgmember_set__role=roles.get_top_dog().id,
                sentry_orgmember_set__organization=self,
            )[0]
        return self._default_owner

    def has_single_owner(self):
        from sentry.models import OrganizationMember
        count = OrganizationMember.objects.filter(
            organization=self,
            role='owner',
            user__isnull=False,
        ).count()
        return count == 1

    def merge_to(from_org, to_org):
        from sentry.models import (ApiKey, AuditLogEntry, OrganizationMember,
                                   OrganizationMemberTeam, Project, Team)

        for from_member in OrganizationMember.objects.filter(
                organization=from_org):
            try:
                to_member = OrganizationMember.objects.get(
                    organization=to_org,
                    user=from_member.user,
                )
            except OrganizationMember.DoesNotExist:
                from_member.update(organization=to_org)
                to_member = from_member
            else:
                qs = OrganizationMemberTeam.objects.filter(
                    organizationmember=from_member,
                    is_active=True,
                ).select_related()
                for omt in qs:
                    OrganizationMemberTeam.objects.create_or_update(
                        organizationmember=to_member,
                        team=omt.team,
                        defaults={
                            'is_active': True,
                        },
                    )

        for team in Team.objects.filter(organization=from_org):
            try:
                with transaction.atomic():
                    team.update(organization=to_org)
            except IntegrityError:
                slugify_instance(team, team.name, organization=to_org)
                team.update(
                    organization=to_org,
                    slug=team.slug,
                )

        for project in Project.objects.filter(organization=from_org):
            try:
                with transaction.atomic():
                    project.update(organization=to_org)
            except IntegrityError:
                slugify_instance(project, project.name, organization=to_org)
                project.update(
                    organization=to_org,
                    slug=project.slug,
                )

        for model in (ApiKey, AuditLogEntry):
            model.objects.filter(
                organization=from_org, ).update(organization=to_org)

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.set_value(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.get_value(self, *args, **kwargs)

    def delete_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.unset_value(self, *args, **kwargs)
예제 #8
0
class Child(models.Model):
    GENDER_CHOICES = Choices(
        ("m", _("male")),
        ("f", _("female")),
        ("o", _("other")),
        ("na", _("prefer not to answer")),
    )

    # Deprecating
    AGE_AT_BIRTH_CHOICES = Choices(
        ("na", _("Not sure or prefer not to answer")),
        ("<24", _("Under 24 weeks")),
        ("24", _("24 weeks")),
        ("25", _("25 weeks")),
        ("26", _("26 weeks")),
        ("27", _("27 weeks")),
        ("28", _("28 weeks")),
        ("29", _("29 weeks")),
        ("30", _("30 weeks")),
        ("31", _("31 weeks")),
        ("32", _("32 weeks")),
        ("33", _("33 weeks")),
        ("34", _("34 weeks")),
        ("35", _("35 weeks")),
        ("36", _("36 weeks")),
        ("37", _("37 weeks")),
        ("38", _("38 weeks")),
        ("39", _("39 weeks")),
        ("40>", _("40 or more weeks")),
    )

    uuid = models.UUIDField(verbose_name="identifier",
                            default=uuid.uuid4,
                            unique=True,
                            db_index=True)
    given_name = models.CharField(max_length=255)
    birthday = models.DateField(blank=True, null=True)
    gender = models.CharField(max_length=2, choices=GENDER_CHOICES)
    gestational_age_at_birth = models.PositiveSmallIntegerField(
        choices=GESTATIONAL_AGE_CHOICES,
        default=GESTATIONAL_AGE_CHOICES.no_answer,
        null=True,
        blank=True,
    )
    additional_information = models.TextField(blank=True)
    deleted = models.BooleanField(default=False)
    former_lookit_profile_id = models.CharField(max_length=255, blank=True)
    existing_conditions = BitField(flags=CONDITIONS, default=0)
    languages_spoken = BitField(flags=LANGUAGES, default=0)

    user = models.ForeignKey(
        "accounts.User",
        related_name="children",
        related_query_name="children",
        on_delete=models.
        CASCADE  # if deleting User, also delete associated Child -
        # although may not be possible depending on Responses already associated
    )

    objects = BitfieldQuerySet.as_manager()

    def __str__(self):
        return f"<Child: {self.given_name}>"

    @property
    def age_at_birth(self):
        return GESTATIONAL_AGE_CHOICES[self.gestational_age_at_birth]

    @property
    def language_list(self):
        return " ".join([
            langcode for langcode, boolean in self.languages_spoken.items()
            if boolean
        ])

    @property
    def condition_list(self):
        return " ".join([
            condition
            for condition, boolean in self.existing_conditions.items()
            if boolean
        ])

    class Meta:
        permissions = [("can_view_all_children_in_analytics",
                        "Can view all children in analytics")]
        ordering = ["-birthday"]

    class JSONAPIMeta:
        resource_name = "children"
        lookup_field = "uuid"
예제 #9
0
 class TestModel(models.Model):
     flags = BitField(flags=(u"FLAG_0", u"FLAG_1", u"FLAG_2",
                             u"FLAG_3"),
                      default=(u"FLAG_1", u"FLAG_2"))
class ApiToken(Model):
    __core__ = True

    # users can generate tokens without being application-bound
    application = FlexibleForeignKey('sentry.ApiApplication', null=True)
    user = FlexibleForeignKey('sentry.User')
    token = models.CharField(max_length=64,
                             unique=True,
                             default=lambda: ApiToken.generate_token())
    refresh_token = models.CharField(max_length=64,
                                     unique=True,
                                     null=True,
                                     default=lambda: ApiToken.generate_token())
    expires_at = models.DateTimeField(
        null=True, default=lambda: timezone.now() + DEFAULT_EXPIRATION)
    scopes = BitField(flags=(
        ('project:read', 'project:read'),
        ('project:write', 'project:write'),
        ('project:admin', 'project:admin'),
        ('project:releases', 'project:releases'),
        ('team:read', 'team:read'),
        ('team:write', 'team:write'),
        ('team:admin', 'team:admin'),
        ('event:read', 'event:read'),
        ('event:write', 'event:write'),
        ('event:admin', 'event:admin'),
        ('org:read', 'org:read'),
        ('org:write', 'org:write'),
        ('org:admin', 'org:admin'),
        ('member:read', 'member:read'),
        ('member:write', 'member:write'),
        ('member:admin', 'member:admin'),
    ))
    scope_list = ArrayField(of=models.TextField)
    date_added = models.DateTimeField(default=timezone.now)

    objects = BaseManager(cache_fields=('token', ))

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_apitoken'

    __repr__ = sane_repr('user_id', 'token', 'application_id')

    def __unicode__(self):
        return six.text_type(self.token)

    @classmethod
    def generate_token(cls):
        return uuid4().hex + uuid4().hex

    @classmethod
    def from_grant(cls, grant):
        with transaction.atomic():
            return cls.objects.create(
                application=grant.application,
                user=grant.user,
                scope_list=grant.get_scopes(),
            )

    def is_expired(self):
        if not self.expires_at:
            return False

        return timezone.now() >= self.expires_at

    def get_audit_log_data(self):
        return {
            'scopes': self.get_scopes(),
        }

    def get_scopes(self):
        if self.scope_list:
            return self.scope_list
        return [k for k, v in six.iteritems(self.scopes) if v]

    def has_scope(self, scope):
        return scope in self.get_scopes()

    def get_allowed_origins(self):
        if self.application:
            return self.application.get_allowed_origins()
        return ()

    def refresh(self, expires_at=None):
        if expires_at is None:
            expires_at = timezone.now() + DEFAULT_EXPIRATION

        self.update(
            token=type(self).generate_token(),
            refresh_token=type(self).generate_token(),
            expires_at=expires_at,
        )
예제 #11
0
class OrganizationMember(Model):
    """
    Identifies relationships between teams and users.

    Users listed as team members are considered to have access to all projects
    and could be thought of as team owners (though their access level may not)
    be set to ownership.
    """
    organization = FlexibleForeignKey('sentry.Organization',
                                      related_name="member_set")

    user = FlexibleForeignKey(settings.AUTH_USER_MODEL,
                              null=True,
                              blank=True,
                              related_name="sentry_orgmember_set")
    email = models.EmailField(null=True, blank=True)
    role = models.CharField(
        choices=roles.get_choices(),
        max_length=32,
        default=roles.get_default().id,
    )
    flags = BitField(flags=(
        ('sso:linked', 'sso:linked'),
        ('sso:invalid', 'sso:invalid'),
    ),
                     default=0)
    date_added = models.DateTimeField(default=timezone.now)
    has_global_access = models.BooleanField(default=True)
    counter = BoundedPositiveIntegerField(null=True, blank=True)
    teams = models.ManyToManyField('sentry.Team',
                                   blank=True,
                                   through='sentry.OrganizationMemberTeam')

    # Deprecated -- no longer used
    type = BoundedPositiveIntegerField(default=50, blank=True)

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_organizationmember'
        unique_together = (
            ('organization', 'user'),
            ('organization', 'email'),
        )

    __repr__ = sane_repr(
        'organization_id',
        'user_id',
        'role',
    )

    @transaction.atomic
    def save(self, *args, **kwargs):
        assert self.user_id or self.email, \
            'Must set user or email'
        super(OrganizationMember, self).save(*args, **kwargs)

        if not self.counter:
            self._set_counter()

    @transaction.atomic
    def delete(self, *args, **kwargs):
        super(OrganizationMember, self).delete(*args, **kwargs)
        if self.counter:
            self._unshift_counter()

    def _unshift_counter(self):
        assert self.counter
        OrganizationMember.objects.filter(
            organization=self.organization,
            counter__gt=self.counter,
        ).update(counter=F('counter') - 1, )

    def _set_counter(self):
        assert self.id and not self.counter
        # XXX(dcramer): this isnt atomic, but unfortunately MySQL doesnt support
        # the subquery pattern we'd need
        self.update(counter=OrganizationMember.objects.filter(
            organization=self.organization, ).count(), )

    @property
    def is_pending(self):
        return self.user_id is None

    @property
    def token(self):
        checksum = md5()
        for x in (str(self.organization_id), self.get_email(),
                  settings.SECRET_KEY):
            checksum.update(x)
        return checksum.hexdigest()

    def send_invite_email(self):
        from sentry.utils.email import MessageBuilder

        context = {
            'email':
            self.email,
            'organization':
            self.organization,
            'url':
            absolute_uri(
                reverse('sentry-accept-invite',
                        kwargs={
                            'member_id': self.id,
                            'token': self.token,
                        })),
        }

        msg = MessageBuilder(
            subject='Join %s in using Sentry' % self.organization.name,
            template='sentry/emails/member-invite.txt',
            html_template='sentry/emails/member-invite.html',
            context=context,
        )

        try:
            msg.send([self.get_email()])
        except Exception as e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)

    def send_sso_link_email(self):
        from sentry.utils.email import MessageBuilder

        context = {
            'email':
            self.email,
            'organization_name':
            self.organization.name,
            'url':
            absolute_uri(
                reverse('sentry-auth-organization',
                        kwargs={
                            'organization_slug': self.organization.slug,
                        })),
        }

        msg = MessageBuilder(
            subject='Action Required for %s' % (self.organization.name, ),
            template='sentry/emails/auth-link-identity.txt',
            html_template='sentry/emails/auth-link-identity.html',
            context=context,
        )
        msg.send_async([self.get_email()])

    def get_display_name(self):
        if self.user_id:
            return self.user.get_display_name()
        return self.email

    def get_label(self):
        if self.user_id:
            return self.user.get_label()
        return self.email or self.id

    def get_email(self):
        if self.user_id:
            return self.user.email
        return self.email

    def get_avatar_type(self):
        if self.user_id:
            return self.user.get_avatar_type()

    def get_audit_log_data(self):
        from sentry.models import Team
        return {
            'email':
            self.email,
            'user':
            self.user_id,
            'teams':
            list(
                Team.objects.filter(
                    id__in=OrganizationMemberTeam.objects.filter(
                        organizationmember=self,
                        is_active=True,
                    ).values_list('team', flat=True))),
            'has_global_access':
            self.has_global_access,
            'role':
            self.role,
        }

    def get_teams(self):
        from sentry.models import Team

        if roles.get(self.role).is_global:
            return self.organization.team_set.all()

        return Team.objects.filter(
            id__in=OrganizationMemberTeam.objects.filter(
                organizationmember=self,
                is_active=True,
            ).values('team'))

    def get_scopes(self):
        return roles.get(self.role).scopes

    def can_manage_member(self, member):
        return roles.can_manage(self.role, member.role)
예제 #12
0
class OrganizationMember(Model):
    """
    Identifies relationships between teams and users.

    Users listed as team members are considered to have access to all projects
    and could be thought of as team owners (though their access level may not)
    be set to ownership.
    """
    organization = FlexibleForeignKey('sentry.Organization', related_name="member_set")

    user = FlexibleForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
                             related_name="sentry_orgmember_set")
    email = models.EmailField(null=True, blank=True)

    type = BoundedPositiveIntegerField(choices=(
        (OrganizationMemberType.BOT, _('Bot')),
        (OrganizationMemberType.MEMBER, _('Member')),
        (OrganizationMemberType.ADMIN, _('Admin')),
        (OrganizationMemberType.OWNER, _('Owner')),
    ), default=OrganizationMemberType.MEMBER)
    flags = BitField(flags=(
        ('sso:linked', 'sso:linked'),
        ('sso:invalid', 'sso:invalid'),
    ), default=0)
    date_added = models.DateTimeField(default=timezone.now)
    has_global_access = models.BooleanField(default=True)
    teams = models.ManyToManyField('sentry.Team', blank=True)

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_organizationmember'
        unique_together = (('organization', 'user'), ('organization', 'email'))

    __repr__ = sane_repr('organization_id', 'user_id', 'type')

    def save(self, *args, **kwargs):
        assert self.user_id or self.email, \
            'Must set user or email'
        return super(OrganizationMember, self).save(*args, **kwargs)

    @property
    def is_pending(self):
        return self.user_id is None

    @property
    def token(self):
        checksum = md5()
        for x in (str(self.organization_id), self.get_email(), settings.SECRET_KEY):
            checksum.update(x)
        return checksum.hexdigest()

    @property
    def scopes(self):
        scopes = []
        if self.type <= OrganizationMemberType.MEMBER:
            scopes.extend([
                'event:read', 'event:write', 'event:delete',
                'org:read', 'project:read', 'team:read',
            ])
        if self.type <= OrganizationMemberType.ADMIN:
            scopes.extend(['project:write', 'team:write'])
        if self.type <= OrganizationMemberType.OWNER:
            scopes.extend(['project:delete', 'team:delete'])
        if self.has_global_access:
            if self.type <= OrganizationMemberType.ADMIN:
                scopes.extend(['org:write'])
            if self.type <= OrganizationMemberType.OWNER:
                scopes.extend(['org:delete'])
        return scopes

    def send_invite_email(self):
        from sentry.utils.email import MessageBuilder

        context = {
            'email': self.email,
            'organization': self.organization,
            'url': absolute_uri(reverse('sentry-accept-invite', kwargs={
                'member_id': self.id,
                'token': self.token,
            })),
        }

        msg = MessageBuilder(
            subject='Invite to join organization: %s' % (self.organization.name,),
            template='sentry/emails/member_invite.txt',
            context=context,
        )

        try:
            msg.send([self.get_email()])
        except Exception as e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)

    def send_sso_link_email(self):
        from sentry.utils.email import MessageBuilder

        context = {
            'email': self.email,
            'organization_name': self.organization.name,
            'url': absolute_uri(reverse('sentry-auth-link-identity', kwargs={
                'organization_slug': self.organization.slug,
            })),
        }

        msg = MessageBuilder(
            subject='Action Required for %s' % (self.organization.name,),
            template='sentry/emails/auth-link-identity.txt',
            html_template='sentry/emails/auth-link-identity.html',
            context=context,
        )

        try:
            msg.send([self.get_email()])
        except Exception as e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)

    def get_display_name(self):
        if self.user_id:
            return self.user.get_display_name()
        return self.email

    def get_email(self):
        if self.user_id:
            return self.user.email
        return self.email

    def get_audit_log_data(self):
        return {
            'email': self.email,
            'user': self.user_id,
            'teams': [t.id for t in self.teams.all()],
            'has_global_access': self.has_global_access,
        }
예제 #13
0
class Game(models.Model):
    """Game model"""
    GAME_FLAGS = (
        ('fully_libre', 'Fully libre'),
        ('open_engine', 'Open engine only'),
        ('free', 'Free'),
        ('freetoplay', 'Free-to-play'),
        ('pwyw', 'Pay what you want'),
        ('demo', 'Has a demo'),
    )
    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, blank=False)
    year = models.IntegerField(null=True, blank=True)
    platforms = models.ManyToManyField(Platform)
    genres = models.ManyToManyField(Genre)
    publisher = models.ForeignKey(Company,
                                  related_name='published_game',
                                  null=True,
                                  blank=True)
    developer = models.ForeignKey(Company,
                                  related_name='developed_game',
                                  null=True,
                                  blank=True)
    website = models.CharField(max_length=200, blank=True)
    icon = models.ImageField(upload_to='games/icons', blank=True)
    title_logo = models.ImageField(upload_to='games/banners', blank=True)
    description = models.TextField(blank=True)
    is_public = models.BooleanField("Published", default=False)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    steamid = models.PositiveIntegerField(null=True, blank=True)
    gogid = models.CharField(max_length=200, blank=True)
    humblestoreid = models.CharField(max_length=200, blank=True)
    flags = BitField(flags=GAME_FLAGS)

    objects = GameManager()

    # pylint: disable=W0232, R0903
    class Meta(object):
        ordering = ['name']
        permissions = (('can_publish_game', "Can publish game"), )

    def __unicode__(self):
        return self.name

    @staticmethod
    def autocomplete_search_fields():
        return ("name__icontains", )

    @property
    def banner_url(self):
        if self.title_logo:
            return reverse('get_banner', kwargs={'slug': self.slug})

    @property
    def icon_url(self):
        if self.icon:
            return reverse('get_icon', kwargs={'slug': self.slug})

    @property
    def flag_labels(self):
        """Return labels of active flags, suitable for display"""
        return [
            self.flags.get_label(flag[0]) for flag in self.flags if flag[1]
        ]

    def has_installer(self):
        return self.installers.exists() or self.has_auto_installers()

    def has_auto_installers(self):
        return self.platforms.filter(default_installer__isnull=False).exists()

    def get_absolute_url(self):
        """Return the absolute url for a game"""
        return reverse("game_detail", kwargs={'slug': self.slug})

    def download_steam_capsule(self):
        if self.title_logo or not self.steamid:
            return
        else:
            self.title_logo = ContentFile(steam.get_capsule(self.steamid),
                                          "%d.jpg" % self.steamid)

    def get_steam_logo(self, img_url):
        self.title_logo = ContentFile(steam.get_image(self.steamid, img_url),
                                      "%d.jpg" % self.steamid)

    def get_steam_icon(self, img_url):
        self.icon = ContentFile(steam.get_image(self.steamid, img_url),
                                "%d.jpg" % self.steamid)

    def steam_support(self):
        """ Return the platform supported by Steam """
        if not self.steamid:
            return False
        platforms = [p.slug for p in self.platforms.all()]
        if 'linux' in platforms:
            return 'linux'
        elif 'windows' in platforms:
            return 'windows'
        else:
            return True

    def get_default_installers(self):
        installers = []
        for platform in self.platforms.all():
            if platform.default_installer:
                installer = platform.default_installer
                installer['name'] = self.name
                installer['game_slug'] = self.slug
                installer['version'] = platform.name
                installer['slug'] = "-".join(
                    (self.slug[:30], platform.slug[:20]))
                installer['platform'] = platform.slug
                installer['description'] = ""
                installer['published'] = True
                installer['auto'] = True
                installers.append(installer)
        return installers

    def check_for_submission(self):

        # Skip freshly created and unpublished objects
        if not self.pk or not self.is_public:
            return

        # Skip objects that were already published
        original = Game.objects.get(pk=self.pk)
        if original.is_public:
            return

        try:
            submission = GameSubmission.objects.get(game=self,
                                                    accepted_at__isnull=True)
        except GameSubmission.DoesNotExist:
            pass
        else:
            submission.accept()

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)[:50]
        self.download_steam_capsule()
        self.check_for_submission()
        return super(Game, self).save(*args, **kwargs)
예제 #14
0
class Project(Model, PendingDeletionMixin):
    from sentry.models.projectteam import ProjectTeam
    """
    Projects are permission based namespaces which generally
    are the top level entry point for all data.
    """

    __include_in_export__ = True

    slug = models.SlugField(null=True)
    name = models.CharField(max_length=200)
    forced_color = models.CharField(max_length=6, null=True, blank=True)
    organization = FlexibleForeignKey("sentry.Organization")
    teams = models.ManyToManyField("sentry.Team",
                                   related_name="teams",
                                   through=ProjectTeam)
    public = models.BooleanField(default=False)
    date_added = models.DateTimeField(default=timezone.now)
    status = BoundedPositiveIntegerField(
        default=0,
        choices=(
            (ObjectStatus.VISIBLE, _("Active")),
            (ObjectStatus.PENDING_DELETION, _("Pending Deletion")),
            (ObjectStatus.DELETION_IN_PROGRESS, _("Deletion in Progress")),
        ),
        db_index=True,
    )
    # projects that were created before this field was present
    # will have their first_event field set to date_added
    first_event = models.DateTimeField(null=True)
    flags = BitField(
        flags=(
            ("has_releases", "This Project has sent release data"),
            ("has_issue_alerts_targeting",
             "This Project has issue alerts targeting"),
            ("has_transactions", "This Project has sent transactions"),
            ("has_alert_filters", "This Project has filters"),
            ("has_sessions", "This Project has sessions"),
        ),
        default=10,
        null=True,
    )

    objects = ProjectManager(cache_fields=["pk"])
    platform = models.CharField(max_length=64, null=True)

    class Meta:
        app_label = "sentry"
        db_table = "sentry_project"
        unique_together = (("organization", "slug"), )

    __repr__ = sane_repr("team_id", "name", "slug")

    _rename_fields_on_pending_delete = frozenset(["slug"])

    def __str__(self):
        return f"{self.name} ({self.slug})"

    def next_short_id(self):
        from sentry.models import Counter

        with sentry_sdk.start_span(
                op="project.next_short_id") as span, metrics.timer(
                    "project.next_short_id"):
            span.set_data("project_id", self.id)
            span.set_data("project_slug", self.slug)
            return Counter.increment(self)

    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,
                    max_length=50,
                )
            super().save(*args, **kwargs)
        else:
            super().save(*args, **kwargs)
        self.update_rev_for_option()

    def get_absolute_url(self, params=None):
        url = f"/organizations/{self.organization.slug}/issues/"
        params = {} if params is None else params
        params["project"] = self.id
        if params:
            url = url + "?" + urlencode(params)
        return absolute_uri(url)

    def is_internal_project(self):
        for value in (settings.SENTRY_FRONTEND_PROJECT,
                      settings.SENTRY_PROJECT):
            if str(self.id) == str(value) or str(self.slug) == str(value):
                return True
        return False

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        return projectoptions.set(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        return projectoptions.get(self, *args, **kwargs)

    def delete_option(self, *args, **kwargs):
        return projectoptions.delete(self, *args, **kwargs)

    def update_rev_for_option(self):
        return projectoptions.update_rev_for_option(self)

    @property
    def color(self):
        if self.forced_color is not None:
            return f"#{self.forced_color}"
        return get_hashed_color(self.slug.upper())

    @property
    def member_set(self):
        """:returns a QuerySet of all Users that belong to this Project"""
        from sentry.models import OrganizationMember

        return self.organization.member_set.filter(
            id__in=OrganizationMember.objects.filter(
                organizationmemberteam__is_active=True,
                organizationmemberteam__team__in=self.teams.all(),
            ).values("id"),
            user__is_active=True,
        ).distinct()

    def has_access(self, user, access=None):
        from sentry.models import AuthIdentity, OrganizationMember

        warnings.warn("Project.has_access is deprecated.", DeprecationWarning)

        queryset = self.member_set.filter(user=user)

        if access is not None:
            queryset = queryset.filter(type__lte=access)

        try:
            member = queryset.get()
        except OrganizationMember.DoesNotExist:
            return False

        try:
            auth_identity = AuthIdentity.objects.get(
                auth_provider__organization=self.organization_id,
                user=member.user_id)
        except AuthIdentity.DoesNotExist:
            return True

        return auth_identity.is_valid(member)

    def get_audit_log_data(self):
        return {
            "id": self.id,
            "slug": self.slug,
            "name": self.name,
            "status": self.status,
            "public": self.public,
        }

    def get_full_name(self):
        return self.slug

    def transfer_to(self, team=None, organization=None):
        # NOTE: this will only work properly if the new team is in a different
        # org than the existing one, which is currently the only use case in
        # production
        # TODO(jess): refactor this to make it an org transfer only
        from sentry.incidents.models import AlertRule
        from sentry.models import (
            Environment,
            EnvironmentProject,
            ProjectTeam,
            ReleaseProject,
            ReleaseProjectEnvironment,
            Rule,
        )
        from sentry.models.actor import ACTOR_TYPES

        if organization is None:
            organization = team.organization

        old_org_id = self.organization_id
        org_changed = old_org_id != organization.id

        self.organization = organization

        try:
            with transaction.atomic():
                self.update(organization=organization)
        except IntegrityError:
            slugify_instance(self,
                             self.name,
                             organization=organization,
                             max_length=50)
            self.update(slug=self.slug, organization=organization)

        # Both environments and releases are bound at an organization level.
        # Due to this, when you transfer a project into another org, we have to
        # handle this behavior somehow. We really only have two options here:
        # * Copy over all releases/environments into the new org and handle de-duping
        # * Delete the bindings and let them reform with new data.
        # We're generally choosing to just delete the bindings since new data
        # flowing in will recreate links correctly. The tradeoff is that
        # historical data is lost, but this is a compromise we're willing to
        # take and a side effect of allowing this feature. There are exceptions
        # to this however, such as rules, which should maintain their
        # configuration when moved across organizations.
        if org_changed:
            for model in ReleaseProject, ReleaseProjectEnvironment, EnvironmentProject:
                model.objects.filter(project_id=self.id).delete()
            # this is getting really gross, but make sure there aren't lingering associations
            # with old orgs or teams
            ProjectTeam.objects.filter(
                project=self, team__organization_id=old_org_id).delete()

        rules_by_environment_id = defaultdict(set)
        for rule_id, environment_id in Rule.objects.filter(
                project_id=self.id, environment_id__isnull=False).values_list(
                    "id", "environment_id"):
            rules_by_environment_id[environment_id].add(rule_id)

        environment_names = dict(
            Environment.objects.filter(
                id__in=rules_by_environment_id).values_list("id", "name"))

        for environment_id, rule_ids in rules_by_environment_id.items():
            Rule.objects.filter(id__in=rule_ids).update(
                environment_id=Environment.get_or_create(
                    self, environment_names[environment_id]).id)

        # ensure this actually exists in case from team was null
        if team is not None:
            self.add_team(team)

        # Remove alert owners not in new org
        alert_rules = AlertRule.objects.fetch_for_project(self).filter(
            owner_id__isnull=False)
        rules = Rule.objects.filter(owner_id__isnull=False, project=self)
        for rule in list(chain(alert_rules, rules)):
            actor = rule.owner
            if actor.type == ACTOR_TYPES["user"]:
                is_member = organization.member_set.filter(
                    user=actor.resolve()).exists()
            if actor.type == ACTOR_TYPES["team"]:
                is_member = actor.resolve().organization_id == organization.id
            if not is_member:
                rule.update(owner=None)

        AlertRule.objects.fetch_for_project(self).update(
            organization=organization)

    def add_team(self, team):
        from sentry.models.projectteam import ProjectTeam

        try:
            with transaction.atomic():
                ProjectTeam.objects.create(project=self, team=team)
        except IntegrityError:
            return False
        else:
            return True

    def remove_team(self, team):
        from sentry.incidents.models import AlertRule
        from sentry.models import Rule
        from sentry.models.projectteam import ProjectTeam

        ProjectTeam.objects.filter(project=self, team=team).delete()
        AlertRule.objects.fetch_for_project(self).filter(
            owner_id=team.actor_id).update(owner=None)
        Rule.objects.filter(owner_id=team.actor_id,
                            project=self).update(owner=None)

    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 get_lock_key(self):
        return f"project_token:{self.id}"

    def copy_settings_from(self, project_id):
        """
        Copies project level settings of the inputted project
        - General Settings
        - ProjectTeams
        - Alerts Settings and Rules
        - EnvironmentProjects
        - ProjectOwnership Rules and settings
        - Project Inbound Data Filters

        Returns True if the settings have successfully been copied over
        Returns False otherwise
        """
        from sentry.models import (
            EnvironmentProject,
            ProjectOption,
            ProjectOwnership,
            ProjectTeam,
            Rule,
        )

        model_list = [EnvironmentProject, ProjectOwnership, ProjectTeam, Rule]

        project = Project.objects.get(id=project_id)
        try:
            with transaction.atomic():
                for model in model_list:
                    # remove all previous project settings
                    model.objects.filter(project_id=self.id).delete()

                    # add settings from other project to self
                    for setting in model.objects.filter(project_id=project_id):
                        setting.pk = None
                        setting.project_id = self.id
                        setting.save()

                options = ProjectOption.objects.get_all_values(project=project)
                for key, value in options.items():
                    self.update_option(key, value)

        except IntegrityError as e:
            logging.exception(
                "Error occurred during copy project settings.",
                extra={
                    "error": str(e),
                    "project_to": self.id,
                    "project_from": project_id,
                },
            )
            return False
        return True

    @staticmethod
    def is_valid_platform(value):
        if not value or value == "other":
            return True
        return integration_doc_exists(value)

    def delete(self, **kwargs):
        from sentry.models import NotificationSetting

        # There is no foreign key relationship so we have to manually cascade.
        NotificationSetting.objects.remove_for_project(self)

        return super().delete(**kwargs)
예제 #15
0
class Organization(Model):
    """
    An organization represents a group of individuals which maintain ownership of projects.
    """
    name = models.CharField(max_length=64)
    slug = models.SlugField(unique=True)
    status = BoundedPositiveIntegerField(choices=(
        (OrganizationStatus.VISIBLE, _('Visible')),
        (OrganizationStatus.PENDING_DELETION, _('Pending Deletion')),
        (OrganizationStatus.DELETION_IN_PROGRESS, _('Deletion in Progress')),
    ),
                                         default=OrganizationStatus.VISIBLE)
    date_added = models.DateTimeField(default=timezone.now)
    members = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                     through='sentry.OrganizationMember',
                                     related_name='org_memberships')

    flags = BitField(flags=((
        'allow_joinleave',
        'Allow members to join and leave teams without requiring approval.'),
                            ),
                     default=0)

    objects = OrganizationManager(cache_fields=(
        'pk',
        'slug',
    ))

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_organization'

    __repr__ = sane_repr('owner_id', 'name', 'slug')

    @classmethod
    def get_default(cls):
        """
        Return the organization used in single organization mode.
        """
        return cls.objects.filter(status=OrganizationStatus.VISIBLE, )[0]

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.slug)

    def save(self, *args, **kwargs):
        if not self.slug:
            lock_key = 'slug:organization'
            with Lock(lock_key):
                slugify_instance(self,
                                 self.name,
                                 reserved=RESERVED_ORGANIZATION_SLUGS)
            super(Organization, self).save(*args, **kwargs)
        else:
            super(Organization, self).save(*args, **kwargs)

    def delete(self):
        if self.is_default:
            raise Exception('You cannot delete the the default organization.')
        return super(Organization, self).delete()

    @cached_property
    def is_default(self):
        if not settings.SENTRY_SINGLE_ORGANIZATION:
            return False

        return self == type(self).get_default()

    def has_access(self, user, access=None):
        queryset = self.member_set.filter(user=user)
        if access is not None:
            queryset = queryset.filter(type__lte=access)

        return queryset.exists()

    def get_audit_log_data(self):
        return {
            'slug': self.slug,
            'name': self.name,
            'status': self.status,
            'flags': self.flags,
        }

    def get_default_owner(self):
        from sentry.models import OrganizationMemberType, User

        return User.objects.filter(
            sentry_orgmember_set__type=OrganizationMemberType.OWNER,
            sentry_orgmember_set__organization=self,
        )[0]

    def has_single_owner(self):
        from sentry.models import OrganizationMember, OrganizationMemberType
        count = OrganizationMember.objects.filter(
            organization=self,
            type=OrganizationMemberType.OWNER,
            has_global_access=True,
            user__isnull=False,
        ).count()
        return count == 1

    def merge_to(from_org, to_org):
        from sentry.models import (ApiKey, AuditLogEntry, OrganizationMember,
                                   OrganizationMemberTeam, Project, Team)

        team_list = list(Team.objects.filter(organization=to_org, ))

        for from_member in OrganizationMember.objects.filter(
                organization=from_org):
            try:
                to_member = OrganizationMember.objects.get(
                    organization=to_org,
                    user=from_member.user,
                )
            except OrganizationMember.DoesNotExist:
                from_member.update(organization=to_org)
                to_member = from_member

            if to_member.has_global_access:
                for team in team_list:
                    OrganizationMemberTeam.objects.get_or_create(
                        organizationmember=to_member,
                        team=team,
                        defaults={
                            'is_active': False,
                        },
                    )

        for model in (Team, Project, ApiKey, AuditLogEntry):
            model.objects.filter(
                organization=from_org, ).update(organization=to_org)
예제 #16
0
from bitfield import BitField
from enum import Enum, Flag

reserved = BitField(31, 31)
instr_field = BitField(26, 30)
cond_field = BitField(22, 25)
reg_target_field = BitField(18, 21)
reg_src1_field = BitField(14, 17)
reg_src2_field = BitField(10, 13)
offset_field = BitField(0, 9)

# Registers are numbered from 0 to 15, and have names
# like r3, r15, etc.  Two special registers have additional
# names:  r0 is called 'zero' because on the DM2020W it always
# holds value 0, and r15 is called 'pc' because it is used to
# hold the program counter.
#
NAMED_REGS = {
    "r0": 0,
    "zero": 0,
    "r1": 1,
    "r2": 2,
    "r3": 3,
    "r4": 4,
    "r5": 5,
    "r6": 6,
    "r7": 7,
    "r8": 8,
    "r9": 9,
    "r10": 10,
    "r11": 11,
예제 #17
0
class Image(models.Model):
    user = models.ForeignKey(User)
    gallery = models.ForeignKey(Gallery, related_name="images")
    uploaded = models.DateTimeField(auto_now_add=True)

    title = models.CharField(max_length=256, null=True, blank=True)

    tags = TaggableManager(blank=True)
    uuid = ShortUUIDField(db_index=True)

    original = models.ImageField(upload_to=set_image_name_on_upload, )

    _view_mapping = {
        "view.3d.180": "view_3d_180",
        "view.3d": "view_3d_360",
        "view.3d.360": "view_3d_360",
        "view.3d.sphere": "view_3d_360",
        "view.sphere": "view_3d_360",
        "view.pano": "view_2d_pano",
        "view.pan": "view_2d_pano",
        "view.panorama": "view_2d_pano",
        "view.panoramic": "view_2d_pano",
    }
    view_flags = BitField(flags=(
        ('view_3d_180', '180 Degrees 3D'),
        ('view_3d_360', '360 Degrees 3D'),
        ('view_2d_pano', 'Panoramic'),
    ),
                          null=True)
    # for multiple tags we use the first
    view_default = models.CharField(max_length=32, null=True, blank=True)

    def self_uuid(self):
        return self.uuid

    full_fixed = ImageSpecField(
        source="original",
        processors=[Transpose()],
        format="JPEG",
    )
    bigger = ImageSpecField(
        source="full_fixed",
        processors=[ResizeToCover(1440, 1080, upscale=False)],
        format="JPEG",
        options={
            'quality': 80,
            'prefix': "b"
        })
    default = ImageSpecField(
        source="full_fixed",
        processors=[ResizeToCover(720, 540, upscale=False)],
        format="JPEG",
        options={
            'quality': 80,
            'prefix': "d"
        })
    preview = ImageSpecField(source="full_fixed",
                             processors=[SmartResize(320, 240)],
                             format="JPEG",
                             options={
                                 'quality': 80,
                                 'prefix': "p"
                             })
    thumb = ImageSpecField(
        source="full_fixed",
        processors=[SmartResize(160, 120)],
        format="JPEG",
        options={
            'quality': 60,
            'prefix': "t"
        },
    )
    tiny_thumb = ImageSpecField(
        source="full_fixed",
        processors=[SmartResize(80, 60)],
        format="JPEG",
        options={
            'quality': 40,
            'prefix': "tt"
        },
    )

    AVAIL_SIZES = [
        "full_fixed", "bigger", "default", "preview", "thumb", "tiny_thumb"
    ]
    AVAIL_INTS = [0, 1, 2, 3, 4, 5]

    exif_data = models.ManyToManyField("EXIFEntry", blank=True)
    exif_timestamp = models.DateTimeField(null=True, blank=True)

    @property
    def uuid_as_b64(self):
        return base64.b64encode(self.uuid)

    def query_exif(self, only_when_empty=True, do_empty=False):
        image = IMG.open(self.original)

        if do_empty:
            self.exif_data.clear()

        if only_when_empty:
            do = not self.exif_data.exists()
        else:
            do = True

        if do:
            try:
                exif_raw = image._getexif()
            except:  # no usable exif
                return
            # I guess this deals with the compactness, so it needs to be decoded?
            if exif_raw:
                exif_decoded = {
                    TAGS.get(k): v
                    for k, v in exif_raw.iteritems()
                }

                out = []
                for key, value in exif_decoded.iteritems():
                    ek, ck = ExifKey.objects.get_or_create(key=key)
                    ev, cv = ExifValue.objects.get_or_create(value=value)

                    ee, ce = EXIFEntry.objects.get_or_create(key=ek, value=ev)
                    self.exif_data.add(ee)

                    if key == "DateTime":
                        value_stf = datetime.strptime(value,
                                                      "%Y:%m:%d %H:%M:%S")
                        self.exif_timestamp = value_stf
                        self.save()
            else:
                pass  # no exif

    def cached_full_fixed(self):
        generator = ImageCacheFile(self.full_fixed)
        return generator.generate()

    def cached_bigger(self):
        generator = ImageCacheFile(self.bigger)
        return generator.generate()

    def cached_default(self):
        generator = ImageCacheFile(self.default)
        return generator.generate()

    def cached_preview(self):
        generator = ImageCacheFile(self.preview)
        return generator.generate()

    def cached_thumb(self):
        generator = ImageCacheFile(self.thumb)
        return generator.generate()

    def cached_tiny_thumb(self):
        generator = ImageCacheFile(self.tiny_thumb)
        return generator.generate()

    def get_next_by_gallery_ordering(self):
        gallery = self.gallery
        gallery_sort = gallery.display_sort_string
        if gallery_sort == ['uploaded']:
            try:
                return self.get_next_by_uploaded(gallery=gallery)
            except:
                return None
        if gallery_sort == ['-uploaded']:
            try:
                return self.get_previous_by_uploaded(gallery=gallery)
            except:
                return None
        # todo add EXIF based sorting

    def get_prev_by_gallery_ordering(self):
        gallery = self.gallery
        gallery_sort = gallery.display_sort_string
        if gallery_sort == ['uploaded']:
            try:
                return self.get_previous_by_uploaded(gallery=gallery)
            except:
                return None
        if gallery_sort == ['-uploaded']:
            try:
                return self.get_next_by_uploaded(gallery=gallery)
            except:
                return None
예제 #18
0
파일: models.py 프로젝트: sonic2kk/website
class Game(models.Model):
    """Game model"""

    GAME_FLAGS = (
        ("fully_libre", "Fully libre"),
        ("open_engine", "Open engine only"),
        ("free", "Free"),
        ("freetoplay", "Free-to-play"),
        ("pwyw", "Pay what you want"),
        ("demo", "Has a demo"),
        ("protected", "Installer modification is restricted"),
    )

    # These model fields are editable by the user
    TRACKED_FIELDS = [
        "name", "year", "platforms", "genres", "publisher", "developer",
        "website", "description", "title_logo"
    ]

    ICON_PATH = os.path.join(settings.MEDIA_ROOT, "game-icons/128")
    BANNER_PATH = os.path.join(settings.MEDIA_ROOT, "game-banners/184")

    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, null=True, blank=True)
    year = models.IntegerField(null=True, blank=True)
    platforms = models.ManyToManyField(Platform, blank=True)
    genres = models.ManyToManyField(Genre, blank=True)
    publisher = models.ForeignKey(
        Company,
        related_name="published_game",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    developer = models.ForeignKey(
        Company,
        related_name="developed_game",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    website = models.CharField(max_length=200, blank=True)
    icon = models.ImageField(upload_to="uploads/icons", blank=True)
    title_logo = models.ImageField(upload_to="uploads/banners", blank=True)
    description = models.TextField(blank=True)
    is_public = models.BooleanField("Published", default=False)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    steamid = models.PositiveIntegerField(null=True, blank=True)
    gogslug = models.CharField(max_length=200, blank=True)
    gogid = models.PositiveIntegerField(null=True, blank=True)
    humblestoreid = models.CharField(max_length=200, blank=True)
    flags = BitField(flags=GAME_FLAGS)
    popularity = models.IntegerField(default=0)
    provider_games = models.ManyToManyField(ProviderGame,
                                            related_name="games",
                                            blank=True)

    # Indicates whether this data row is a changeset for another data row.
    # If so, this attribute is not NULL and the value is the ID of the
    # corresponding data row
    change_for = models.ForeignKey("self",
                                   null=True,
                                   blank=True,
                                   on_delete=models.CASCADE)

    objects = GameManager()

    class Meta:
        """Model configuration"""
        ordering = ["name"]
        permissions = (("can_publish_game", "Can publish game"), )

    @classmethod
    def valid_fields(cls):
        """Return a list of valid field names for the model"""
        return [f.name for f in cls._meta.fields]

    def __str__(self):
        if self.change_for is None:
            return self.name
        return "[Changes for] " + self.change_for.name

    @staticmethod
    def autocomplete_search_fields():
        """Autocomplete fields used in the Django admin"""
        return ("name__icontains", )

    @property
    def humbleid(self):
        """Humble Bundle ID, different from humblestoreid which is the store
        page ID for Humble Bundle
        """
        gog_slugs = self.provider_games.filter(
            provider__name="HUMBLE").values_list("slug", flat=True)
        if gog_slugs:
            return gog_slugs[0]
        return ""

    @property
    def user_count(self):
        """How many users have the game in their libraries"""
        return self.libraries.count()

    @property
    def website_url(self):
        """Returns self.website guaranteed to be a valid URI"""
        if not self.website:
            return None

        # Fall back to http if no protocol specified (cannot assume that https will work)
        has_protocol = "://" in self.website
        return "http://" + self.website if not has_protocol else self.website

    @property
    def website_url_hr(self):
        """Returns a human readable website URL (stripped protocols and trailing slashes)"""
        if not self.website:
            return None
        return self.website.split("https:", 1)[-1].split("http:",
                                                         1)[-1].strip("/")

    @property
    def banner_url(self):
        """Return URL for the game banner"""
        if self.title_logo:
            return reverse("get_banner", kwargs={"slug": self.slug})
        return ""

    @property
    def icon_url(self):
        """Return URL for the game icon"""
        if self.icon:
            return reverse("get_icon", kwargs={"slug": self.slug})
        return ""

    @property
    def flag_labels(self):
        """Return labels of active flags, suitable for display"""
        # pylint: disable=E1133; self.flags *is* iterable
        return [
            self.flags.get_label(flag[0]) for flag in self.flags if flag[1]
        ]

    def get_change_model(self):
        """Returns a dictionary which can be used as initial value in forms"""
        return {
            "name": self.name,
            "year": self.year,
            "platforms": [x.id for x in list(self.platforms.all())],
            "genres": [x.id for x in list(self.genres.all())],

            # The Select2 dropdowns want ids instead of complete models
            "publisher": self.publisher.id if self.publisher else None,
            "developer": self.developer.id if self.developer else None,
            "website": self.website,
            "description": self.description,
            "title_logo": self.title_logo,
        }

    def get_changes(self):
        """Returns a dictionary of the changes"""

        changes = []

        # From the considered fields, only those who differ will be returned
        for entry in self.TRACKED_FIELDS:
            old_value = getattr(self.change_for, entry)
            new_value = getattr(self, entry)

            # M2M relations to string
            if entry in ["platforms", "genres"]:
                old_value = ", ".join("[{0}]".format(str(x))
                                      for x in list(old_value.all()))
                new_value = ", ".join("[{0}]".format(str(x))
                                      for x in list(new_value.all()))

            if old_value != new_value:
                changes.append((entry, old_value, new_value))

        return changes

    def apply_changes(self, change_set):
        """Applies user-suggested changes to this model"""

        self.name = change_set.name
        self.year = change_set.year
        self.platforms.set(change_set.platforms.all())
        self.genres.set(change_set.genres.all())
        self.publisher = change_set.publisher
        self.developer = change_set.developer
        self.website = change_set.website
        self.description = change_set.description
        self.title_logo = change_set.title_logo

    def has_installer(self):
        """Return whether this game has an installer"""
        return self.installers.exists() or self.has_auto_installers()

    def has_auto_installers(self):
        """Return whether this game has auto-generated installers"""
        return self.platforms.filter(default_installer__isnull=False).exists()

    def get_absolute_url(self):
        """Return the absolute url for a game"""
        if self.change_for:
            slug = self.change_for.slug
        else:
            slug = self.slug
        return reverse("game_detail", kwargs={"slug": slug})

    def precache_media(self):
        """Prerenders thumbnails so we can host them as static files"""
        icon_path = os.path.join(settings.MEDIA_ROOT, self.icon.name)
        if self.icon.name and os.path.exists(icon_path):
            self.precache_icon()
        banner_path = os.path.join(settings.MEDIA_ROOT, self.title_logo.name)
        if self.title_logo.name and os.path.exists(banner_path):
            self.precache_banner()

    def precache_icon(self):
        """Render the icon and place it in the icons folder"""
        dest_file = os.path.join(self.ICON_PATH, "%s.png" % self.slug)
        if os.path.exists(dest_file):
            return
        thumbnail = get_thumbnail(self.icon,
                                  settings.ICON_SIZE,
                                  crop="center",
                                  format="PNG")
        shutil.copy(os.path.join(settings.MEDIA_ROOT, thumbnail.name),
                    dest_file)

    def precache_banner(self):
        """Render the icon and place it in the banners folder"""
        dest_file = os.path.join(self.BANNER_PATH, "%s.jpg" % self.slug)
        if os.path.exists(dest_file):
            return
        thumbnail = get_thumbnail(self.title_logo,
                                  settings.BANNER_SIZE,
                                  crop="center")
        shutil.copy(os.path.join(settings.MEDIA_ROOT, thumbnail.name),
                    dest_file)

    def set_logo_from_steam(self):
        """Fetch the banner from Steam and use it for the game"""
        if self.title_logo or not self.steamid:
            return
        self.title_logo = ContentFile(steam.get_capsule(self.steamid),
                                      "%s.jpg" % self.steamid)

    def set_logo_from_steam_api(self, img_url):
        """Sets the game banner from the Steam API URLs"""
        self.title_logo = ContentFile(steam.get_image(self.steamid, img_url),
                                      "%s.jpg" % self.steamid)

    def set_icon_from_steam_api(self, img_url):
        """Sets the game icon from the Steam API URLs"""
        self.icon = ContentFile(steam.get_image(self.steamid, img_url),
                                "%s.jpg" % self.steamid)

    def set_logo_from_gog(self, gog_game):
        """Sets the game logo from the data retrieved from GOG"""
        if self.title_logo or not self.gogid:
            return
        self.title_logo = ContentFile(gog.get_logo(gog_game),
                                      "gog-%s.jpg" % self.gogid)

    def steam_support(self):
        """ Return the platform supported by Steam """
        if not self.steamid:
            return False
        platforms = [p.slug for p in self.platforms.all()]
        if "linux" in platforms:
            return "linux"
        if "windows" in platforms:
            return "windows"
        return True

    def get_default_installers(self):
        """Return all auto-installers for this game's platforms"""
        auto_installers = []

        for platform in self.platforms.all():
            if platform.default_installer:
                installer = platform.default_installer
                installer["name"] = self.name
                installer["game_slug"] = self.slug
                installer["version"] = platform.name
                installer["slug"] = "-".join(
                    (self.slug[:30], platform.slug[:20]))
                installer["platform"] = platform.slug
                installer["description"] = ""
                installer["published"] = True
                installer["auto"] = True
                auto_installers.append(installer)
        return auto_installers

    def check_for_submission(self):
        """What? This saves submissions on save? Why?
        This is fully wrong. The name itself is a huge red flag since nothing
        is checked and this method has side effects.
        """
        # Skip freshly created and unpublished objects
        if not self.pk or not self.is_public:
            return

        # Skip objects that were already published
        original = Game.objects.get(pk=self.pk)
        if original.is_public:
            return

        try:
            submission = GameSubmission.objects.get(game=self,
                                                    accepted_at__isnull=True)
        except GameSubmission.DoesNotExist:
            pass
        else:
            submission.accept()

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        # Only create slug etc. if this is a game submission, no change submission
        if not self.change_for:
            if not self.slug:
                self.slug = slugify(self.name)[:50]
            if not self.slug:
                raise ValueError("Can't generate a slug for name %s" %
                                 self.name)
            self.set_logo_from_steam()
            self.check_for_submission()
        super(Game, self).save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields,
        )
        # Not ideal to have this here since this can generate disk IO activity
        # Not a problem though, we want to discourage mass updates for games
        # since that would DDOS the site.
        self.precache_media()
예제 #19
0
class Game(models.Model):
    """Game model"""
    GAME_FLAGS = (
        ('fully_libre', 'Fully libre'),
        ('open_engine', 'Open engine only'),
        ('free', 'Free'),
        ('freetoplay', 'Free-to-play'),
        ('pwyw', 'Pay what you want'),
        ('demo', 'Has a demo'),
        ('protected', 'Installer modification is restricted'),
    )
    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, null=True, blank=True)
    year = models.IntegerField(null=True, blank=True)
    platforms = models.ManyToManyField(Platform)
    genres = models.ManyToManyField(Genre)
    publisher = models.ForeignKey(Company,
                                  related_name='published_game',
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL)
    developer = models.ForeignKey(Company,
                                  related_name='developed_game',
                                  null=True,
                                  blank=True,
                                  on_delete=models.SET_NULL)
    website = models.CharField(max_length=200, blank=True)
    icon = models.ImageField(upload_to='games/icons', blank=True)
    title_logo = models.ImageField(upload_to='games/banners', blank=True)
    description = models.TextField(blank=True)
    is_public = models.BooleanField("Published", default=False)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    steamid = models.PositiveIntegerField(null=True, blank=True)
    gogslug = models.CharField(max_length=200, blank=True)
    gogid = models.PositiveIntegerField(null=True, unique=True)
    humblestoreid = models.CharField(max_length=200, blank=True)
    flags = BitField(flags=GAME_FLAGS)

    # Indicates whether this data row is a changeset for another data row.
    # If so, this attribute is not NULL and the value is the ID of the
    # corresponding data row
    change_for = models.ForeignKey('self',
                                   null=True,
                                   blank=True,
                                   on_delete=models.CASCADE)

    objects = GameManager()

    # pylint: disable=W0232, R0903
    class Meta(object):
        ordering = ['name']
        permissions = (('can_publish_game', "Can publish game"), )

    def __str__(self):
        if self.change_for is None:
            return self.name
        return '[Changes for] ' + self.change_for.name

    @staticmethod
    def autocomplete_search_fields():
        return ("name__icontains", )

    @property
    def website_url(self):
        """Returns self.website guaranteed to be a valid URI"""

        if not self.website:
            return None

        # Fall back to http if no protocol specified (cannot assume that https will work)
        has_protocol = '://' in self.website
        return 'http://' + self.website if not has_protocol else self.website

    @property
    def website_url_hr(self):
        """Returns a human readable website URL (stripped protocols and trailing slashes)"""

        if not self.website:
            return None

        return (self.website.split('https:', 1)[-1].split('http:',
                                                          1)[-1].strip('/'))

    @property
    def banner_url(self):
        if self.title_logo:
            return reverse('get_banner', kwargs={'slug': self.slug})

    @property
    def icon_url(self):
        if self.icon:
            return reverse('get_icon', kwargs={'slug': self.slug})

    @property
    def flag_labels(self):
        """Return labels of active flags, suitable for display"""
        # pylint: disable=E1133; self.flags *is* iterable
        return [
            self.flags.get_label(flag[0]) for flag in self.flags if flag[1]
        ]

    def get_change_model(self):
        """Returns a dictionary which can be used as initial value in forms"""
        return {
            'name': self.name,
            'year': self.year,
            'website': self.website,
            'description': self.description,
            'platforms': [x.id for x in list(self.platforms.all())],
            'genres': [x.id for x in list(self.genres.all())]
        }

    def get_changes(self):
        """Returns a dictionary of the changes"""

        changes = []
        considered_entries = [
            'name', 'year', 'platforms', 'genres', 'website', 'description'
        ]

        # From the considered fields, only those who differ will be returned
        for entry in considered_entries:
            old_value = getattr(self.change_for, entry)
            new_value = getattr(self, entry)

            # M2M relations to string
            if entry in ['platforms', 'genres']:
                old_value = ', '.join('[{0}]'.format(str(x))
                                      for x in list(old_value.all()))
                new_value = ', '.join('[{0}]'.format(str(x))
                                      for x in list(new_value.all()))

            if old_value != new_value:
                changes.append((entry, old_value, new_value))

        return changes

    def apply_changes(self, change_set):
        """Applies user-suggested changes to this model"""

        self.name = change_set.name
        self.year = change_set.year
        self.platforms.set(change_set.platforms.all())
        self.genres.set(change_set.genres.all())
        self.website = change_set.website
        self.description = change_set.description

    def has_installer(self):
        return self.installers.exists() or self.has_auto_installers()

    def has_auto_installers(self):
        return self.platforms.filter(default_installer__isnull=False).exists()

    def get_absolute_url(self):
        """Return the absolute url for a game"""
        if self.change_for:
            slug = self.change_for.slug
        else:
            slug = self.slug
        return reverse("game_detail", kwargs={'slug': slug})

    def download_steam_capsule(self):
        if self.title_logo or not self.steamid:
            return
        else:
            self.title_logo = ContentFile(steam.get_capsule(self.steamid),
                                          "%d.jpg" % self.steamid)

    def get_steam_logo(self, img_url):
        self.title_logo = ContentFile(steam.get_image(self.steamid, img_url),
                                      "%d.jpg" % self.steamid)

    def get_steam_icon(self, img_url):
        self.icon = ContentFile(steam.get_image(self.steamid, img_url),
                                "%d.jpg" % self.steamid)

    def steam_support(self):
        """ Return the platform supported by Steam """
        if not self.steamid:
            return False
        platforms = [p.slug for p in self.platforms.all()]
        if 'linux' in platforms:
            return 'linux'
        elif 'windows' in platforms:
            return 'windows'
        return True

    def get_default_installers(self):
        installers = []
        for platform in self.platforms.all():
            if platform.default_installer:
                installer = platform.default_installer
                installer['name'] = self.name
                installer['game_slug'] = self.slug
                installer['version'] = platform.name
                installer['slug'] = "-".join(
                    (self.slug[:30], platform.slug[:20]))
                installer['platform'] = platform.slug
                installer['description'] = ""
                installer['published'] = True
                installer['auto'] = True
                installers.append(installer)
        return installers

    def check_for_submission(self):
        # Skip freshly created and unpublished objects
        if not self.pk or not self.is_public:
            return

        # Skip objects that were already published
        original = Game.objects.get(pk=self.pk)
        if original.is_public:
            return

        try:
            submission = GameSubmission.objects.get(game=self,
                                                    accepted_at__isnull=True)
        except GameSubmission.DoesNotExist:
            pass
        else:
            submission.accept()

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        # Only create slug etc. if this is a game submission, no change submission
        if not self.change_for:
            if not self.slug:
                self.slug = slugify(self.name)[:50]
            if not self.slug:
                raise ValueError("Can't generate a slug for name %s" %
                                 self.name)
            self.download_steam_capsule()
            self.check_for_submission()
        return super(Game, self).save(force_insert=force_insert,
                                      force_update=force_update,
                                      using=using,
                                      update_fields=update_fields)
예제 #20
0
class ApiKey(Model):
    __core__ = True

    organization = FlexibleForeignKey("sentry.Organization", related_name="key_set")
    label = models.CharField(max_length=64, blank=True, default="Default")
    key = models.CharField(max_length=32, unique=True)
    scopes = BitField(
        flags=(
            ("project:read", "project:read"),
            ("project:write", "project:write"),
            ("project:admin", "project:admin"),
            ("project:releases", "project:releases"),
            ("team:read", "team:read"),
            ("team:write", "team:write"),
            ("team:admin", "team:admin"),
            ("event:read", "event:read"),
            ("event:write", "event:write"),
            ("event:admin", "event:admin"),
            ("org:read", "org:read"),
            ("org:write", "org:write"),
            ("org:admin", "org:admin"),
            ("member:read", "member:read"),
            ("member:write", "member:write"),
            ("member:admin", "member:admin"),
        )
    )
    scope_list = ArrayField(of=models.TextField)
    status = BoundedPositiveIntegerField(
        default=0,
        choices=((ApiKeyStatus.ACTIVE, _("Active")), (ApiKeyStatus.INACTIVE, _("Inactive"))),
        db_index=True,
    )
    date_added = models.DateTimeField(default=timezone.now)
    allowed_origins = models.TextField(blank=True, null=True)

    objects = BaseManager(cache_fields=("key",))

    class Meta:
        app_label = "sentry"
        db_table = "sentry_apikey"

    __repr__ = sane_repr("organization_id", "key")

    def __str__(self):
        return str(self.key)

    @classmethod
    def generate_api_key(cls):
        return uuid4().hex

    @property
    def is_active(self):
        return self.status == ApiKeyStatus.ACTIVE

    def save(self, *args, **kwargs):
        if not self.key:
            self.key = ApiKey.generate_api_key()
        super().save(*args, **kwargs)

    def get_allowed_origins(self):
        if not self.allowed_origins:
            return []
        return filter(bool, self.allowed_origins.split("\n"))

    def get_audit_log_data(self):
        return {
            "label": self.label,
            "key": self.key,
            "scopes": self.get_scopes(),
            "status": self.status,
        }

    def get_scopes(self):
        if self.scope_list:
            return self.scope_list
        return [k for k, v in self.scopes.items() if v]

    def has_scope(self, scope):
        return scope in self.get_scopes()
예제 #21
0
class Organization(Model):
    """
    An organization represents a group of individuals which maintain ownership of projects.
    """

    __core__ = True

    name = models.CharField(max_length=64)
    slug = models.SlugField(unique=True)
    status = BoundedPositiveIntegerField(
        choices=OrganizationStatus.as_choices(),
        # south will generate a default value of `'<OrganizationStatus.ACTIVE: 0>'`
        # if `.value` is omitted
        default=OrganizationStatus.ACTIVE.value,
    )
    date_added = models.DateTimeField(default=timezone.now)
    members = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        through="sentry.OrganizationMember",
        related_name="org_memberships",
        through_fields=("organization", "user"),
    )
    default_role = models.CharField(
        choices=roles.get_choices(), max_length=32, default=roles.get_default().id
    )

    flags = BitField(
        flags=(
            (
                "allow_joinleave",
                "Allow members to join and leave teams without requiring approval.",
            ),
            (
                "enhanced_privacy",
                "Enable enhanced privacy controls to limit personally identifiable information (PII) as well as source code in things like notifications.",
            ),
            (
                "disable_shared_issues",
                "Disable sharing of limited details on issues to anonymous users.",
            ),
            (
                "early_adopter",
                "Enable early adopter status, gaining access to features prior to public release.",
            ),
            ("require_2fa", "Require and enforce two-factor authentication for all members."),
            (
                "disable_new_visibility_features",
                "Temporarily opt out of new visibility features and ui",
            ),
        ),
        default=1,
    )

    objects = OrganizationManager(cache_fields=("pk", "slug"))

    class Meta:
        app_label = "sentry"
        db_table = "sentry_organization"

    __repr__ = sane_repr("owner_id", "name", "slug")

    @classmethod
    def get_default(cls):
        """
        Return the organization used in single organization mode.
        """

        if settings.SENTRY_ORGANIZATION is not None:
            return cls.objects.get(id=settings.SENTRY_ORGANIZATION)

        return cls.objects.filter(status=OrganizationStatus.ACTIVE)[0]

    def __unicode__(self):
        return u"%s (%s)" % (self.name, self.slug)

    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 delete(self):
        if self.is_default:
            raise Exception("You cannot delete the the default organization.")
        return super(Organization, self).delete()

    @cached_property
    def is_default(self):
        if not settings.SENTRY_SINGLE_ORGANIZATION:
            return False

        return self == type(self).get_default()

    def has_access(self, user, access=None):
        queryset = self.member_set.filter(user=user)
        if access is not None:
            queryset = queryset.filter(type__lte=access)

        return queryset.exists()

    def get_audit_log_data(self):
        return {
            "id": self.id,
            "slug": self.slug,
            "name": self.name,
            "status": int(self.status),
            "flags": int(self.flags),
            "default_role": self.default_role,
        }

    def get_owners(self):
        from sentry.models import User

        return User.objects.filter(
            sentry_orgmember_set__role=roles.get_top_dog().id,
            sentry_orgmember_set__organization=self,
            is_active=True,
        )

    def get_default_owner(self):
        if not hasattr(self, "_default_owner"):
            self._default_owner = self.get_owners()[0]
        return self._default_owner

    def has_single_owner(self):
        from sentry.models import OrganizationMember

        count = OrganizationMember.objects.filter(
            organization=self, role=roles.get_top_dog().id, user__isnull=False, user__is_active=True
        )[:2].count()
        return count == 1

    def merge_to(from_org, to_org):
        from sentry.models import (
            ApiKey,
            AuditLogEntry,
            AuthProvider,
            Commit,
            OrganizationAvatar,
            OrganizationIntegration,
            OrganizationMember,
            OrganizationMemberTeam,
            Project,
            Release,
            ReleaseCommit,
            ReleaseEnvironment,
            ReleaseFile,
            ReleaseHeadCommit,
            Repository,
            Team,
            Environment,
        )

        for from_member in OrganizationMember.objects.filter(
            organization=from_org, user__isnull=False
        ):
            logger = logging.getLogger("sentry.merge")
            try:
                to_member = OrganizationMember.objects.get(
                    organization=to_org, user=from_member.user
                )
            except OrganizationMember.DoesNotExist:
                from_member.update(organization=to_org)
                to_member = from_member
            else:
                qs = OrganizationMemberTeam.objects.filter(
                    organizationmember=from_member, is_active=True
                ).select_related()
                for omt in qs:
                    OrganizationMemberTeam.objects.create_or_update(
                        organizationmember=to_member, team=omt.team, defaults={"is_active": True}
                    )
            logger.info(
                "user.migrate",
                extra={
                    "instance_id": from_member.id,
                    "new_member_id": to_member.id,
                    "from_organization_id": from_org.id,
                    "to_organization_id": to_org.id,
                },
            )

        for from_team in Team.objects.filter(organization=from_org):
            try:
                with transaction.atomic():
                    from_team.update(organization=to_org)
            except IntegrityError:
                slugify_instance(from_team, from_team.name, organization=to_org)
                from_team.update(organization=to_org, slug=from_team.slug)
            logger.info(
                "team.migrate",
                extra={
                    "instance_id": from_team.id,
                    "new_slug": from_team.slug,
                    "from_organization_id": from_org.id,
                    "to_organization_id": to_org.id,
                },
            )

        for from_project in Project.objects.filter(organization=from_org):
            try:
                with transaction.atomic():
                    from_project.update(organization=to_org)
            except IntegrityError:
                slugify_instance(
                    from_project,
                    from_project.name,
                    organization=to_org,
                    reserved=RESERVED_PROJECT_SLUGS,
                )
                from_project.update(organization=to_org, slug=from_project.slug)
            logger.info(
                "project.migrate",
                extra={
                    "instance_id": from_project.id,
                    "new_slug": from_project.slug,
                    "from_organization_id": from_org.id,
                    "to_organization_id": to_org.id,
                },
            )

        # TODO(jess): update this when adding unique constraint
        # on version, organization for releases
        for from_release in Release.objects.filter(organization=from_org):
            try:
                to_release = Release.objects.get(version=from_release.version, organization=to_org)
            except Release.DoesNotExist:
                Release.objects.filter(id=from_release.id).update(organization=to_org)
            else:
                Release.merge(to_release, [from_release])
            logger.info(
                "release.migrate",
                extra={
                    "instance_id": from_release.id,
                    "from_organization_id": from_org.id,
                    "to_organization_id": to_org.id,
                },
            )

        def do_update(queryset, params):
            model_name = queryset.model.__name__.lower()
            try:
                with transaction.atomic():
                    queryset.update(**params)
            except IntegrityError:
                for instance in queryset:
                    try:
                        with transaction.atomic():
                            instance.update(**params)
                    except IntegrityError:
                        logger.info(
                            "{}.migrate-skipped".format(model_name),
                            extra={
                                "from_organization_id": from_org.id,
                                "to_organization_id": to_org.id,
                            },
                        )
                    else:
                        logger.info(
                            "{}.migrate".format(model_name),
                            extra={
                                "instance_id": instance.id,
                                "from_organization_id": from_org.id,
                                "to_organization_id": to_org.id,
                            },
                        )
            else:
                logger.info(
                    "{}.migrate".format(model_name),
                    extra={"from_organization_id": from_org.id, "to_organization_id": to_org.id},
                )

        INST_MODEL_LIST = (
            AuthProvider,
            ApiKey,
            AuditLogEntry,
            OrganizationAvatar,
            OrganizationIntegration,
            ReleaseEnvironment,
            ReleaseFile,
        )

        ATTR_MODEL_LIST = (Commit, ReleaseCommit, ReleaseHeadCommit, Repository, Environment)

        for model in INST_MODEL_LIST:
            queryset = model.objects.filter(organization=from_org)
            do_update(queryset, {"organization": to_org})

        for model in ATTR_MODEL_LIST:
            queryset = model.objects.filter(organization_id=from_org.id)
            do_update(queryset, {"organization_id": to_org.id})

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.set_value(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.get_value(self, *args, **kwargs)

    def delete_option(self, *args, **kwargs):
        from sentry.models import OrganizationOption

        return OrganizationOption.objects.unset_value(self, *args, **kwargs)

    def send_delete_confirmation(self, audit_log_entry, countdown):
        from sentry import options
        from sentry.utils.email import MessageBuilder

        owners = self.get_owners()

        context = {
            "organization": self,
            "audit_log_entry": audit_log_entry,
            "eta": timezone.now() + timedelta(seconds=countdown),
            "url": absolute_uri(reverse("sentry-restore-organization", args=[self.slug])),
        }

        MessageBuilder(
            subject="%sOrganization Queued for Deletion" % (options.get("mail.subject-prefix"),),
            template="sentry/emails/org_delete_confirm.txt",
            html_template="sentry/emails/org_delete_confirm.html",
            type="org.confirm_delete",
            context=context,
        ).send_async([o.email for o in owners])

    def flag_has_changed(self, flag_name):
        "Returns ``True`` if ``flag`` has changed since initialization."
        return getattr(self.old_value("flags"), flag_name, None) != getattr(self.flags, flag_name)

    def handle_2fa_required(self, request):
        from sentry.models import ApiKey
        from sentry.tasks.auth import remove_2fa_non_compliant_members

        actor_id = request.user.id if request.user and request.user.is_authenticated() else None
        api_key_id = (
            request.auth.id
            if hasattr(request, "auth") and isinstance(request.auth, ApiKey)
            else None
        )
        ip_address = request.META["REMOTE_ADDR"]

        remove_2fa_non_compliant_members.delay(
            self.id, actor_id=actor_id, actor_key_id=api_key_id, ip_address=ip_address
        )

    def get_url_viewname(self):
        return "sentry-organization-issue-list"

    def get_url(self):
        return reverse(self.get_url_viewname(), args=[self.slug])
예제 #22
0
class BitFieldTestModel(models.Model):
    flags = BitField(
        flags=('FLAG_0', 'FLAG_1', 'FLAG_2', 'FLAG_3', ), default=3, db_column='another_name'
    )
예제 #23
0
class ApiKey(Model):
    organization = FlexibleForeignKey('sentry.Organization',
                                      related_name='key_set')
    label = models.CharField(max_length=64, blank=True, default='Default')
    key = models.CharField(max_length=32, unique=True)
    scopes = BitField(flags=(
        ('project:read', 'project:read'),
        ('project:write', 'project:write'),
        ('project:delete', 'project:delete'),
        ('team:read', 'team:read'),
        ('team:write', 'team:write'),
        ('team:delete', 'team:delete'),
        ('event:read', 'event:read'),
        ('event:write', 'event:write'),
        ('event:delete', 'event:delete'),
        ('org:read', 'org:read'),
        ('org:write', 'org:write'),
        ('org:delete', 'org:delete'),
    ))
    status = BoundedPositiveIntegerField(default=0,
                                         choices=(
                                             (ApiKeyStatus.ACTIVE,
                                              _('Active')),
                                             (ApiKeyStatus.INACTIVE,
                                              _('Inactive')),
                                         ),
                                         db_index=True)
    date_added = models.DateTimeField(default=timezone.now)
    allowed_origins = models.TextField(blank=True, null=True)

    objects = BaseManager(cache_fields=('key', ))

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_apikey'

    __repr__ = sane_repr('organization_id', 'key')

    def __unicode__(self):
        return six.text_type(self.key)

    @classmethod
    def generate_api_key(cls):
        return uuid4().hex

    @property
    def is_active(self):
        return self.status == ApiKeyStatus.ACTIVE

    def save(self, *args, **kwargs):
        if not self.key:
            self.key = ApiKey.generate_api_key()
        super(ApiKey, self).save(*args, **kwargs)

    def get_allowed_origins(self):
        return filter(bool, self.allowed_origins.split('\n'))

    def get_audit_log_data(self):
        return {
            'label': self.label,
            'key': self.key,
            'roles': int(self.roles),
            'status': self.status,
        }
예제 #24
0
class Notification(models.Model):
    """
    ENG: A task executed on receiving a signal.
    RUS: Диспетчер сигналов. Задача выполняется при получении сигнала.
    """
    MODES = {
        0: ('email', _('By Email')),
        1: ('push', _('By Push Notification')),
    }

    RECIPIENTS_EMPTY_CHOICES_VALUE = ('0', _("Nobody"))
    """
        "1":  "owner",
        "2": "moderate_person",
        "3": "responsible_person",
        "4": "private_person",
        "5": "auditory_persons",
        "6": "regional_persons"
    """
    RECIPIENTS_ROLES_CHOICES = (RECIPIENTS_EMPTY_CHOICES_VALUE, )

    SPLIT_CHARSET = ','

    name = models.CharField(max_length=255, verbose_name=_("Name"))
    transition = MultiSelectField(
        verbose_name=_('Transition'),
        max_length=400,
        dinamic_choices_model_attr='get_transition_choices',
        blank=True)

    notify_to_roles = MultiSelectField(
        verbose_name=_('Notify to roles'),
        max_length=255,
        default='0',
        dinamic_choices_model_attr='get_notification_recipients_roles_choices')

    copy_to = models.ManyToManyField('CustomerProxy',
                                     blank=True,
                                     limit_choices_to={'is_staff': True})
    template = models.ForeignKey(EmailTemplate,
                                 verbose_name=_("Template"),
                                 limit_choices_to=Q(language__isnull=True)
                                 | Q(language=''))

    mode = BitField(flags=MODES, verbose_name=_('Mode'), default=Bit(0).mask)
    active = models.BooleanField(verbose_name=_("Active"), default=True)

    class Meta:
        app_label = APP_LABEL
        verbose_name = _("Notification")
        verbose_name_plural = _("Notifications")
        ordering = ('transition', )

    def __str__(self):
        return self.name

    @staticmethod
    def get_senders_objects():
        """
        RUS: Получение списка моделей для которых можно отправлять уведомления
        :return: [ список моделей, ... ]
        """

        return NotificationMixin._notification_classes.values()

    @staticmethod
    def get_transition_name(object_model, source, target):
        """
        RUS: Получение форматированного наименования состояния

        :param object_model: модель объекта
        :param source: наименование начального состояния
        :param target: наименование конечного состояния
        :return: форматированную строку модель:начальное состояние: конечное состояние
        """

        return '{}:{}:{}'.format(object_model.__name__.lower(), source, target)

    @classmethod
    def get_notification_recipients_roles_choices(cls):
        """
        RUS: Получение типов получателей доступных для уведомления для всех моделей
        :return: ((id:  _("title")), ...)
        """
        choices = list(cls.RECIPIENTS_ROLES_CHOICES)
        senders_objects = cls.get_senders_objects()

        for sender in senders_objects:
            for obj in sender.get_notification_recipients_roles_choices():
                if obj not in choices:
                    choices.append(obj)

        return choices

    @classmethod
    def get_transition_choices(cls):
        """
        RUS: Получение доступных вариантов переходов состояний для всех моделей
        :return:
            [(ransition_choice_name, transition_choice_title),...]
        """
        choices = {}
        for clazz in cls.get_senders_objects():
            for transition in clazz.get_notification_transitions():
                transition_choice_name = cls.get_transition_name(
                    clazz, transition.source, transition.target)
                transition_choice_title = "{}: {} - {} ({} - {})".format(
                    clazz._meta.verbose_name,
                    clazz.get_transition_name(transition.source),
                    clazz.get_transition_name(transition.target),
                    transition.source, transition.target)
                choices[transition_choice_name] = transition_choice_title

        return sorted(choices.items(), key=lambda item: item[1])

    @classmethod
    def get_avalible_recipients_roles_for_notifications(cls):
        """
        RUS: Получение всех доступных ролей для состояниий
        :return: словарь {'модель:начальное состояние:конечное состояние': [ ид роли, ...]}
        """
        roles_dict = {}
        senders_objects = cls.get_senders_objects()
        empty_value = cls.RECIPIENTS_EMPTY_CHOICES_VALUE[0]
        for sender in senders_objects:
            sender_name = sender.__name__.lower()
            for key, value in sender.get_avalible_recipients_roles_for_notifications(
            ).items():
                if not empty_value in value:
                    value.append(empty_value)
                roles_dict["%s:%s" % (sender_name, key)] = value

        return roles_dict

    @classmethod
    def send_notification(cls, object, source, target, **kwargs):
        """
        RUS: Главная функция для подписки на сигналы
        Отправка уведомлений по подписанным событиям
        :param object: объект уведомления
        :param source: начальное состояние
        :param target: конечное состояние
        :param kwargs:

        """

        transition_name = cls.get_transition_name(type(object), source, target)

        for n in Notification.objects.filter(
                transition__contains=transition_name, active=True):
            if n.mode.email:
                n.notify_by_email(object, source, target)
            if n.mode.push:
                n.notify_by_push(object, source, target)

    def get_notify_recipients_roles(self):
        """
        RUS: Получение списка ролей персон для уведомления из модели
        :return: [Идентивикатор роли,...]
        """

        return [
            recipient_id for recipient_id in self.notify_to_roles
            if recipient_id != self.RECIPIENTS_ROLES_CHOICES[0][0]
        ]

    def notify_by_email(self, object, source, target, **kwargs):
        """
        RUS: Отправка уведомлениий по email
        :param object: Объект уведомления Entity
        :param source - имя начального состояния
        :param target - имя конечного состояния

        recipients - список сосотящий из (email, пользователя, класс сериализации пользователя)
        example: [([email protected], customer_object, CustomerSerializer), ...]
        """

        recipients = [
        ]  #  - list of tuples (recipient_email, recipient_object, recipient_serialaizer_cls)
        if self.copy_to:
            recipients.extend(
                object.get_email_notification_recipients(self.copy_to.all()))

        recipients_roles = self.get_notify_recipients_roles()
        if recipients_roles:
            recipients.extend(
                object.get_email_notification_recipients_by_roles(
                    recipients_roles))

        if recipients:
            self.notify(recipients, object, source, target, 'email')

    def notify_by_push(self, object, source, target, **kwargs):
        """
        RUS: Отправка push уведомлениий
        :param object: Объект уведомления Entity
        :param source - имя начального состояния
        :param target - имя конечного состояния

        recipients - список сосотящий из (id пользователя, пользователя, класс сериализации пользователя)
        example: [(key, customer_object, CustomerSerializer), ...]
        """

        if push is not None:
            recipients = [
            ]  # - list of tuples (recipient_key, recipient_object, recipient_serialaizer_cls)
            if self.copy_to:
                recipients.extend(
                    object.get_push_notification_recipients(
                        self.copy_to.all()))

            recipients_roles = self.get_notify_recipients_roles()
            if recipients_roles:
                recipients.extend(
                    object.get_push_notification_recipients_by_roles(
                        recipients_roles))

            if recipients:
                self.notify(recipients, object, source, target, 'push')

    def notify(self,
               recipients,
               object,
               source,
               target,
               mode='email',
               **kwargs):
        """
        RUS: Подготовка и отправка сообщений по списку получателей

        :param recipients: список сосотящий из [(email или push_id, пользователь, класс сериализации пользователя),...]
        :param object: объект уведомления
        :param source - имя начального состояния
        :param target - имя конечного состояния
        :param mode: 'email' или 'push'

        """

        # подготовка общего контекста
        stored_request = object.stored_request[0] if isinstance(
            object.stored_request, (tuple, list)) else object.stored_request

        emulated_request = EmulateHttpRequest(object.customer, stored_request)
        authenticators = [
            auth() for auth in api_settings.DEFAULT_AUTHENTICATION_CLASSES
        ]

        serialaizer_cls = object.get_serialaizer_class()
        entity_serializer = serialaizer_cls(
            object,
            context={
                'request': Request(emulated_request,
                                   authenticators=authenticators)
            })

        language = stored_request.get('language')
        translation.activate(language)

        try:
            template = self.template.translated_templates.get(
                language=language)
        except EmailTemplate.DoesNotExist:
            template = self.template
        attachments = {}
        for notiatt in self.notificationattachment_set.all():
            attachments[notiatt.attachment.
                        original_filename] = notiatt.attachment.file.file

        context = {
            'data': entity_serializer.data,
            'ABSOLUTE_BASE_URI':
            emulated_request.build_absolute_uri().rstrip('/'),
            'render_language': language,
            'transition': {
                'source': {
                    'name': source,
                    'title': object.get_transition_name(source)
                },
                'target': {
                    'name': target,
                    'title': object.get_transition_name(target)
                },
            }
        }

        # отправка уведомления пользователям
        if mode == 'email':
            for recipient in recipients:
                try:
                    email_validator(recipient[0])
                except ValidationError:
                    pass
                else:
                    # подготовка контекста получателя
                    recipient_serialaizer_cls = recipient[2]
                    context['recipient'] = recipient_serialaizer_cls(
                        recipient[1]).data

                    mail.send(recipient[0],
                              template=template,
                              context=context,
                              attachments=attachments,
                              render_on_delivery=True)

        elif mode == 'push' and push is not None:

            for recipient in recipients:
                # подготовка контекста получателя
                recipient_serialaizer_cls = recipient[2]
                context['recipient'] = recipient_serialaizer_cls(
                    recipient[1]).data

                push.send(recipient[0],
                          template=template,
                          context=context,
                          render_on_delivery=True)
예제 #25
0
파일: models.py 프로젝트: xhacker/coursys
class CareerEvent(models.Model):
    STATUS_CHOICES = (
        ('NA', 'Needs Approval'),
        ('A', 'Approved'),
        ('D', 'Deleted'),
    )

    person = models.ForeignKey(Person, related_name="career_events")
    unit = models.ForeignKey(Unit)

    slug = AutoSlugField(populate_from='slug_string',
                         unique_with=('person', ),
                         slugify=make_slug,
                         null=False,
                         editable=False)
    start_date = models.DateField(null=False, blank=False)
    end_date = models.DateField(null=True, blank=True)
    comments = models.TextField(blank=True)

    event_type = models.CharField(max_length=10, choices=EVENT_TYPE_CHOICES)
    config = JSONField(default={})

    flags = BitField(flags=EVENT_FLAGS, default=0)

    status = models.CharField(max_length=2,
                              choices=STATUS_CHOICES,
                              blank=False,
                              default='')
    import_key = models.TextField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = CareerEventManager()

    class Meta:
        ordering = (
            '-start_date',
            '-end_date',
            'event_type',
        )
        unique_together = (("person", "slug"), )

    def __unicode__(self):
        return u"%s from %s to %s" % (self.get_event_type_display(),
                                      self.start_date, self.end_date)

    def save(self, editor, call_from_handler=False, *args, **kwargs):
        # we're doing to so we can add an audit trail later.
        assert editor.__class__.__name__ == 'Person'
        assert call_from_handler, "must save through handler"
        return super(CareerEvent, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("faculty_event_view",
                       args=[self.person.userid, self.slug])

    def get_status_change_url(self):
        return reverse("faculty_change_event_status",
                       args=[self.person.userid, self.slug])

    def get_change_url(self):
        return reverse("faculty_change_event",
                       args=[self.person.userid, self.slug])

    @property
    def slug_string(self):
        return u'{} {}'.format(self.start_date.year,
                               self.get_event_type_display())

    def handler_type_name(self):
        return self.get_handler().NAME

    @classmethod
    @cached(6 * 3600)
    def current_ranks(cls, person):
        """
        Return a string representing the current rank(s) for this person
        """
        salaries = CareerEvent.objects.filter(
            person=person, event_type='SALARY').effective_now()
        if not salaries:
            return 'unknown'

        ranks = set(s.get_handler().get_rank_display() for s in salaries)
        return ', '.join(ranks)

    def get_event_type_display(self):
        "Override to display nicely"
        return EVENT_TYPES[self.event_type].NAME

    def get_handler(self):
        if not hasattr(self, 'handler_cache'):
            self.handler_cache = EVENT_TYPES[self.event_type](self)
        return self.handler_cache

    def get_duration_within_range(self, start, end):
        """
        Returns the number of days the event overlaps with a given date range
        """
        if (self.start_date < end
                and (self.end_date == None or self.end_date > start)):
            s = max(start, self.start_date)
            if self.end_date:
                e = min(end, self.end_date)
            else:
                e = end
            delta = e - s
            return delta.days
        return 0

    def filter_classes(self):
        """
        return the class="..." value for this event on the summary page (for filtering)
        """
        today = datetime.date.today()
        classes = []
        #if self.start_date <= today and (self.end_date == None or self.end_date >= today):
        if self.end_date == None or self.end_date >= today:
            classes.append('current')
        if self.flags.affects_teaching:
            classes.append('teach')
        if self.flags.affects_salary:
            classes.append('salary')

        return ' '.join(classes)

    def memo_info(self):
        """
        Context dictionary for building memo text
        """

        # basic personal stuff
        gender = self.person.gender()
        title = self.person.get_title()

        if gender == "M":
            hisher = "his"
            heshe = 'he'
        elif gender == "F":
            hisher = "her"
            heshe = 'she'
        else:
            hisher = "his/her"
            heshe = 'he/she'

        # grab event type specific config data
        handler = self.get_handler()
        config_data = copy.deepcopy(self.config)
        for key in config_data:
            try:
                config_data[key] = unicode(handler.get_display(key))
            except AttributeError:
                pass

        start = self.start_date.strftime('%B %d, %Y')
        end = self.end_date.strftime('%B %d, %Y') if self.end_date else '???'

        ls = {  # if changing, also update EVENT_TAGS above!
            # For security reasons, all values must be strings (to avoid presenting dangerous methods in templates)
            'title': title,
            'his_her': hisher,
            'His_Her': hisher.title(),
            'he_she': heshe,
            'He_She': heshe.title(),
            'first_name': self.person.first_name,
            'last_name': self.person.last_name,
            'start_date': start,
            'end_date': end,
            'current_rank': CareerEvent.current_ranks(self.person)
        }
        ls = dict(ls.items() + config_data.items())
        return ls
예제 #26
0
class CareerEvent(models.Model):
    STATUS_CHOICES = (
        ('NA', 'Needs Approval'),
        ('A', 'Approved'),
        ('D', 'Deleted'),
    )

    person = models.ForeignKey(Person,
                               related_name="career_events",
                               on_delete=models.PROTECT)
    unit = models.ForeignKey(Unit, on_delete=models.PROTECT)

    slug = AutoSlugField(populate_from='slug_string',
                         unique_with=('person', ),
                         slugify=make_slug,
                         null=False,
                         editable=False)
    start_date = models.DateField(null=False, blank=False)
    end_date = models.DateField(null=True, blank=True)
    comments = models.TextField(blank=True)

    event_type = models.CharField(max_length=10, choices=EVENT_TYPE_CHOICES)
    config = JSONField(default=dict)

    flags = BitField(flags=EVENT_FLAGS, default=0)

    status = models.CharField(max_length=2,
                              choices=STATUS_CHOICES,
                              blank=False,
                              default='')
    import_key = models.TextField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    objects = CareerQuerySet.as_manager()

    class Meta:
        ordering = (
            '-start_date',
            '-end_date',
            'event_type',
        )
        unique_together = (("person", "slug"), )

    def __str__(self):
        return "%s from %s to %s" % (self.get_event_type_display(),
                                     self.start_date, self.end_date)

    def save(self, editor, call_from_handler=False, *args, **kwargs):
        # we're doing to so we can add an audit trail later.
        assert editor.__class__.__name__ == 'Person'
        assert call_from_handler, "must save through handler"
        return super(CareerEvent, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse("faculty:view_event",
                       args=[self.person.userid, self.slug])

    def get_status_change_url(self):
        return reverse("faculty:change_event_status",
                       args=[self.person.userid, self.slug])

    def get_change_url(self):
        return reverse("faculty:change_event",
                       args=[self.person.userid, self.slug])

    @property
    def slug_string(self):
        return '{} {}'.format(self.start_date.year,
                              self.get_event_type_display())

    def handler_type_name(self):
        return self.get_handler().NAME

    @classmethod
    @cached(6 * 3600)
    def current_ranks(cls, person_id):
        """
        Return a string representing the current rank(s) for this person
        """
        salaries = CareerEvent.objects.filter(
            person__id=person_id, event_type='SALARY').effective_now()
        if not salaries:
            return 'unknown'

        ranks = set(s.get_handler().get_rank_display() for s in salaries)
        return ', '.join(ranks)

    @classmethod
    @cached(6 * 3600)
    def ranks_as_of_semester(cls, person_id, semester):
        """
        Return a string representing the rank(s) for this person as of the beginning of a given semester.
        """
        salaries = CareerEvent.objects.filter(
            person__id=person_id,
            event_type='SALARY').effective_date(semester.start)
        if not salaries:
            return 'unknown'

        ranks = set(s.get_handler().get_rank_display() for s in salaries)
        return ', '.join(ranks)

    @classmethod
    @cached(6 * 3600)
    def current_base_salary(cls, person):
        """
        Return a string representing the current base salary for this person.  If the person has more than
        one currently effective one, they get added together.
        """
        salaries = CareerEvent.objects.filter(
            person=person, event_type='SALARY').effective_now()
        if not salaries:
            return 'unknown'
        # One could theoretically have more than one active base salary (for example, if one is a member of more than
        # one school and gets a salary from both).  In that case, add them up.
        total = Decimal(0)
        for s in salaries:
            if 'base_salary' in s.config:
                total += Decimal(s.config.get('base_salary'))
        # format it nicely with commas, see http://stackoverflow.com/a/10742904/185884
        return str('$' + "{:,}".format(total))

    @classmethod
    @cached(6 * 3600)
    def current_market_diff(cls, person):
        """
        Return a string representing the current market differential for this person.
        """
        diffs = CareerEvent.objects.filter(
            person=person, event_type='STIPEND').effective_now()
        if not diffs:
            return 'unknown'
        #  Retention, market differentials, research chair stipends, and other adjustments are stored in the same
        #  stipend type event. We only care about market differentials.
        marketdiffs = [
            d for d in diffs
            if 'source' in d.config and d.config.get('source') == 'MARKETDIFF'
        ]
        if marketdiffs:
            # Just like base salaries, we could theoretically have more than one active at a given time, we think.
            # Let's add them up in that case
            total = Decimal(0)
            for diff in marketdiffs:
                if 'amount' in diff.config:
                    total += Decimal(diff.config.get('amount'))
            return str('$' + "{:,}".format(total))
        else:
            return 'unknown'

    def get_event_type_display_(self):
        "Override to display nicely"
        return EVENT_TYPES[self.event_type].NAME

    def get_handler(self):
        if not hasattr(self, 'handler_cache'):
            self.handler_cache = EVENT_TYPES[self.event_type](self)
        return self.handler_cache

    def get_duration_within_range(self, start, end):
        """
        Returns the number of days the event overlaps with a given date range
        """
        if (self.start_date < end
                and (self.end_date == None or self.end_date > start)):
            s = max(start, self.start_date)
            if self.end_date:
                e = min(end, self.end_date)
            else:
                e = end
            delta = e - s
            return delta.days
        return 0

    def filter_classes(self):
        """
        return the class="..." value for this event on the summary page (for filtering)
        """
        today = datetime.date.today()
        classes = []
        #if self.start_date <= today and (self.end_date == None or self.end_date >= today):
        if self.end_date == None or self.end_date >= today:
            classes.append('current')
        if self.flags.affects_teaching:
            classes.append('teach')
        if self.flags.affects_salary:
            classes.append('salary')

        return ' '.join(classes)

    def memo_info(self):
        """
        Context dictionary for building memo text
        """

        # basic personal stuff
        gender = self.person.gender()
        title = self.person.get_title()

        if gender == "M":
            hisher = "his"
            heshe = 'he'
            himher = 'him'
        elif gender == "F":
            hisher = "her"
            heshe = 'she'
            himher = 'her'
        else:
            hisher = "his/her"
            heshe = 'he/she'
            himher = 'him/her'

        # grab event type specific config data
        handler = self.get_handler()
        config_data = copy.deepcopy(self.config)
        for key in config_data:
            try:
                config_data[key] = str(handler.get_display(key))
            except AttributeError:
                pass

        start = self.start_date.strftime('%B %d, %Y')
        end = self.end_date.strftime('%B %d, %Y') if self.end_date else '???'

        ls = { # if changing, also update EVENT_TAGS above!
               # For security reasons, all values must be strings (to avoid presenting dangerous methods in templates)
                'title' : title,
                'his_her' : hisher,
                'His_Her' : hisher.title(),
                'he_she' : heshe,
                'He_She' : heshe.title(),
                'him_her' : himher,
                'Him_Her' : himher.title(),
                'first_name': self.person.first_name,
                'last_name': self.person.last_name,
                'start_date': start,
                'end_date': end,
                'current_rank': CareerEvent.current_ranks(self.person.id),
                'unit': self.unit.name,
                'current_base_salary': CareerEvent.current_base_salary(self.person),
                'current_market_diff': CareerEvent.current_market_diff(self.person),
              }
        ls = dict(list(ls.items()) + list(config_data.items()))
        return ls

    def has_memos(self):
        return Memo.objects.filter(career_event=self, hidden=False).count() > 0

    def has_attachments(self):
        return DocumentAttachment.objects.filter(career_event=self,
                                                 hidden=False).count() > 0
예제 #27
0
class Game(models.Model):
    """Game model"""

    GAME_FLAGS = (
        ("fully_libre", "Fully libre"),
        ("open_engine", "Open engine only"),
        ("free", "Free"),
        ("freetoplay", "Free-to-play"),
        ("pwyw", "Pay what you want"),
        ("demo", "Has a demo"),
        ("protected", "Installer modification is restricted"),
    )

    # These model fields are editable by the user
    TRACKED_FIELDS = [
        "name", "year", "platforms", "genres", "publisher", "developer",
        "website", "description", "title_logo"
    ]

    name = models.CharField(max_length=200)
    slug = models.SlugField(unique=True, null=True, blank=True)
    year = models.IntegerField(null=True, blank=True)
    platforms = models.ManyToManyField(Platform)
    genres = models.ManyToManyField(Genre)
    publisher = models.ForeignKey(
        Company,
        related_name="published_game",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    developer = models.ForeignKey(
        Company,
        related_name="developed_game",
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
    )
    website = models.CharField(max_length=200, blank=True)
    icon = models.ImageField(upload_to="games/icons", blank=True)
    title_logo = models.ImageField(upload_to="games/banners", blank=True)
    description = models.TextField(blank=True)
    is_public = models.BooleanField("Published", default=False)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    steamid = models.PositiveIntegerField(null=True, blank=True)
    gogslug = models.CharField(max_length=200, blank=True)
    gogid = models.PositiveIntegerField(null=True, blank=True)
    humblestoreid = models.CharField(max_length=200, blank=True)
    flags = BitField(flags=GAME_FLAGS)
    popularity = models.IntegerField(default=0)

    # Indicates whether this data row is a changeset for another data row.
    # If so, this attribute is not NULL and the value is the ID of the
    # corresponding data row
    change_for = models.ForeignKey("self",
                                   null=True,
                                   blank=True,
                                   on_delete=models.CASCADE)

    objects = GameManager()

    # pylint: disable=W0232, R0903
    class Meta(object):
        ordering = ["name"]
        permissions = (("can_publish_game", "Can publish game"), )

    def __str__(self):
        if self.change_for is None:
            return self.name
        return "[Changes for] " + self.change_for.name

    @staticmethod
    def autocomplete_search_fields():
        return ("name__icontains", )

    @property
    def website_url(self):
        """Returns self.website guaranteed to be a valid URI"""
        if not self.website:
            return None

        # Fall back to http if no protocol specified (cannot assume that https will work)
        has_protocol = "://" in self.website
        return "http://" + self.website if not has_protocol else self.website

    @property
    def website_url_hr(self):
        """Returns a human readable website URL (stripped protocols and trailing slashes)"""
        if not self.website:
            return None
        return self.website.split("https:", 1)[-1].split("http:",
                                                         1)[-1].strip("/")

    @property
    def banner_url(self):
        if self.title_logo:
            return reverse("get_banner", kwargs={"slug": self.slug})

    @property
    def icon_url(self):
        if self.icon:
            return reverse("get_icon", kwargs={"slug": self.slug})

    @property
    def flag_labels(self):
        """Return labels of active flags, suitable for display"""
        # pylint: disable=E1133; self.flags *is* iterable
        return [
            self.flags.get_label(flag[0]) for flag in self.flags if flag[1]
        ]

    def get_change_model(self):
        """Returns a dictionary which can be used as initial value in forms"""
        return {
            "name": self.name,
            "year": self.year,
            "platforms": [x.id for x in list(self.platforms.all())],
            "genres": [x.id for x in list(self.genres.all())],

            # The Select2 dropdowns want ids instead of complete models
            "publisher": self.publisher.id if self.publisher else None,
            "developer": self.developer.id if self.developer else None,
            "website": self.website,
            "description": self.description,
            "title_logo": self.title_logo,
        }

    def get_changes(self):
        """Returns a dictionary of the changes"""

        changes = []

        # From the considered fields, only those who differ will be returned
        for entry in self.TRACKED_FIELDS:
            old_value = getattr(self.change_for, entry)
            new_value = getattr(self, entry)

            # M2M relations to string
            if entry in ["platforms", "genres"]:
                old_value = ", ".join("[{0}]".format(str(x))
                                      for x in list(old_value.all()))
                new_value = ", ".join("[{0}]".format(str(x))
                                      for x in list(new_value.all()))

            if old_value != new_value:
                changes.append((entry, old_value, new_value))

        return changes

    def apply_changes(self, change_set):
        """Applies user-suggested changes to this model"""

        self.name = change_set.name
        self.year = change_set.year
        self.platforms.set(change_set.platforms.all())
        self.genres.set(change_set.genres.all())
        self.publisher = change_set.publisher
        self.developer = change_set.developer
        self.website = change_set.website
        self.description = change_set.description
        self.title_logo = change_set.title_logo

    def has_installer(self):
        return self.installers.exists() or self.has_auto_installers()

    def has_auto_installers(self):
        return self.platforms.filter(default_installer__isnull=False).exists()

    def get_absolute_url(self):
        """Return the absolute url for a game"""
        if self.change_for:
            slug = self.change_for.slug
        else:
            slug = self.slug
        return reverse("game_detail", kwargs={"slug": slug})

    def set_logo_from_steam(self):
        if self.title_logo or not self.steamid:
            return
        self.title_logo = ContentFile(steam.get_capsule(self.steamid),
                                      "%s.jpg" % self.steamid)

    def set_logo_from_steam_api(self, img_url):
        """Sets the game banner from the Steam API URLs"""
        self.title_logo = ContentFile(steam.get_image(self.steamid, img_url),
                                      "%s.jpg" % self.steamid)

    def set_icon_from_steam_api(self, img_url):
        """Sets the game icon from the Steam API URLs"""
        self.icon = ContentFile(steam.get_image(self.steamid, img_url),
                                "%s.jpg" % self.steamid)

    def set_logo_from_gog(self, gog_game):
        """Sets the game logo from the data retrieved from GOG"""
        if self.title_logo or not self.gogid:
            return
        self.title_logo = ContentFile(gog.get_logo(gog_game),
                                      "gog-%s.jpg" % self.gogid)

    def steam_support(self):
        """ Return the platform supported by Steam """
        if not self.steamid:
            return False
        platforms = [p.slug for p in self.platforms.all()]
        if "linux" in platforms:
            return "linux"
        if "windows" in platforms:
            return "windows"
        return True

    def get_default_installers(self):
        auto_installers = []

        for platform in self.platforms.all():
            if platform.default_installer:
                installer = platform.default_installer
                installer["name"] = self.name
                installer["game_slug"] = self.slug
                installer["version"] = platform.name
                installer["slug"] = "-".join(
                    (self.slug[:30], platform.slug[:20]))
                installer["platform"] = platform.slug
                installer["description"] = ""
                installer["published"] = True
                installer["auto"] = True
                auto_installers.append(installer)
        return auto_installers

    def check_for_submission(self):
        # Skip freshly created and unpublished objects
        if not self.pk or not self.is_public:
            return

        # Skip objects that were already published
        original = Game.objects.get(pk=self.pk)
        if original.is_public:
            return

        try:
            submission = GameSubmission.objects.get(game=self,
                                                    accepted_at__isnull=True)
        except GameSubmission.DoesNotExist:
            pass
        else:
            submission.accept()

    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None):
        # Only create slug etc. if this is a game submission, no change submission
        if not self.change_for:
            if not self.slug:
                self.slug = slugify(self.name)[:50]
            if not self.slug:
                raise ValueError("Can't generate a slug for name %s" %
                                 self.name)
            self.set_logo_from_steam()
            self.check_for_submission()
        return super(Game, self).save(
            force_insert=force_insert,
            force_update=force_update,
            using=using,
            update_fields=update_fields,
        )
예제 #28
0
class AuthProvider(Model):
    __include_in_export__ = True

    organization = FlexibleForeignKey("sentry.Organization", unique=True)
    provider = models.CharField(max_length=128)
    config = EncryptedJsonField()

    date_added = models.DateTimeField(default=timezone.now)
    sync_time = BoundedPositiveIntegerField(null=True)
    last_sync = models.DateTimeField(null=True)

    default_role = BoundedPositiveIntegerField(default=50)
    default_global_access = models.BooleanField(default=True)
    # TODO(dcramer): ManyToMany has the same issue as ForeignKey and we need
    # to either write our own which works w/ BigAuto or switch this to use
    # through.
    default_teams = models.ManyToManyField("sentry.Team", blank=True)

    flags = BitField(
        flags=(
            ("allow_unlinked",
             "Grant access to members who have not linked SSO accounts."),
            ("scim_enabled",
             "Enable SCIM for member and team provisioning and syncing"),
        ),
        default=0,
    )

    class Meta:
        app_label = "sentry"
        db_table = "sentry_authprovider"

    __repr__ = sane_repr("organization_id", "provider")

    def __str__(self):
        return self.provider

    def get_provider(self):
        from sentry.auth import manager

        return manager.get(self.provider, **self.config)

    @property
    def provider_name(self) -> str:
        return self.get_provider().name

    def get_scim_token(self):
        from sentry.models import SentryAppInstallationToken

        if self.flags.scim_enabled:
            return SentryAppInstallationToken.objects.get_token(
                self.organization, f"{self.provider}_scim")
        else:
            logger.warning(
                "SCIM disabled but tried to access token",
                extra={"organization_id": self.organization.id},
            )
            return None

    def get_scim_url(self):
        if self.flags.scim_enabled:
            url_prefix = options.get("system.url-prefix")
            # the SCIM protocol doesn't use trailing slashes in URLs
            return f"{url_prefix}/api/0/organizations/{self.organization.slug}/scim/v2"

        else:
            return None

    def enable_scim(self, user):
        from sentry.mediators.sentry_apps import InternalCreator
        from sentry.models import SentryAppInstallation, SentryAppInstallationForProvider

        if (not self.get_provider().can_use_scim(self.organization, user)
                or self.flags.scim_enabled is True):
            logger.warning(
                "SCIM already enabled",
                extra={"organization_id": self.organization.id},
            )
            return

        # check if we have a scim app already

        if SentryAppInstallationForProvider.objects.filter(
                organization=self.organization, provider="okta_scim").exists():
            logger.warning(
                "SCIM installation already exists",
                extra={"organization_id": self.organization.id},
            )
            return

        data = {
            "name":
            "SCIM Internal Integration",
            "author":
            "Auto-generated by Sentry",
            "organization":
            self.organization,
            "overview":
            SCIM_INTERNAL_INTEGRATION_OVERVIEW,
            "user":
            user,
            "scopes": [
                "member:read",
                "member:write",
                "member:admin",
                "team:write",
                "team:admin",
            ],
        }
        # create the internal integration and link it to the join table
        sentry_app = InternalCreator.run(**data)
        sentry_app_installation = SentryAppInstallation.objects.get(
            sentry_app=sentry_app)
        SentryAppInstallationForProvider.objects.create(
            sentry_app_installation=sentry_app_installation,
            organization=self.organization,
            provider=f"{self.provider}_scim",
        )
        self.flags.scim_enabled = True

    def disable_scim(self, user):
        from sentry.mediators.sentry_apps import Destroyer
        from sentry.models import SentryAppInstallationForProvider

        if self.flags.scim_enabled:
            install = SentryAppInstallationForProvider.objects.get(
                organization=self.organization,
                provider=f"{self.provider}_scim")
            Destroyer.run(
                sentry_app=install.sentry_app_installation.sentry_app,
                user=user)
            self.flags.scim_enabled = False

    def get_audit_log_data(self):
        return {"provider": self.provider, "config": self.config}
예제 #29
0
class User(BaseModel, AbstractBaseUser):
    __core__ = True

    id = BoundedAutoField(primary_key=True)
    username = models.CharField(_("username"), max_length=128, unique=True)
    # this column is called first_name for legacy reasons, but it is the entire
    # display name
    name = models.CharField(_("name"),
                            max_length=200,
                            blank=True,
                            db_column="first_name")
    email = models.EmailField(_("email address"), blank=True, max_length=75)
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_(
            "Designates whether the user can log into this admin site."),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_("Designates whether this user should be treated as "
                    "active. Unselect this instead of deleting accounts."),
    )
    is_superuser = models.BooleanField(
        _("superuser status"),
        default=False,
        help_text=
        _("Designates that this user has all permissions without explicitly assigning them."
          ),
    )
    is_managed = models.BooleanField(
        _("managed"),
        default=False,
        help_text=_("Designates whether this user should be treated as "
                    "managed. Select this to disallow the user from "
                    "modifying their account (username, password, etc)."),
    )
    is_sentry_app = models.NullBooleanField(
        _("is sentry app"),
        null=True,
        default=None,
        help_text=_(
            "Designates whether this user is the entity used for Permissions"
            "on behalf of a Sentry App. Cannot login or use Sentry like a"
            "normal User would."),
    )
    is_password_expired = models.BooleanField(
        _("password expired"),
        default=False,
        help_text=_("If set to true then the user needs to change the "
                    "password on next sign in."),
    )
    last_password_change = models.DateTimeField(
        _("date of last password change"),
        null=True,
        help_text=_("The date the password was changed last."),
    )

    flags = BitField(
        flags=(("newsletter_consent_prompt",
                "Do we need to ask this user for newsletter consent?"), ),
        default=0,
        null=True,
    )

    session_nonce = models.CharField(max_length=12, null=True)
    actor = FlexibleForeignKey("sentry.Actor",
                               db_index=True,
                               unique=True,
                               null=True,
                               on_delete=models.PROTECT)
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
    last_active = models.DateTimeField(_("last active"),
                                       default=timezone.now,
                                       null=True)

    objects = UserManager(cache_fields=["pk"])

    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email"]

    class Meta:
        app_label = "sentry"
        db_table = "auth_user"
        verbose_name = _("user")
        verbose_name_plural = _("users")

    __repr__ = sane_repr("id")

    def delete(self):
        if self.username == "sentry":
            raise Exception(
                'You cannot delete the "sentry" user as it is required by Sentry.'
            )
        avatar = self.avatar.first()
        if avatar:
            avatar.delete()
        return super().delete()

    def save(self, *args, **kwargs):
        if not self.username:
            self.username = self.email
        return super().save(*args, **kwargs)

    def has_perm(self, perm_name):
        warnings.warn("User.has_perm is deprecated", DeprecationWarning)
        return self.is_superuser

    def has_module_perms(self, app_label):
        warnings.warn("User.has_module_perms is deprecated",
                      DeprecationWarning)
        return self.is_superuser

    def get_unverified_emails(self):
        return self.emails.filter(is_verified=False)

    def get_verified_emails(self):
        return self.emails.filter(is_verified=True)

    def has_unverified_emails(self):
        return self.get_unverified_emails().exists()

    def get_label(self):
        return self.email or self.username or self.id

    def get_display_name(self):
        return self.name or self.email or self.username

    def get_full_name(self):
        return self.name

    def get_short_name(self):
        return self.username

    def get_salutation_name(self):
        name = self.name or self.username.split("@", 1)[0].split(".", 1)[0]
        first_name = name.split(" ", 1)[0]
        return first_name.capitalize()

    def get_avatar_type(self):
        avatar = self.avatar.first()
        if avatar:
            return avatar.get_avatar_type_display()
        return "letter_avatar"

    def send_confirm_email_singular(self, email, is_new_user=False):
        from sentry import options
        from sentry.utils.email import MessageBuilder

        if not email.hash_is_valid():
            email.set_hash()
            email.save()

        context = {
            "user":
            self,
            "url":
            absolute_uri(
                reverse("sentry-account-confirm-email",
                        args=[self.id, email.validation_hash])),
            "confirm_email":
            email.email,
            "is_new_user":
            is_new_user,
        }
        msg = MessageBuilder(
            subject="{}Confirm Email".format(
                options.get("mail.subject-prefix")),
            template="sentry/emails/confirm_email.txt",
            html_template="sentry/emails/confirm_email.html",
            type="user.confirm_email",
            context=context,
        )
        msg.send_async([email.email])

    def send_confirm_emails(self, is_new_user=False):
        email_list = self.get_unverified_emails()
        for email in email_list:
            self.send_confirm_email_singular(email, is_new_user)

    def merge_to(from_user, to_user):
        # TODO: we could discover relations automatically and make this useful
        from sentry import roles
        from sentry.models import (
            Activity,
            AuditLogEntry,
            Authenticator,
            AuthIdentity,
            GroupAssignee,
            GroupBookmark,
            GroupSeen,
            GroupShare,
            GroupSubscription,
            Identity,
            OrganizationMember,
            OrganizationMemberTeam,
            UserAvatar,
            UserEmail,
            UserOption,
        )

        audit_logger.info("user.merge",
                          extra={
                              "from_user_id": from_user.id,
                              "to_user_id": to_user.id
                          })

        for obj in OrganizationMember.objects.filter(user=from_user):
            try:
                with transaction.atomic():
                    obj.update(user=to_user)
            # this will error if both users are members of obj.org
            except IntegrityError:
                pass

            # identify the highest priority membership
            # only applies if both users are members of obj.org
            # if roles are different, grants combined user the higher of the two
            to_member = OrganizationMember.objects.get(
                organization=obj.organization_id, user=to_user)
            if roles.get(obj.role).priority > roles.get(
                    to_member.role).priority:
                to_member.update(role=obj.role)

            for team in obj.teams.all():
                try:
                    with transaction.atomic():
                        OrganizationMemberTeam.objects.create(
                            organizationmember=to_member, team=team)
                # this will error if both users are on the same team in obj.org,
                # in which case, no need to update anything
                except IntegrityError:
                    pass

        model_list = (
            Authenticator,
            GroupAssignee,
            GroupBookmark,
            GroupSeen,
            GroupShare,
            GroupSubscription,
            Identity,
            UserAvatar,
            UserEmail,
            UserOption,
        )

        for model in model_list:
            for obj in model.objects.filter(user=from_user):
                try:
                    with transaction.atomic():
                        obj.update(user=to_user)
                except IntegrityError:
                    pass

        Activity.objects.filter(user=from_user).update(user=to_user)
        # users can be either the subject or the object of actions which get logged
        AuditLogEntry.objects.filter(actor=from_user).update(actor=to_user)
        AuditLogEntry.objects.filter(target_user=from_user).update(
            target_user=to_user)

        # remove any SSO identities that exist on from_user that might conflict
        # with to_user's existing identities (only applies if both users have
        # SSO identities in the same org), then pass the rest on to to_user
        AuthIdentity.objects.filter(
            user=from_user,
            auth_provider__organization__in=AuthIdentity.objects.filter(
                user=to_user).values("auth_provider__organization"),
        ).delete()
        AuthIdentity.objects.filter(user=from_user).update(user=to_user)

    def set_password(self, raw_password):
        super().set_password(raw_password)
        self.last_password_change = timezone.now()
        self.is_password_expired = False

    def refresh_session_nonce(self, request=None):
        from django.utils.crypto import get_random_string

        self.session_nonce = get_random_string(12)
        if request is not None:
            request.session["_nonce"] = self.session_nonce

    def get_orgs(self):
        from sentry.models import Organization, OrganizationMember, OrganizationStatus

        return Organization.objects.filter(
            status=OrganizationStatus.VISIBLE,
            id__in=OrganizationMember.objects.filter(
                user=self).values("organization"),
        )

    def get_projects(self):
        from sentry.models import OrganizationMemberTeam, Project, ProjectStatus, ProjectTeam

        return Project.objects.filter(
            status=ProjectStatus.VISIBLE,
            id__in=ProjectTeam.objects.filter(
                team_id__in=OrganizationMemberTeam.objects.filter(
                    organizationmember__user=self).values_list(
                        "team_id", flat=True)).values_list("project_id",
                                                           flat=True),
        )

    def get_orgs_require_2fa(self):
        from sentry.models import Organization, OrganizationStatus

        return Organization.objects.filter(
            flags=models.F("flags").bitor(Organization.flags.require_2fa),
            status=OrganizationStatus.VISIBLE,
            member_set__user=self,
        )

    def clear_lost_passwords(self):
        LostPasswordHash.objects.filter(user=self).delete()
예제 #30
0
 def test_insert_neg(self):
     bitfield = BitField(3, 5)
     packed = bitfield.insert(-1, 0)
     self.assertEqual(packed, 0b000_111_000)
     unpacked = bitfield.extract_signed(packed)
     self.assertEqual(unpacked, -1)