Esempio n. 1
0
  def get_zstring(self):
    """For string opcodes, return the address of the zstring pointed
    to by the PC.  Increment PC just past the text."""

    start_addr = self.program_counter
    bf = BitField(0)

    while True:
      bf.__init__(self._memory[self.program_counter])
      self.program_counter += 2
      if bf[7] == 1:
        break

    return start_addr
Esempio n. 2
0
a simulated computer modeled loosely on the ARM processor
found in many cell phones and the Raspberry Pi.

Instruction words are unsigned 32-bit integers
with the following fields (from high-order to low-order bits).  
All are unsigned except offset, which is a signed value in 
range -2^11 to 2^11 - 1. 

See docs/duck_machine.md for details. 
"""

from bitfield import BitField
from enum import Enum, Flag

# The field bit positions
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)

# The following operation codes control both the ALU and some
# other parts of the CPU.  Only the ALU is modeled in the
# bitfields project.  The CPU is introduced the following
# week.
# ADD, SUB, MUL, DIV, SHL, SHR are ALU-only operations
# HALT, LOAD, STORE involve other parts of the CPU

Esempio n. 3
0
class ProjectKey(Model):
    __core__ = True

    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)

    rate_limit_count = BoundedPositiveIntegerField(null=True)
    rate_limit_window = BoundedPositiveIntegerField(null=True)

    objects = ProjectKeyManager(
        cache_fields=("public_key", "secret_key"),
        # store projectkeys in memcached for longer than other models,
        # specifically to make the relay_projectconfig endpoint faster.
        cache_ttl=60 * 30,
    )

    data = JSONField()

    # support legacy project keys in API
    scopes = (
        "project:read",
        "project:write",
        "project:admin",
        "project:releases",
        "event:read",
        "event:write",
        "event:admin",
    )

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

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

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

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

    @classmethod
    def looks_like_api_key(cls, key):
        return bool(_uuid4_re.match(key))

    @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):
        return cls.objects.filter(
            project=project,
            roles=models.F("roles").bitor(cls.roles.store),
            status=ProjectKeyStatus.ACTIVE,
        ).first()

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

    @property
    def rate_limit(self):
        if self.rate_limit_count and self.rate_limit_window:
            return (self.rate_limit_count, self.rate_limit_window)
        return (0, 0)

    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, " ", letters=10).title()
        super().save(*args, **kwargs)

    def get_dsn(self, domain=None, secure=True, public=False):
        urlparts = urlparse(self.get_endpoint(public=public))

        if not public:
            key = f"{self.public_key}:{self.secret_key}"
        else:
            key = self.public_key

        # If we do not have a scheme or domain/hostname, dsn is never valid
        if not urlparts.netloc or not urlparts.scheme:
            return ""

        return "{}://{}@{}/{}".format(
            urlparts.scheme,
            key,
            urlparts.netloc + urlparts.path,
            self.project_id,
        )

    @property
    def organization_id(self):
        return self.project.organization_id

    @property
    def organization(self):
        return self.project.organization

    @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 = self.get_endpoint()

        return f"{endpoint}/api/{self.project_id}/csp-report/?sentry_key={self.public_key}"

    @property
    def security_endpoint(self):
        endpoint = self.get_endpoint()

        return f"{endpoint}/api/{self.project_id}/security/?sentry_key={self.public_key}"

    @property
    def minidump_endpoint(self):
        endpoint = self.get_endpoint()

        return f"{endpoint}/api/{self.project_id}/minidump/?sentry_key={self.public_key}"

    @property
    def unreal_endpoint(self):
        return f"{self.get_endpoint()}/api/{self.project_id}/unreal/{self.public_key}/"

    @property
    def js_sdk_loader_cdn_url(self):
        if settings.JS_SDK_LOADER_CDN_URL:
            return f"{settings.JS_SDK_LOADER_CDN_URL}{self.public_key}.min.js"
        else:
            endpoint = self.get_endpoint()
            return "{}{}".format(
                endpoint,
                reverse("sentry-js-sdk-loader", args=[self.public_key,
                                                      ".min"]),
            )

    def get_endpoint(self, public=True):
        if public:
            endpoint = settings.SENTRY_PUBLIC_ENDPOINT or settings.SENTRY_ENDPOINT
        else:
            endpoint = settings.SENTRY_ENDPOINT

        if not endpoint:
            endpoint = options.get("system.url-prefix")

        if features.has("organizations:org-subdomains",
                        self.project.organization):
            urlparts = urlparse(endpoint)
            if urlparts.scheme and urlparts.netloc:
                endpoint = "{}://{}.{}{}".format(
                    urlparts.scheme,
                    settings.SENTRY_ORG_SUBDOMAIN_TEMPLATE.format(
                        organization_id=self.project.organization_id),
                    urlparts.netloc,
                    urlparts.path,
                )

        return endpoint

    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,
            "rate_limit_count": self.rate_limit_count,
            "rate_limit_window": self.rate_limit_window,
        }

    def get_scopes(self):
        return self.scopes
Esempio n. 4
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 {
            'id': self.id,
            '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)
Esempio n. 5
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)
    user = FlexibleForeignKey(settings.AUTH_USER_MODEL, 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)

    # For audits
    user_added = FlexibleForeignKey(settings.AUTH_USER_MODEL, null=True, related_name='keys_added_set')
    date_added = models.DateTimeField(default=timezone.now, null=True)

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

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

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

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

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

    @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()
        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

        urlparts = urlparse(url or settings.SENTRY_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)

    def get_audit_log_data(self):
        return {
            'label': self.label,
            'user_id': self.user_id,
            'public_key': self.public_key,
            'secret_key': self.secret_key,
            'roles': int(self.roles),
            'status': self.status,
        }
Esempio n. 6
0
class Abon(BaseAccount):
    current_tariff = models.OneToOneField(AbonTariff,
                                          null=True,
                                          blank=True,
                                          on_delete=models.SET_NULL,
                                          default=None)
    group = models.ForeignKey(Group,
                              on_delete=models.SET_NULL,
                              blank=True,
                              null=True,
                              verbose_name=_('User group'))
    ballance = models.FloatField(default=0.0)
    ip_address = models.GenericIPAddressField(verbose_name=_('Ip address'),
                                              null=True,
                                              blank=True)
    description = models.TextField(_('Comment'), null=True, blank=True)
    street = models.ForeignKey(AbonStreet,
                               on_delete=models.SET_NULL,
                               null=True,
                               blank=True,
                               verbose_name=_('Street'))
    house = models.CharField(_('House'), max_length=12, null=True, blank=True)
    device = models.ForeignKey('devapp.Device',
                               null=True,
                               blank=True,
                               on_delete=models.SET_NULL)
    dev_port = models.ForeignKey('devapp.Port',
                                 null=True,
                                 blank=True,
                                 on_delete=models.SET_NULL)
    is_dynamic_ip = models.BooleanField(_('Is dynamic ip'), default=False)
    nas = models.ForeignKey('gw_app.NASModel',
                            null=True,
                            blank=True,
                            on_delete=models.SET_NULL,
                            verbose_name=_('Network access server'),
                            default=None)
    autoconnect_service = models.BooleanField(
        _('Automatically connect next service'), default=False)
    last_connected_tariff = models.ForeignKey(
        Tariff,
        verbose_name=_('Last connected service'),
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        default=None)

    MARKER_FLAGS = (('icon_donkey', _('Donkey')), ('icon_fire', _('Fire')),
                    ('icon_ok', _('Ok')), ('icon_king', _('King')), ('icon_tv',
                                                                     _('TV')),
                    ('icon_smile', _('Smile')), ('icon_dollar', _('Dollar')),
                    ('icon_service', _('Service')), ('icon_mrk', _('Marker')))
    markers = BitField(flags=MARKER_FLAGS, default=0)

    def get_flag_icons(self):
        """
        Return icon list of set flags from self.markers
        :return: ['m-icon-donkey', 'm-icon-tv', ...]
        """
        return tuple("m-%s" % name for name, state in self.markers if state)

    def is_markers_empty(self):
        return int(self.markers) == 0

    def active_tariff(self):
        return self.current_tariff

    objects = AbonManager()

    class Meta:
        db_table = 'abonent'
        permissions = (('can_buy_tariff', _('Buy service perm')),
                       ('can_add_ballance',
                        _('fill account')), ('can_ping', _('Can ping')))
        verbose_name = _('Abon')
        verbose_name_plural = _('Abons')
        ordering = ('fio', )
        unique_together = ('ip_address', 'nas')

    def add_ballance(self, current_user, amount, comment):
        AbonLog.objects.create(abon=self,
                               amount=amount,
                               author=current_user if isinstance(
                                   current_user, UserProfile) else None,
                               comment=comment)
        self.ballance += amount

    def pick_tariff(self, tariff, author, comment=None, deadline=None) -> None:
        """
        Trying to buy a service if enough money.
        :param tariff: instance of tariff_app.models.Tariff.
        :param author: Instance of accounts_app.models.UserProfile.
        Who connected this service. May be None if author is a system.
        :param comment: Optional text for logging this pay.
        :param deadline: Instance of datetime.datetime. Date when service is
        expired.
        :return: Nothing
        """
        if not isinstance(tariff, Tariff):
            raise TypeError

        amount = round(tariff.amount, 2)

        if tariff.is_admin and author is not None:
            if not author.is_staff:
                raise LogicError(
                    _('User that is no staff can not buy admin services'))

        if self.current_tariff is not None:
            if self.current_tariff.tariff == tariff:
                # if service already connected
                raise LogicError(_('That service already activated'))
            else:
                # if service is present then speak about it
                raise LogicError(_('Service already activated'))

        # if not enough money
        if self.ballance < amount:
            raise LogicError(
                _('%s not enough money for service %s') %
                (self.username, tariff.title))

        with transaction.atomic():
            new_abtar = AbonTariff.objects.create(deadline=deadline,
                                                  tariff=tariff)
            self.current_tariff = new_abtar
            if self.last_connected_tariff != tariff:
                self.last_connected_tariff = tariff

            # charge for the service
            self.ballance -= amount

            self.save(update_fields=('ballance', 'current_tariff',
                                     'last_connected_tariff'))

            # make log about it
            AbonLog.objects.create(abon=self,
                                   amount=-tariff.amount,
                                   author=author,
                                   comment=comment
                                   or _('Buy service default log'))

    def attach_ip_addr(self, ip, strict=False):
        """
        Attach ip address to account
        :param ip: Instance of str or ip_address
        :param strict: If strict is True then ip not replaced quietly
        :return: None
        """
        if strict and self.ip_address:
            raise LogicError('Ip address already exists')
        self.ip_address = ip
        self.save(update_fields=('ip_address', ))

    def free_ip_addr(self) -> bool:
        if self.ip_address:
            self.ip_address = None
            self.save(update_fields=('ip_address', ))
            return True
        return False

    # is subscriber have access to service,
    # view in tariff_app.custom_tariffs.<TariffBase>.manage_access()
    def is_access(self) -> bool:
        if not self.is_active:
            return False
        abon_tariff = self.active_tariff()
        if abon_tariff is None:
            return False
        trf = abon_tariff.tariff
        ct = trf.get_calc_type()(abon_tariff)
        return ct.manage_access(self)

    # make subscriber from agent structure
    def build_agent_struct(self):
        if not self.ip_address:
            return
        abon_tariff = self.active_tariff()
        if abon_tariff:
            abon_tariff = abon_tariff.tariff
            return SubnetQueue(name="uid%d" % self.pk,
                               network=self.ip_address,
                               max_limit=(abon_tariff.speedIn,
                                          abon_tariff.speedOut),
                               is_access=self.is_access())

    def nas_sync_self(self) -> Optional[Exception]:
        """
        Synchronize user with gateway
        :return:
        """
        if self.nas is None:
            raise LogicError(_('gateway required'))
        try:
            agent_abon = self.build_agent_struct()
            if agent_abon is not None:
                mngr = self.nas.get_nas_manager()
                mngr.update_user(agent_abon)
        except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
            print('ERROR:', e)
            return e
        except LogicError:
            pass

    def nas_add_self(self):
        """
        Will add this user to network access server
        :return:
        """
        if self.nas is None:
            raise LogicError(_('gateway required'))
        try:
            agent_abon = self.build_agent_struct()
            if agent_abon is not None:
                mngr = self.nas.get_nas_manager()
                mngr.add_user(agent_abon)
        except (NasFailedResult, NasNetworkError, ConnectionResetError) as e:
            print('ERROR:', e)
            return e
        except LogicError:
            pass

    def get_absolute_url(self):
        return resolve_url('abonapp:abon_home', self.group.id, self.username)

    def enable_service(self, tariff: Tariff, deadline=None, time_start=None):
        """
        Makes a services for current user, without money
        :param tariff: Instance of service
        :param deadline: Time when service is expired
        :param time_start: Time when service has started
        :return: None
        """
        if deadline is None:
            deadline = tariff.calc_deadline()
        if time_start is None:
            time_start = datetime.now()
        new_abtar = AbonTariff.objects.create(deadline=deadline,
                                              tariff=tariff,
                                              time_start=time_start)
        self.current_tariff = new_abtar
        self.last_connected_tariff = tariff
        self.save(update_fields=('current_tariff', 'last_connected_tariff'))
Esempio n. 7
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'
    )
    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.
        """

        return cls.objects.get(
            id=settings.SENTRY_ORGANIZATION,
            status=OrganizationStatus.ACTIVE,
        )

    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])
Esempio n. 8
0
class AssetType(models.Model):
    JPEG = 'jpeg'
    PNG = 'png'
    FORMAT_CHOICES = ((JPEG, 'JPEG'), (PNG, 'PNG'))

    slug = models.SlugField(verbose_name=_("Slug"), unique=True)
    formats = BitField(verbose_name=_('Formats'),
                       flags=FORMAT_CHOICES,
                       default=0)
    min_width = models.IntegerField(verbose_name=_('Min Width'), default=0)
    min_height = models.IntegerField(verbose_name=_('Min Height'), default=0)
    aspect = models.FloatField(verbose_name=_('Aspect'), default=0)
    accuracy = models.FloatField(verbose_name=_('Aspect accuracy'),
                                 default=0.01)
    max_size = models.IntegerField(verbose_name=_('Max file size'), default=0)

    required_for = models.ManyToManyField(
        ContentType,
        blank=True,
        verbose_name=_('Required for'),
        related_name='required_asset_types',
        related_query_name='required_asset_types')
    allowed_for = models.ManyToManyField(
        ContentType,
        blank=True,
        verbose_name=_('Allowed for'),
        related_name='allowed_asset_types',
        related_query_name='allowed_asset_types')

    class Meta:
        abstract = defaults.ASSET_TYPE_MODEL != 'image_assets.AssetType'
        verbose_name = _('Asset Type')
        verbose_name_plural = _('Asset Types')

    objects = AssetTypeManager()

    def __str__(self):
        return self.slug

    # noinspection PyUnusedLocal
    def get_validators(
            self,
            file: Image.Image) -> List[Callable[[Image.Image], List[str]]]:
        """
        Returns list of checks to run against file.

        :param file: opened image file.
        :return: list of check methods.
        """
        return [
            self.validate_format,
            self.validate_dimensions,
            self.validate_aspect,
        ]

    def validate_max_size(self, value: FieldFile) -> List[str]:
        """ Validate max file size."""
        if (self.max_size and value.size and self.max_size < value.size):
            msg = _('File size must be not greater than %s')
            return [msg % self.max_size]
        return []

    def validate_format(self, file: Image.Image) -> List[str]:
        """ Validate allowed format list."""
        fmt = file.format.lower()
        set_flags = dict(self.formats.items())
        if self.formats and not set_flags.get(fmt):
            msg = _('Image format must be one of %s')
            formats = ','.join([
                self.formats.get_label(k) for k, v in self.formats.items() if v
            ])
            return [msg % formats]
        return []

    def validate_dimensions(self, file: Image.Image) -> List[str]:
        """ Validate minimum image width and height."""
        errors = []
        # image width
        if file.width and self.min_width > file.width:
            msg = _('Image width must be not less than %s')
            errors.append(msg % self.min_width)
        # image height
        if file.height and self.min_height > file.height:
            msg = _('Image height must be not less than %s')
            errors.append(msg % self.min_height)
        return errors

    def validate_aspect(self, file: Image.Image) -> List[str]:
        """ Validate image aspect ratio with accuracy."""
        if not (file.width and file.height and self.aspect):
            return []
        image_aspect = file.width / file.height
        delta = abs(image_aspect - self.aspect)
        if self.accuracy == 0:
            if image_aspect != self.aspect:
                msg = _('Image aspect must be %s')
                return [msg % self.aspect]
        elif round(delta / self.accuracy) > 1:
            # round at scale of accuracy
            msg = _('Image aspect must be %(aspect)s ± %(accuracy)s')
            args = {'aspect': self.aspect, 'accuracy': self.accuracy}
            return [msg % args]
        return []
Esempio n. 9
0
 class TestModel(models.Model):
     flags = BitField(
         flags=("FLAG_0", "FLAG_1", "FLAG_2", "FLAG_3"), default=("FLAG_1", "FLAG_2")
     )
Esempio n. 10
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')
    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',
    ])

    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 merge_to(self, project):
        from sentry.models import (Group, GroupTagValue, Event, TagValue)

        if not isinstance(project, Project):
            project = Project.objects.get_from_cache(pk=project)

        for group in Group.objects.filter(project=self):
            try:
                other = Group.objects.get(project=project, )
            except Group.DoesNotExist:
                group.update(project=project)
                GroupTagValue.objects.filter(
                    project_id=self.id,
                    group_id=group.id,
                ).update(project_id=project.id)
            else:
                Event.objects.filter(
                    group_id=group.id, ).update(group_id=other.id)

                for obj in GroupTagValue.objects.filter(group=group):
                    obj2, created = GroupTagValue.objects.get_or_create(
                        project_id=project.id,
                        group_id=group.id,
                        key=obj.key,
                        value=obj.value,
                        defaults={'times_seen': obj.times_seen})
                    if not created:
                        obj2.update(times_seen=F('times_seen') +
                                    obj.times_seen)

        for fv in TagValue.objects.filter(project=self):
            TagValue.objects.get_or_create(project=project,
                                           key=fv.key,
                                           value=fv.value)
            fv.delete()
        self.delete()

    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

    def get_tags(self, with_internal=True):
        from sentry.models import TagKey

        if not hasattr(self, '_tag_cache'):
            tags = self.get_option('tags', None)
            if tags is None:
                tags = [
                    t for t in TagKey.objects.all_keys(self)
                    if with_internal or not t.startswith('sentry:')
                ]
            self._tag_cache = tags
        return self._tag_cache

    # 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 is_user_subscribed_to_workflow(self, user):
        from sentry.models import UserOption, UserOptionValue

        opt_value = UserOption.objects.get_value(user,
                                                 'workflow:notifications',
                                                 project=self)
        if opt_value is None:
            opt_value = UserOption.objects.get_value(
                user, 'workflow:notifications',
                UserOptionValue.all_conversations)
        return opt_value == UserOptionValue.all_conversations
Esempio n. 11
0
class WebsiteContact(models.Model):
    id = models.AutoField(primary_key=True)
    website_id = models.IntegerField()
    name = models.CharField(max_length=255)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    middle_name = models.CharField(max_length=255)
    score = BitField(flags=(
        ('has_org', 'has organization', 1),
        ('has_title', 'has title', 2),
        ('has_phone', 'has phone', 4),
        ('has_email', 'has email', 8),
        ('has_unique_phone', 'has unique phone', 16),
        ('has_unique_email', 'has unique email', 32),
        ('has_matching_email', 'has matching email', 64),
    ))

    class Meta:
        db_table = 'website_contacts'

    @staticmethod
    def add_contact(contact, spider, from_tuple=True):
        contacts = spider.contacts
        new_contact = {}
        if from_tuple:
            for key, contact_part in contact.items():
                if key == 'URL':
                    continue
                new_contact[key] = [value[0] for value in contact_part]
        else:
            new_contact = contact

        name = new_contact['PERSON'][0]
        name_parts = WebsiteContact.get_name_key(name)
        name_key = name_parts['name_key']
        new_name = name_parts['name']

        new_contact['URL'] = contact['URL']
        new_contact['PERSON'] = new_name.title()

        if 'TITLE' in new_contact:
            titles = new_contact['TITLE']
            new_titles = []
            for job_title in titles:
                new_titles.append(job_title)
                pass
            new_contact['TITLE'] = new_titles

        if name_key in contacts:
            merge_dicts(contacts[name_key], new_contact)
        else:
            contacts[name_key] = new_contact

        important_keys = ['PERSON', 'EMAIL', 'PHONE']
        contact_keys = new_contact.keys()
        important_keys_intersection = list(
            set(important_keys) & set(contact_keys))

        if len(important_keys_intersection) >= 3:
            contacts[name_key]['DONE'] = True
        else:
            contacts[name_key]['DONE'] = False

        return contacts[name_key]

    @staticmethod
    def get_name_key(name):
        pp_name = pp_contact_name({'PERSON': name})
        new_name = ''

        if 'GivenName' in pp_name:
            new_name += pp_name['GivenName']

        if 'Surname' in pp_name:
            new_name += ' ' + pp_name['Surname']

        if not ('GivenName' in pp_name and 'Surname' in pp_name):
            new_name = name

        name_key = re.sub(r'[^a-zA-Z]+', '', new_name)
        return {
            'name_key': hashlib.md5(name_key.encode('utf8')).hexdigest(),
            'name': new_name
        }

    @staticmethod
    def save_contact(website, contact, score):
        metas = {}
        url = contact['URL']
        website_contact = website.extract_contact(contact, score)
        website_contact.save()
        for _type, items in contact.items():
            for item in items:
                key = str(website_contact.id) + str(_type) + str(item)
                website_contact_meta = WebsiteContactMeta(
                    website_contact_id=website_contact.id,
                    meta_key=_type,
                    meta_value=item,
                    page=url)
                website_contact_meta.update_phone_value(
                    website.get_country_codes())
                metas[key] = website_contact_meta
        WebsiteContactMeta.objects.bulk_create(metas.values(),
                                               ignore_conflicts=True)
        return website_contact

    @staticmethod
    def valid_contact(contact, length=1, has_contact=False):
        """
        check if a contact array is valid
        :param contact:
        :param length:
        :param has_contact:
        :return:
        """
        if not contact:
            return False

        important_keys = ['PERSON', 'TITLE', 'EMAIL', 'PHONE']
        has_contacts_keys = ['EMAIL', 'PHONE']
        contact_keys = contact.keys()
        important_keys_intersection = list(
            set(important_keys) & set(contact_keys))

        if has_contact:
            has_contact_keys_intersection = list(
                set(contact_keys) & set(has_contacts_keys))
            if len(has_contact_keys_intersection) == 0:
                return False

        if len(important_keys_intersection) < length:
            return False

        if 'PERSON' not in contact:
            return False
        else:
            if len(contact['PERSON']) > 1 and not isinstance(
                    contact['PERSON'], str):
                return False

        return True
Esempio n. 12
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(),
        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(max_length=32,
                                    default=str(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 __str__(self):
        return f"{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().save(*args, **kwargs)
        else:
            super().save(*args, **kwargs)

    def delete(self):
        if self.is_default:
            raise Exception("You cannot delete the the default organization.")
        return super().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(
                            f"{model_name}.migrate-skipped",
                            extra={
                                "from_organization_id": from_org.id,
                                "to_organization_id": to_org.id,
                            },
                        )
                    else:
                        logger.info(
                            f"{model_name}.migrate",
                            extra={
                                "instance_id": instance.id,
                                "from_organization_id": from_org.id,
                                "to_organization_id": to_org.id,
                            },
                        )
            else:
                logger.info(
                    f"{model_name}.migrate",
                    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="{}Organization Queued for Deletion".format(
                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])
Esempio n. 13
0
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
Esempio n. 14
0
class Event(with_metaclass(ModelBase, *get_model_bases())):
    '''
    This model stores meta data for a date.  You can relate this data to many
    other models.
    '''
    appropriate_for = BitField(verbose_name=_("appropriate for"),
                               flags=AUDIENCE_FLAGS,
                               default=31)
    start = models.DateTimeField(_("start"))
    end = models.DateTimeField(
        _("end"),
        help_text=_("The end time must be later than the start time."))
    all_day = models.BooleanField(default=False)
    title = models.CharField(_("title"), max_length=255)
    description = models.TextField(_("description"), null=True, blank=True)
    creator = models.ForeignKey(AUTH_USER_MODEL,
                                null=True,
                                verbose_name=_("creator"),
                                related_name='creator')
    created_on = models.DateTimeField(_("created on"), auto_now_add=True)
    rule = models.ForeignKey(
        'events.Rule',
        null=True,
        blank=True,
        verbose_name=_("Repeats"),
        help_text=_("Select '----' for a one time only event."),
        related_name="events")
    end_recurring_period = models.DateTimeField(
        _("Ends on"),
        null=True,
        blank=True,
        help_text=_("This date is ignored for one time only events."))
    calendar = models.ForeignKey('events.Calendar', related_name="events")
    objects = EventManager()

    class Meta(object):
        verbose_name = _('event')
        verbose_name_plural = _('events')
        app_label = 'events'
        get_latest_by = 'start'

    def __str__(self):
        date_format = u'l, %s' % settings.DATE_FORMAT
        return ugettext('%(title)s: %(start)s - %(end)s') % {
            'title': self.title,
            'start': date(self.start, date_format),
            'end': date(self.end, date_format),
        }

    def save(self, *args, **kwargs):
        # if the event is an all day event then make sure it has the right start and end times
        if self.all_day:
            self.start = datetime.datetime.combine(self.start,
                                                   datetime.time.min)
            self.end = datetime.datetime.combine(self.end, datetime.time.max)
        super(Event, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('event', args=[self.id])

    def create_relation(self, obj, distinction=None):
        """
        Creates a EventRelation between self and obj.
        """
        EventRelation.objects.create_relation(self, obj, distinction)

    def get_occurrences(self, start, end, boost=True):
        """
        >>> rule = Rule(frequency = "MONTHLY", name = "Monthly")
        >>> rule.save()
        >>> event = Event(rule=rule, start=datetime.datetime(2008,1,1), end=datetime.datetime(2008,1,2))
        >>> event.rule
        <Rule: Monthly>
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        ['2008-02-01 00:00:00 to 2008-02-02 00:00:00', '2008-03-01 00:00:00 to 2008-03-02 00:00:00']

        Ensure that if an event has no rule, that it appears only once.

        >>> event = Event(start=datetime.datetime(2008,1,1,8,0), end=datetime.datetime(2008,1,1,9,0))
        >>> occurrences = event.get_occurrences(datetime.datetime(2008,1,24), datetime.datetime(2008,3,2))
        >>> ["%s to %s" %(o.start, o.end) for o in occurrences]
        []

        """
        from events.utils import OccurrenceReplacer
        if self.pk and boost:
            # performance booster for occurrences relationship, unless you have
            # already selected several in a QuerySet. Then this slows performance.
            Event.objects.select_related('occurrence').get(pk=self.pk)
        persisted_occurrences = self.occurrence_set.all()
        occ_replacer = OccurrenceReplacer(persisted_occurrences)
        occurrences = self._get_occurrence_list(start, end)
        final_occurrences = []
        for occ in occurrences:
            # replace occurrences with their persisted counterparts
            if occ_replacer.has_occurrence(occ):
                p_occ = occ_replacer.get_occurrence(occ)
                # ...but only if they are within this period
                if p_occ.start < end and p_occ.end >= start:
                    final_occurrences.append(p_occ)
            else:
                final_occurrences.append(occ)
        # then add persisted occurrences which originated outside of this period but now
        # fall within it
        final_occurrences += occ_replacer.get_additional_occurrences(
            start, end)
        return final_occurrences

    def get_rrule_object(self):
        if self.rule is not None:
            params = self.rule.get_params()
            frequency = rrule.__dict__[self.rule.frequency]
            return rrule.rrule(frequency, dtstart=self.start, **params)

    def _create_occurrence(self, start, end=None):
        if end is None:
            end = start + (self.end - self.start)
        return Occurrence(event=self,
                          start=start,
                          end=end,
                          original_start=start,
                          original_end=end)

    def get_occurrence(self, date):
        if timezone.is_naive(date) and settings.USE_TZ:
            date = timezone.make_aware(date, timezone.utc)
        rule = self.get_rrule_object()
        if rule:
            next_occurrence = rule.after(date, inc=True)
        else:
            next_occurrence = self.start
        if next_occurrence == date:
            try:
                return Occurrence.objects.get(event=self, original_start=date)
            except Occurrence.DoesNotExist:
                return self._create_occurrence(next_occurrence)

    def _get_occurrence_list(self, start, end):
        """
        returns a list of occurrences for this event from start to end.
        """
        difference = (self.end - self.start)
        if self.rule is not None:
            occurrences = []
            if self.end_recurring_period and self.end_recurring_period < end:
                end = self.end_recurring_period
            rule = self.get_rrule_object()
            o_starts = []
            o_starts.extend(rule.between(start, end, inc=True))
            o_starts.extend(
                rule.between(start - (difference // 2),
                             end - (difference // 2),
                             inc=True))
            o_starts.extend(
                rule.between(start - difference, end - difference, inc=True))
            for o_start in o_starts:
                o_end = o_start + difference
                occurrences.append(self._create_occurrence(o_start, o_end))
            return occurrences
        else:
            # check if event is in the period
            if self.start < end and self.end >= start:
                return [self._create_occurrence(self.start)]
            else:
                return []

    def _occurrences_after_generator(self, after=None):
        """
        returns a generator that produces unpresisted occurrences after the
        datetime ``after``.
        """

        if after is None:
            after = timezone.now()
        rule = self.get_rrule_object()
        if rule is None:
            if self.end > after:
                yield self._create_occurrence(self.start, self.end)
            raise StopIteration
        date_iter = iter(rule)
        difference = self.end - self.start
        while True:
            o_start = next(date_iter)
            if self.end_recurring_period and o_start > self.end_recurring_period:
                raise StopIteration
            o_end = o_start + difference
            if o_end > after:
                yield self._create_occurrence(o_start, o_end)

    def occurrences_after(self, after=None):
        """
        returns a generator that produces occurrences after the datetime
        ``after``.  Includes all of the persisted Occurrences.
        """
        from events.utils import OccurrenceReplacer
        occ_replacer = OccurrenceReplacer(self.occurrence_set.all())
        generator = self._occurrences_after_generator(after)
        while True:
            next_occurence = next(generator)
            yield occ_replacer.get_occurrence(next_occurence)
class BitFieldTestModel(models.Model):
    flags = BitField(
        flags=('FLAG_0', 'FLAG_1', 'FLAG_2', 'FLAG_3', ), default=3, db_column='another_name'
    )
Esempio n. 16
0
class Project(Model, PendingDeletionMixin):
    """
    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"),
            ("has_issue_alerts_targeting",
             "This Project has issue alerts targeting"),
            ("has_transactions", "This Project has sent transactions"),
            ("has_alert_filters", "This Project has filters"),
        ),
        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 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):
        """ :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.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,
                             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)

    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):
        from sentry.incidents.models import AlertRule
        from sentry.models import Rule

        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 "project_token:%s" % 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, 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)
Esempio n. 17
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)
    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)

    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')

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

    def save(self, *args, **kwargs):
        if not self.username:
            self.username = self.email
        return super(User, self).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):
        try:
            avatar = self.avatar.first()
            return avatar.get_avatar_type_display()
        except ObjectDoesNotExist:
            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='%sConfirm Email' % (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,
            AuthIdentity,
            Authenticator,
            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)
            except IntegrityError:
                pass

            # identify the highest priority membership
            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,
                        )
                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)
        AuditLogEntry.objects.filter(actor=from_user, ).update(actor=to_user)
        AuditLogEntry.objects.filter(
            target_user=from_user, ).update(target_user=to_user)

        # remove any duplicate identities that exist on the current user that
        # might conflict w/ the new users existing SSO
        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(User, self).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_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()
Esempio n. 18
0
class OrganizationMember(Model):
    """
    Identifies relationships between organizations 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.
    """

    __include_in_export__ = True

    objects = OrganizationMemberManager()

    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, max_length=75)
    role = models.CharField(max_length=32, default=str(roles.get_default().id))
    flags = BitField(
        flags=(
            ("sso:linked", "sso:linked"),
            ("sso:invalid", "sso:invalid"),
            ("member-limit:restricted", "member-limit:restricted"),
        ),
        default=0,
    )
    token = models.CharField(max_length=64, null=True, blank=True, unique=True)
    date_added = models.DateTimeField(default=timezone.now)
    token_expires_at = models.DateTimeField(default=None, null=True)
    has_global_access = models.BooleanField(default=True)
    teams = models.ManyToManyField("sentry.Team",
                                   blank=True,
                                   through="sentry.OrganizationMemberTeam")
    inviter = FlexibleForeignKey(
        settings.AUTH_USER_MODEL,
        null=True,
        blank=True,
        related_name="sentry_inviter_set",
        on_delete=models.SET_NULL,
    )
    invite_status = models.PositiveSmallIntegerField(
        choices=(
            (InviteStatus.APPROVED.value, _("Approved")),
            (
                InviteStatus.REQUESTED_TO_BE_INVITED.value,
                _("Organization member requested to invite user"),
            ),
            (InviteStatus.REQUESTED_TO_JOIN.value,
             _("User requested to join organization")),
        ),
        default=InviteStatus.APPROVED.value,
        null=True,
    )

    # 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"
        if self.token and not self.token_expires_at:
            self.refresh_expires_at()
        super().save(*args, **kwargs)

    def set_user(self, user):
        self.user = user
        self.email = None
        self.token = None
        self.token_expires_at = None

    def remove_user(self):
        self.email = self.get_email()
        self.user = None
        self.token = self.generate_token()

    def regenerate_token(self):
        self.token = self.generate_token()
        self.refresh_expires_at()

    def refresh_expires_at(self):
        now = timezone.now()
        self.token_expires_at = now + timedelta(days=INVITE_DAYS_VALID)

    def approve_invite(self):
        self.invite_status = InviteStatus.APPROVED.value
        self.regenerate_token()

    def get_invite_status_name(self):
        if self.invite_status is None:
            return
        return invite_status_names[self.invite_status]

    @property
    def invite_approved(self):
        return self.invite_status == InviteStatus.APPROVED.value

    @property
    def requested_to_join(self):
        return self.invite_status == InviteStatus.REQUESTED_TO_JOIN.value

    @property
    def requested_to_be_invited(self):
        return self.invite_status == InviteStatus.REQUESTED_TO_BE_INVITED.value

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

    @property
    def token_expired(self):
        # Old tokens don't expire to preserve compatibility and not require
        # a backfill migration.
        if self.token_expires_at is None:
            return False
        if self.token_expires_at > timezone.now():
            return False
        return True

    @property
    def legacy_token(self):
        checksum = md5()
        checksum.update(str(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 or not self.invite_approved:
            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=f"Action Required for {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.models import LostPasswordHash
        from sentry.utils.email import MessageBuilder

        email = self.get_email()

        recover_uri = "{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=f"Action Required for {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 OrganizationMemberTeam, 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,
            "invite_status": invite_status_names[self.invite_status],
        }

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

        return Team.objects.filter(
            status=TeamStatus.VISIBLE,
            id__in=OrganizationMemberTeam.objects.filter(
                organizationmember=self, is_active=True).values("team"),
        )

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

        disabled_scopes = set()

        if self.role == "member":
            if not self.organization.get_option("sentry:events_member_admin",
                                                EVENTS_MEMBER_ADMIN_DEFAULT):
                disabled_scopes.add("event:admin")
            if not self.organization.get_option("sentry:alerts_member_write",
                                                ALERTS_MEMBER_WRITE_DEFAULT):
                disabled_scopes.add("alerts:write")

        scopes = frozenset(s for s in scopes if s not in disabled_scopes)
        return scopes

    def validate_invitation(self, user_to_approve, allowed_roles):
        """
        Validates whether an org has the options to invite members, handle join requests,
        and that the member role doesn't exceed the allowed roles to invite.
        """
        organization = self.organization
        if not features.has("organizations:invite-members",
                            organization,
                            actor=user_to_approve):
            raise UnableToAcceptMemberInvitationException(ERR_CANNOT_INVITE)

        if (organization.get_option("sentry:join_requests") is False and
                self.invite_status == InviteStatus.REQUESTED_TO_JOIN.value):
            raise UnableToAcceptMemberInvitationException(
                ERR_JOIN_REQUESTS_DISABLED)

        # members cannot invite roles higher than their own
        if self.role not in {r.id for r in allowed_roles}:
            raise UnableToAcceptMemberInvitationException(
                f"You do not have permission approve a member invitation with the role {self.role}."
            )
        return True

    def approve_member_invitation(self,
                                  user_to_approve,
                                  api_key=None,
                                  ip_address=None,
                                  referrer=None):
        """
        Approve a member invite/join request and send an audit log entry
        """
        from sentry.models.auditlogentry import AuditLogEntryEvent
        from sentry.utils.audit import create_audit_entry_from_user

        self.approve_invite()
        self.save()

        if settings.SENTRY_ENABLE_INVITES:
            self.send_invite_email()
            member_invited.send_robust(
                member=self,
                user=user_to_approve,
                sender=self.approve_member_invitation,
                referrer=referrer,
            )

        create_audit_entry_from_user(
            user_to_approve,
            api_key,
            ip_address,
            organization_id=self.organization_id,
            target_object=self.id,
            data=self.get_audit_log_data(),
            event=AuditLogEntryEvent.MEMBER_INVITE if
            settings.SENTRY_ENABLE_INVITES else AuditLogEntryEvent.MEMBER_ADD,
        )

    def reject_member_invitation(
        self,
        user_to_approve,
        api_key=None,
        ip_address=None,
    ):
        """
        Reject a member invite/jin request and send an audit log entry
        """
        from sentry.models.auditlogentry import AuditLogEntryEvent
        from sentry.utils.audit import create_audit_entry_from_user

        self.delete()

        create_audit_entry_from_user(
            user_to_approve,
            api_key,
            ip_address,
            organization_id=self.organization_id,
            target_object=self.id,
            data=self.get_audit_log_data(),
            event=AuditLogEntryEvent.INVITE_REQUEST_REMOVE,
        )

    def get_allowed_roles_to_invite(self):
        """
        Return a list of roles which that member could invite
        Must check if member member has member:admin first before checking
        """
        return [
            r for r in roles.get_all()
            if r.priority <= roles.get(self.role).priority
        ]

    def is_only_owner(self) -> bool:
        if self.role != roles.get_top_dog().id:
            return False

        return (not OrganizationMember.objects.filter(
            organization=self.organization_id,
            role=roles.get_top_dog().id,
            user__isnull=False,
            user__is_active=True,
        ).exclude(id=self.id).exists())
Esempio n. 19
0
class ProjectKey(Model):
    __core__ = True

    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:admin',
        'project:releases',
        'event:read',
        'event:write',
        'event:admin',
    )

    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 looks_like_api_key(cls, key):
        return bool(_uuid4_re.match(key))

    @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, ' ', letters=10).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
Esempio n. 20
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,
            ReleaseHeadCommit,
            Repository,
            Team,
            Environment,
        )

        for from_member in OrganizationMember.objects.filter(
                organization=from_org, user__isnull=False):
            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,
                      ReleaseHeadCommit, Repository, Environment):
            try:
                with transaction.atomic():
                    model.objects.filter(organization_id=from_org.id, ).update(
                        organization_id=to_org.id)
            except IntegrityError:
                pass

    # 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])
Esempio n. 21
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)
Esempio n. 22
0
class CommunicationPreference(CommunicationPreferenceMixin, BaseModel):
    """
        A CommunicationPreference object defines how a Worker would like to
        contacted for a given CommunicationType.

        Attributes:
            worker (orchestra.models.Worker):
                Django user that the preference represents.
            methods (BitField):
                The ways in which the user would like to be contacted.
            type (CommunicationType):
                The type of communication to which this preference applies.
    """
    objects = CommunicationPreferenceManager()

    class CommunicationMethods:
        SLACK = 'slack'
        EMAIL = 'email'

    COMMUNICATION_METHODS = (
        (CommunicationMethods.SLACK, 'Slack'),
        (CommunicationMethods.EMAIL, 'Email'),
    )

    class CommunicationType(ChoicesEnum):
        TASK_STATUS_CHANGE = 'task_status_change'
        NEW_TASK_AVAILABLE = 'new_task_available'

    COMMUNICATION_TYPE_DESCRIPTIONS = {
        CommunicationType.TASK_STATUS_CHANGE: {
            'short_description':
            'Task Status Changes',
            'long_description':
            """
            When a task status changes (e.g., when it moves from in
            review to returned from reviewer), you will automatically
            receive a notification in the project Slack group. Select
            whether you would like to receive an email notification as
            well.
            """
        },
        CommunicationType.NEW_TASK_AVAILABLE: {
            'short_description':
            'New Tasks',
            'long_description':
            """
            Select your preferred channel for being notified when a
            new task is available to work on.
            """
        }
    }

    worker = models.ForeignKey(Worker, on_delete=models.CASCADE)
    methods = BitField(flags=COMMUNICATION_METHODS,
                       blank=True,
                       null=True,
                       default=None)
    communication_type = models.IntegerField(
        choices=CommunicationType.choices())

    class Meta:
        app_label = 'orchestra'
        unique_together = (('worker', 'communication_type'), )
Esempio n. 23
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_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)
Esempio n. 24
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"
Esempio n. 25
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"),
    )

    # Those fields in the model are editable by the user
    TRACKED_FIELDS = ["name", "year", "platforms", "genres", "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,
            "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())],
            "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.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"
        elif "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,
        )
Esempio n. 26
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'),
    ))
    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):
        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': int(self.scopes),
            'status': self.status,
        }

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

    def has_scope(self, scope):
        return scope in self.scopes
Esempio n. 27
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,
            AuthIdentity,
            Authenticator,
            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 Project, ProjectStatus, ProjectTeam, OrganizationMemberTeam

        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()
Esempio n. 28
0
class OrganizationMember(Model):
    """
    Identifies relationships between organizations 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, max_length=75)
    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)
    token_expires_at = models.DateTimeField(default=None, null=True)
    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"
        if self.token and not self.token_expires_at:
            self.refresh_expires_at()
        super(OrganizationMember, self).save(*args, **kwargs)

    def set_user(self, user):
        self.user = user
        self.email = None
        self.token = None
        self.token_expires_at = None

    def remove_user(self):
        self.email = self.get_email()
        self.user = None
        self.token = self.generate_token()

    def regenerate_token(self):
        self.token = self.generate_token()
        self.refresh_expires_at()

    def refresh_expires_at(self):
        now = timezone.now()
        self.token_expires_at = now + timedelta(days=INVITE_DAYS_VALID)

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

    @property
    def token_expired(self):
        # Old tokens don't expire to preserve compatibility and not require
        # a backfill migration.
        if self.token_expires_at is None:
            return False
        if self.token_expires_at > timezone.now():
            return False
        return True

    @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

        return Team.objects.filter(
            status=TeamStatus.VISIBLE,
            id__in=OrganizationMemberTeam.objects.filter(
                organizationmember=self, is_active=True).values("team"),
        )

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

    @classmethod
    def delete_expired(cls, threshold):
        """
        Delete un-accepted member invitations that expired
        ``threshold`` days ago.
        """
        cls.objects.filter(
            token_expires_at__lt=threshold,
            user_id__exact=None).exclude(email__exact=None).delete()
Esempio n. 29
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')
    # DEPRECATED. use teams instead.
    team = FlexibleForeignKey('sentry.Team',
                              null=True,
                              on_delete=models.SET_NULL)
    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)
            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__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):
        team_name = self.teams.values_list('name', flat=True).first()
        if team_name is not None and team_name not in self.name:
            return '%s %s' % (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 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
Esempio n. 30
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)
            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):
        team_name = self.teams.values_list('name', flat=True).first()
        if team_name is not None and team_name not in self.name:
            return '%s %s' % (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=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
class CompositeBitFieldTestModel(models.Model):
    flags_1 = BitField(flags=('FLAG_0', 'FLAG_1', 'FLAG_2', 'FLAG_3', ), default=0)
    flags_2 = BitField(flags=('FLAG_4', 'FLAG_5', 'FLAG_6', 'FLAG_7', ), default=0)
    flags = CompositeBitField(('flags_1', 'flags_2', ))