示例#1
0
class UserNotificationDetailsSerializer(serializers.Serializer):
    deployNotifications = EmptyIntegerField(
        required=False, min_value=2, max_value=4, allow_null=True
    )
    personalActivityNotifications = serializers.BooleanField(required=False)
    selfAssignOnResolve = serializers.BooleanField(required=False)
    subscribeByDefault = serializers.BooleanField(required=False)
    workflowNotifications = EmptyIntegerField(
        required=False, min_value=0, max_value=2, allow_null=True
    )
class CheckInSerializer(serializers.Serializer):
    status = serializers.ChoiceField(choices=(
        ("ok", CheckInStatus.OK),
        ("error", CheckInStatus.ERROR),
        ("in_progress", CheckInStatus.IN_PROGRESS),
    ))
    duration = EmptyIntegerField(required=False, allow_null=True)
示例#3
0
class RepositorySerializer(serializers.Serializer):
    status = serializers.ChoiceField(choices=(
        # XXX(dcramer): these are aliased, and we prefer 'active' over 'visible'
        ("visible", "visible"),
        ("active", "active"),
    ))
    integrationId = EmptyIntegerField(required=False, allow_null=True)
示例#4
0
class CronJobValidator(serializers.Serializer):
    schedule_type = serializers.ChoiceField(
        choices=zip(SCHEDULE_TYPES.keys(), SCHEDULE_TYPES.keys()))
    schedule = ObjectField()
    checkin_margin = EmptyIntegerField(required=False, default=None)
    max_runtime = EmptyIntegerField(required=False, default=None)

    def validate_schedule_type(self, value):
        if value:
            value = SCHEDULE_TYPES[value]
        return value

    def validate(self, attrs):
        if "schedule_type" in attrs:
            schedule_type = attrs["schedule_type"]
        else:
            schedule_type = self.instance["schedule_type"]

        value = attrs.get("schedule")
        if not value:
            return attrs

        if schedule_type == ScheduleType.INTERVAL:
            # type: [int count, str unit name]
            if not isinstance(value, list):
                raise ValidationError("Invalid value for schedule_type")
            if not isinstance(value[0], int):
                raise ValidationError(
                    "Invalid value for schedule unit count (index 0)")
            if value[1] not in INTERVAL_NAMES:
                raise ValidationError(
                    "Invalid value for schedule unit name (index 1)")
        elif schedule_type == ScheduleType.CRONTAB:
            # type: str schedule
            if not isinstance(value, six.string_types):
                raise ValidationError("Invalid value for schedule_type")
            value = value.strip()
            if value.startswith("@"):
                try:
                    value = NONSTANDARD_CRONTAB_SCHEDULES[value]
                except KeyError:
                    raise ValidationError("Schedule was not parseable")
            if not croniter.is_valid(value):
                raise ValidationError("Schedule was not parseable")
            attrs["schedule"] = value
        return attrs
示例#5
0
class ProjectAdminSerializer(ProjectMemberSerializer):
    name = serializers.CharField(max_length=200)
    slug = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
    team = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
    digestsMinDelay = serializers.IntegerField(min_value=60, max_value=3600)
    digestsMaxDelay = serializers.IntegerField(min_value=60, max_value=3600)
    subjectPrefix = serializers.CharField(max_length=200, allow_blank=True)
    subjectTemplate = serializers.CharField(max_length=200)
    securityToken = serializers.RegexField(
        r"^[-a-zA-Z0-9+/=\s]+$", max_length=255, allow_blank=True
    )
    securityTokenHeader = serializers.RegexField(
        r"^[a-zA-Z0-9_\-]+$", max_length=20, allow_blank=True
    )
    verifySSL = serializers.BooleanField(required=False)

    defaultEnvironment = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    dataScrubber = serializers.BooleanField(required=False)
    dataScrubberDefaults = serializers.BooleanField(required=False)
    sensitiveFields = ListField(child=serializers.CharField(), required=False)
    safeFields = ListField(child=serializers.CharField(), required=False)
    storeCrashReports = serializers.IntegerField(
        min_value=-1, max_value=STORE_CRASH_REPORTS_MAX, required=False, allow_null=True
    )
    relayPiiConfig = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    builtinSymbolSources = ListField(child=serializers.CharField(), required=False)
    symbolSources = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    scrubIPAddresses = serializers.BooleanField(required=False)
    groupingConfig = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    groupingEnhancements = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    fingerprintingRules = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    secondaryGroupingConfig = serializers.CharField(
        required=False, allow_blank=True, allow_null=True
    )
    secondaryGroupingExpiry = serializers.IntegerField(min_value=1, required=False, allow_null=True)
    scrapeJavaScript = serializers.BooleanField(required=False)
    allowedDomains = EmptyListField(child=OriginField(allow_blank=True), required=False)
    resolveAge = EmptyIntegerField(required=False, allow_null=True)
    platform = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    copy_from_project = serializers.IntegerField(required=False)
    dynamicSampling = DynamicSamplingSerializer(required=False)

    def validate(self, data):
        max_delay = (
            data["digestsMaxDelay"]
            if "digestsMaxDelay" in data
            else self.context["project"].get_option("digests:mail:maximum_delay")
        )
        min_delay = (
            data["digestsMinDelay"]
            if "digestsMinDelay" in data
            else self.context["project"].get_option("digests:mail:minimum_delay")
        )

        if min_delay is not None and max_delay and max_delay is not None and min_delay > max_delay:
            raise serializers.ValidationError(
                {"digestsMinDelay": "The minimum delay on digests must be lower than the maximum."}
            )

        return data

    def validate_allowedDomains(self, value):
        value = filter(bool, value)
        if len(value) == 0:
            raise serializers.ValidationError(
                "Empty value will block all requests, use * to accept from all domains"
            )
        return value

    def validate_slug(self, slug):
        if slug in RESERVED_PROJECT_SLUGS:
            raise serializers.ValidationError(f'The slug "{slug}" is reserved and not allowed.')
        project = self.context["project"]
        other = (
            Project.objects.filter(slug=slug, organization=project.organization)
            .exclude(id=project.id)
            .first()
        )
        if other is not None:
            raise serializers.ValidationError(
                "Another project (%s) is already using that slug" % other.name
            )
        return slug

    def validate_relayPiiConfig(self, value):
        organization = self.context["project"].organization
        return validate_pii_config_update(organization, value)

    def validate_builtinSymbolSources(self, value):
        if not value:
            return value

        from sentry import features

        organization = self.context["project"].organization
        request = self.context["request"]
        has_sources = features.has("organizations:symbol-sources", organization, actor=request.user)

        if not has_sources:
            raise serializers.ValidationError("Organization is not allowed to set symbol sources")

        return value

    def validate_symbolSources(self, sources_json):
        if not sources_json:
            return sources_json

        from sentry import features

        organization = self.context["project"].organization
        request = self.context["request"]

        try:
            # We should really only grab and parse if there are sources in sources_json whose
            # secrets are set to {"hidden-secret":true}
            orig_sources = parse_sources(
                self.context["project"].get_option("sentry:symbol_sources")
            )
            sources = parse_backfill_sources(sources_json.strip(), orig_sources)
        except InvalidSourcesError as e:
            raise serializers.ValidationError(str(e))

        sources_json = json.dumps(sources) if sources else ""

        # If no sources are added or modified, we're either only deleting sources or doing nothing.
        # This is always allowed.
        added_or_modified_sources = [s for s in sources if s not in orig_sources]
        if not added_or_modified_sources:
            return sources_json

        # Adding sources is only allowed if custom symbol sources are enabled.
        has_sources = features.has(
            "organizations:custom-symbol-sources", organization, actor=request.user
        )

        if not has_sources:
            raise serializers.ValidationError(
                "Organization is not allowed to set custom symbol sources"
            )

        has_multiple_appconnect = features.has(
            "organizations:app-store-connect-multiple", organization, actor=request.user
        )
        appconnect_sources = [s for s in sources if s.get("type") == "appStoreConnect"]
        if not has_multiple_appconnect and len(appconnect_sources) > 1:
            raise serializers.ValidationError(
                "Only one Apple App Store Connect application is allowed in this project"
            )

        return sources_json

    def validate_groupingEnhancements(self, value):
        if not value:
            return value

        try:
            Enhancements.from_config_string(value)
        except InvalidEnhancerConfig as e:
            raise serializers.ValidationError(str(e))

        return value

    def validate_secondaryGroupingExpiry(self, value):
        if not isinstance(value, (int, float)) or math.isnan(value):
            raise serializers.ValidationError(
                f"Grouping expiry must be a numerical value, a UNIX timestamp with second resolution, found {type(value)}"
            )
        now = time.time()
        if value < now:
            raise serializers.ValidationError(
                "Grouping expiry must be sometime within the next 90 days and not in the past. Perhaps you specified the timestamp not in seconds?"
            )

        max_expiry_date = now + (91 * 24 * 3600)
        if value > max_expiry_date:
            value = max_expiry_date

        return value

    def validate_fingerprintingRules(self, value):
        if not value:
            return value

        try:
            FingerprintingRules.from_config_string(value)
        except InvalidFingerprintingConfig as e:
            raise serializers.ValidationError(str(e))

        return value

    def validate_copy_from_project(self, other_project_id):
        try:
            other_project = Project.objects.filter(
                id=other_project_id, organization_id=self.context["project"].organization_id
            ).prefetch_related("teams")[0]
        except IndexError:
            raise serializers.ValidationError("Project to copy settings from not found.")

        request = self.context["request"]
        if not request.access.has_project_access(other_project):
            raise serializers.ValidationError(
                "Project settings cannot be copied from a project you do not have access to."
            )

        for project_team in other_project.projectteam_set.all():
            if not request.access.has_team_scope(project_team.team, "team:write"):
                raise serializers.ValidationError(
                    "Project settings cannot be copied from a project with a team you do not have write access to."
                )

        return other_project_id

    def validate_platform(self, value):
        if Project.is_valid_platform(value):
            return value
        raise serializers.ValidationError("Invalid platform")
示例#6
0
class OrganizationSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=64)
    slug = serializers.RegexField(r'^[a-z0-9_\-]+$', max_length=50)
    accountRateLimit = EmptyIntegerField(
        min_value=0,
        max_value=1000000,
        required=False,
        allow_null=True,
    )
    projectRateLimit = EmptyIntegerField(
        min_value=50,
        max_value=100,
        required=False,
        allow_null=True,
    )
    avatar = AvatarField(required=False, allow_null=True)
    avatarType = serializers.ChoiceField(
        choices=(
            ('upload', 'upload'),
            ('letter_avatar', 'letter_avatar'),
        ),
        required=False,
        allow_null=True,
    )

    openMembership = serializers.BooleanField(required=False)
    allowSharedIssues = serializers.BooleanField(required=False)
    enhancedPrivacy = serializers.BooleanField(required=False)
    dataScrubber = serializers.BooleanField(required=False)
    dataScrubberDefaults = serializers.BooleanField(required=False)
    sensitiveFields = ListField(child=serializers.CharField(), required=False)
    safeFields = ListField(child=serializers.CharField(), required=False)
    storeCrashReports = serializers.BooleanField(required=False)
    attachmentsRole = serializers.CharField(required=True)
    scrubIPAddresses = serializers.BooleanField(required=False)
    scrapeJavaScript = serializers.BooleanField(required=False)
    isEarlyAdopter = serializers.BooleanField(required=False)
    require2FA = serializers.BooleanField(required=False)
    trustedRelays = ListField(child=serializers.CharField(), required=False)

    @memoize
    def _has_legacy_rate_limits(self):
        org = self.context['organization']
        return OrganizationOption.objects.filter(
            organization=org,
            key__in=LEGACY_RATE_LIMIT_OPTIONS,
        ).exists()

    def _has_sso_enabled(self):
        org = self.context['organization']
        return AuthProvider.objects.filter(organization=org).exists()

    def validate_slug(self, value):
        # Historically, the only check just made sure there was more than 1
        # character for the slug, but since then, there are many slugs that
        # fit within this new imposed limit. We're not fixing existing, but
        # just preventing new bad values.
        if len(value) < 3:
            raise serializers.ValidationError(
                'This slug "%s" is too short. Minimum of 3 characters.' %
                (value, ))
        if value in RESERVED_ORGANIZATION_SLUGS:
            raise serializers.ValidationError(
                'This slug "%s" is reserved and not allowed.' % (value, ))
        qs = Organization.objects.filter(
            slug=value, ).exclude(id=self.context['organization'].id)
        if qs.exists():
            raise serializers.ValidationError(
                'The slug "%s" is already in use.' % (value, ))
        return value

    def validate_sensitiveFields(self, value):
        if value and not all(value):
            raise serializers.ValidationError('Empty values are not allowed.')
        return value

    def validate_safeFields(self, value):
        if value and not all(value):
            raise serializers.ValidationError('Empty values are not allowed.')
        return value

    def validate_attachmentsRole(self, value):
        try:
            roles.get(value)
        except KeyError:
            raise serializers.ValidationError('Invalid role')
        return value

    def validate_require2FA(self, value):
        user = self.context['user']
        has_2fa = Authenticator.objects.user_has_2fa(user)
        if value and not has_2fa:
            raise serializers.ValidationError(ERR_NO_2FA)

        if value and self._has_sso_enabled():
            raise serializers.ValidationError(ERR_SSO_ENABLED)
        return value

    def validate_trustedRelays(self, value):
        from sentry import features

        organization = self.context['organization']
        request = self.context["request"]
        has_relays = features.has('organizations:relay',
                                  organization,
                                  actor=request.user)
        if not has_relays:
            raise serializers.ValidationError(
                'Organization does not have the relay feature enabled')
        return value

    def validate_accountRateLimit(self, value):
        if not self._has_legacy_rate_limits:
            raise serializers.ValidationError(
                'The accountRateLimit option cannot be configured for this organization'
            )
        return value

    def validate_projectRateLimit(self, value):
        if not self._has_legacy_rate_limits:
            raise serializers.ValidationError(
                'The accountRateLimit option cannot be configured for this organization'
            )
        return value

    def validate(self, attrs):
        attrs = super(OrganizationSerializer, self).validate(attrs)
        if attrs.get('avatarType') == 'upload':
            has_existing_file = OrganizationAvatar.objects.filter(
                organization=self.context['organization'],
                file__isnull=False,
            ).exists()
            if not has_existing_file and not attrs.get('avatar'):
                raise serializers.ValidationError({
                    'avatarType':
                    'Cannot set avatarType to upload without avatar',
                })
        return attrs

    def save(self):
        org = self.context['organization']
        changed_data = {}

        for key, option, type_, default_value in ORG_OPTIONS:
            if key not in self.initial_data:
                continue
            try:
                option_inst = OrganizationOption.objects.get(organization=org,
                                                             key=option)
            except OrganizationOption.DoesNotExist:
                OrganizationOption.objects.set_value(
                    organization=org,
                    key=option,
                    value=type_(self.initial_data[key]),
                )

                if self.initial_data[key] != default_value:
                    changed_data[key] = u'to {}'.format(self.initial_data[key])
            else:
                option_inst.value = self.initial_data[key]
                # check if ORG_OPTIONS changed
                if option_inst.has_changed('value'):
                    old_val = option_inst.old_value('value')
                    changed_data[key] = u'from {} to {}'.format(
                        old_val, option_inst.value)
                option_inst.save()

        if 'openMembership' in self.initial_data:
            org.flags.allow_joinleave = self.initial_data['openMembership']
        if 'allowSharedIssues' in self.initial_data:
            org.flags.disable_shared_issues = not self.initial_data[
                'allowSharedIssues']
        if 'enhancedPrivacy' in self.initial_data:
            org.flags.enhanced_privacy = self.initial_data['enhancedPrivacy']
        if 'isEarlyAdopter' in self.initial_data:
            org.flags.early_adopter = self.initial_data['isEarlyAdopter']
        if 'require2FA' in self.initial_data:
            org.flags.require_2fa = self.initial_data['require2FA']
        if 'name' in self.initial_data:
            org.name = self.initial_data['name']
        if 'slug' in self.initial_data:
            org.slug = self.initial_data['slug']

        org_tracked_field = {
            'name': org.name,
            'slug': org.slug,
            'default_role': org.default_role,
            'flag_field': {
                'allow_joinleave': org.flags.allow_joinleave.is_set,
                'enhanced_privacy': org.flags.enhanced_privacy.is_set,
                'disable_shared_issues':
                org.flags.disable_shared_issues.is_set,
                'early_adopter': org.flags.early_adopter.is_set,
                'require_2fa': org.flags.require_2fa.is_set,
            }
        }

        # check if fields changed
        for f, v in six.iteritems(org_tracked_field):
            if f is not 'flag_field':
                if org.has_changed(f):
                    old_val = org.old_value(f)
                    changed_data[f] = u'from {} to {}'.format(old_val, v)
            else:
                # check if flag fields changed
                for f, v in six.iteritems(org_tracked_field['flag_field']):
                    if org.flag_has_changed(f):
                        changed_data[f] = u'to {}'.format(v)

        org.save()

        if 'avatar' in self.initial_data or 'avatarType' in self.initial_data:
            OrganizationAvatar.save_avatar(
                relation={'organization': org},
                type=self.initial_data.get('avatarType', 'upload'),
                avatar=self.initial_data.get('avatar'),
                filename=u'{}.png'.format(org.slug),
            )
        if 'require2FA' in self.initial_data and self.initial_data[
                'require2FA'] is True:
            org.handle_2fa_required(self.context['request'])
        return org, changed_data
示例#7
0
class ProjectAdminSerializer(ProjectMemberSerializer):
    name = serializers.CharField(max_length=200)
    slug = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
    team = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
    digestsMinDelay = serializers.IntegerField(min_value=60, max_value=3600)
    digestsMaxDelay = serializers.IntegerField(min_value=60, max_value=3600)
    subjectPrefix = serializers.CharField(max_length=200, allow_blank=True)
    subjectTemplate = serializers.CharField(max_length=200)
    securityToken = serializers.RegexField(r"^[-a-zA-Z0-9+/=\s]+$",
                                           max_length=255,
                                           allow_blank=True)
    securityTokenHeader = serializers.RegexField(r"^[a-zA-Z0-9_\-]+$",
                                                 max_length=20,
                                                 allow_blank=True)
    verifySSL = serializers.BooleanField(required=False)

    defaultEnvironment = serializers.CharField(required=False,
                                               allow_null=True,
                                               allow_blank=True)
    dataScrubber = serializers.BooleanField(required=False)
    dataScrubberDefaults = serializers.BooleanField(required=False)
    sensitiveFields = ListField(child=serializers.CharField(), required=False)
    safeFields = ListField(child=serializers.CharField(), required=False)
    storeCrashReports = serializers.IntegerField(min_value=-1,
                                                 max_value=20,
                                                 required=False,
                                                 allow_null=True)
    relayPiiConfig = serializers.CharField(required=False,
                                           allow_blank=True,
                                           allow_null=True)
    builtinSymbolSources = ListField(child=serializers.CharField(),
                                     required=False)
    symbolSources = serializers.CharField(required=False,
                                          allow_blank=True,
                                          allow_null=True)
    scrubIPAddresses = serializers.BooleanField(required=False)
    groupingConfig = serializers.CharField(required=False,
                                           allow_blank=True,
                                           allow_null=True)
    groupingEnhancements = serializers.CharField(required=False,
                                                 allow_blank=True,
                                                 allow_null=True)
    groupingEnhancementsBase = serializers.CharField(required=False,
                                                     allow_blank=True,
                                                     allow_null=True)
    fingerprintingRules = serializers.CharField(required=False,
                                                allow_blank=True,
                                                allow_null=True)
    scrapeJavaScript = serializers.BooleanField(required=False)
    allowedDomains = EmptyListField(child=OriginField(allow_blank=True),
                                    required=False)
    resolveAge = EmptyIntegerField(required=False, allow_null=True)
    platform = serializers.CharField(required=False,
                                     allow_null=True,
                                     allow_blank=True)
    copy_from_project = serializers.IntegerField(required=False)
    dynamicSampling = DynamicSamplingSerializer(required=False)

    def validate(self, data):
        max_delay = (
            data["digestsMaxDelay"] if "digestsMaxDelay" in data else
            self.context["project"].get_option("digests:mail:maximum_delay"))
        min_delay = (
            data["digestsMinDelay"] if "digestsMinDelay" in data else
            self.context["project"].get_option("digests:mail:minimum_delay"))

        if min_delay is not None and max_delay and max_delay is not None and min_delay > max_delay:
            raise serializers.ValidationError({
                "digestsMinDelay":
                "The minimum delay on digests must be lower than the maximum."
            })

        return data

    def validate_allowedDomains(self, value):
        value = filter(bool, value)
        if len(value) == 0:
            raise serializers.ValidationError(
                "Empty value will block all requests, use * to accept from all domains"
            )
        return value

    def validate_slug(self, slug):
        if slug in RESERVED_PROJECT_SLUGS:
            raise serializers.ValidationError(
                f'The slug "{slug}" is reserved and not allowed.')
        project = self.context["project"]
        other = (Project.objects.filter(
            slug=slug,
            organization=project.organization).exclude(id=project.id).first())
        if other is not None:
            raise serializers.ValidationError(
                "Another project (%s) is already using that slug" % other.name)
        return slug

    def validate_relayPiiConfig(self, value):
        organization = self.context["project"].organization
        return validate_pii_config_update(organization, value)

    def validate_builtinSymbolSources(self, value):
        if not value:
            return value

        from sentry import features

        organization = self.context["project"].organization
        request = self.context["request"]
        has_sources = features.has("organizations:symbol-sources",
                                   organization,
                                   actor=request.user)

        if not has_sources:
            raise serializers.ValidationError(
                "Organization is not allowed to set symbol sources")

        return value

    def validate_symbolSources(self, sources_json):
        if not sources_json:
            return sources_json

        from sentry import features

        organization = self.context["project"].organization
        request = self.context["request"]
        has_sources = features.has("organizations:custom-symbol-sources",
                                   organization,
                                   actor=request.user)

        if not has_sources:
            raise serializers.ValidationError(
                "Organization is not allowed to set custom symbol sources")

        try:
            sources = parse_sources(sources_json.strip())
            sources_json = json.dumps(sources) if sources else ""
        except InvalidSourcesError as e:
            raise serializers.ValidationError(str(e))

        return sources_json

    def validate_groupingEnhancements(self, value):
        if not value:
            return value

        try:
            Enhancements.from_config_string(value)
        except InvalidEnhancerConfig as e:
            raise serializers.ValidationError(str(e))

        return value

    def validate_fingerprintingRules(self, value):
        if not value:
            return value

        try:
            FingerprintingRules.from_config_string(value)
        except InvalidFingerprintingConfig as e:
            raise serializers.ValidationError(str(e))

        return value

    def validate_copy_from_project(self, other_project_id):
        try:
            other_project = Project.objects.filter(
                id=other_project_id,
                organization_id=self.context["project"].organization_id
            ).prefetch_related("teams")[0]
        except IndexError:
            raise serializers.ValidationError(
                "Project to copy settings from not found.")

        request = self.context["request"]
        if not request.access.has_project_access(other_project):
            raise serializers.ValidationError(
                "Project settings cannot be copied from a project you do not have access to."
            )

        for project_team in other_project.projectteam_set.all():
            if not request.access.has_team_scope(project_team.team,
                                                 "team:write"):
                raise serializers.ValidationError(
                    "Project settings cannot be copied from a project with a team you do not have write access to."
                )

        return other_project_id

    def validate_platform(self, value):
        if Project.is_valid_platform(value):
            return value
        raise serializers.ValidationError("Invalid platform")
示例#8
0
class OrganizationSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=64)
    slug = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
    accountRateLimit = EmptyIntegerField(min_value=0,
                                         max_value=1000000,
                                         required=False,
                                         allow_null=True)
    projectRateLimit = EmptyIntegerField(min_value=50,
                                         max_value=100,
                                         required=False,
                                         allow_null=True)
    avatar = AvatarField(required=False, allow_null=True)
    avatarType = serializers.ChoiceField(
        choices=(("upload", "upload"), ("letter_avatar", "letter_avatar")),
        required=False,
        allow_null=True,
    )

    openMembership = serializers.BooleanField(required=False)
    allowSharedIssues = serializers.BooleanField(required=False)
    enhancedPrivacy = serializers.BooleanField(required=False)
    dataScrubber = serializers.BooleanField(required=False)
    dataScrubberDefaults = serializers.BooleanField(required=False)
    sensitiveFields = ListField(child=serializers.CharField(), required=False)
    safeFields = ListField(child=serializers.CharField(), required=False)
    storeCrashReports = serializers.IntegerField(min_value=-1,
                                                 max_value=20,
                                                 required=False)
    attachmentsRole = serializers.CharField(required=True)
    debugFilesRole = serializers.CharField(required=True)
    eventsMemberAdmin = serializers.BooleanField(required=False)
    alertsMemberWrite = serializers.BooleanField(required=False)
    scrubIPAddresses = serializers.BooleanField(required=False)
    scrapeJavaScript = serializers.BooleanField(required=False)
    isEarlyAdopter = serializers.BooleanField(required=False)
    require2FA = serializers.BooleanField(required=False)
    requireEmailVerification = serializers.BooleanField(required=False)
    trustedRelays = ListField(child=TrustedRelaySerializer(), required=False)
    allowJoinRequests = serializers.BooleanField(required=False)
    relayPiiConfig = serializers.CharField(required=False,
                                           allow_blank=True,
                                           allow_null=True)
    apdexThreshold = serializers.IntegerField(min_value=1, required=False)

    @memoize
    def _has_legacy_rate_limits(self):
        org = self.context["organization"]
        return OrganizationOption.objects.filter(
            organization=org, key__in=LEGACY_RATE_LIMIT_OPTIONS).exists()

    def _has_sso_enabled(self):
        org = self.context["organization"]
        return AuthProvider.objects.filter(organization=org).exists()

    def validate_slug(self, value):
        # Historically, the only check just made sure there was more than 1
        # character for the slug, but since then, there are many slugs that
        # fit within this new imposed limit. We're not fixing existing, but
        # just preventing new bad values.
        if len(value) < 3:
            raise serializers.ValidationError(
                f'This slug "{value}" is too short. Minimum of 3 characters.')
        if value in RESERVED_ORGANIZATION_SLUGS:
            raise serializers.ValidationError(
                f'This slug "{value}" is reserved and not allowed.')
        qs = Organization.objects.filter(slug=value).exclude(
            id=self.context["organization"].id)
        if qs.exists():
            raise serializers.ValidationError(
                f'The slug "{value}" is already in use.')
        return value

    def validate_relayPiiConfig(self, value):
        organization = self.context["organization"]
        return validate_pii_config_update(organization, value)

    def validate_sensitiveFields(self, value):
        if value and not all(value):
            raise serializers.ValidationError("Empty values are not allowed.")
        return value

    def validate_safeFields(self, value):
        if value and not all(value):
            raise serializers.ValidationError("Empty values are not allowed.")
        return value

    def validate_attachmentsRole(self, value):
        try:
            roles.get(value)
        except KeyError:
            raise serializers.ValidationError("Invalid role")
        return value

    def validate_debugFilesRole(self, value):
        try:
            roles.get(value)
        except KeyError:
            raise serializers.ValidationError("Invalid role")
        return value

    def validate_require2FA(self, value):
        user = self.context["user"]
        has_2fa = Authenticator.objects.user_has_2fa(user)
        if value and not has_2fa:
            raise serializers.ValidationError(ERR_NO_2FA)

        if value and self._has_sso_enabled():
            raise serializers.ValidationError(ERR_SSO_ENABLED)
        return value

    def validate_requireEmailVerification(self, value):
        user = self.context["user"]
        has_verified = UserEmail.get_primary_email(user).is_verified
        if value and not has_verified:
            raise serializers.ValidationError(ERR_EMAIL_VERIFICATION)
        return value

    def validate_trustedRelays(self, value):
        from sentry import features

        organization = self.context["organization"]
        request = self.context["request"]
        has_relays = features.has("organizations:relay",
                                  organization,
                                  actor=request.user)
        if not has_relays:
            raise serializers.ValidationError(
                "Organization does not have the relay feature enabled")

        # make sure we don't have multiple instances of one public key
        public_keys = set()
        if value is not None:
            for key_info in value:
                key = key_info.get("public_key")
                if key in public_keys:
                    raise serializers.ValidationError(
                        f"Duplicated key in Trusted Relays: '{key}'")
                public_keys.add(key)

        return value

    def validate_accountRateLimit(self, value):
        if not self._has_legacy_rate_limits:
            raise serializers.ValidationError(
                "The accountRateLimit option cannot be configured for this organization"
            )
        return value

    def validate_projectRateLimit(self, value):
        if not self._has_legacy_rate_limits:
            raise serializers.ValidationError(
                "The accountRateLimit option cannot be configured for this organization"
            )
        return value

    def validate(self, attrs):
        attrs = super().validate(attrs)
        if attrs.get("avatarType") == "upload":
            has_existing_file = OrganizationAvatar.objects.filter(
                organization=self.context["organization"],
                file__isnull=False).exists()
            if not has_existing_file and not attrs.get("avatar"):
                raise serializers.ValidationError({
                    "avatarType":
                    "Cannot set avatarType to upload without avatar"
                })
        return attrs

    def save_trusted_relays(self, incoming, changed_data, organization):
        timestamp_now = datetime.utcnow().replace(tzinfo=UTC).isoformat()
        option_key = "sentry:trusted-relays"
        try:
            # get what we already have
            existing = OrganizationOption.objects.get(
                organization=organization, key=option_key)

            key_dict = {val.get("public_key"): val for val in existing.value}
            original_number_of_keys = len(existing.value)
        except OrganizationOption.DoesNotExist:
            key_dict = {}  # we don't have anything set
            original_number_of_keys = 0
            existing = None

        modified = False
        for option in incoming:
            public_key = option.get("public_key")
            existing_info = key_dict.get(public_key, {})

            option["created"] = existing_info.get("created", timestamp_now)
            option["last_modified"] = existing_info.get("last_modified")

            # check if we modified the current public_key info and update last_modified if we did
            if (not existing_info
                    or existing_info.get("name") != option.get("name")
                    or existing_info.get("description") !=
                    option.get("description")):
                option["last_modified"] = timestamp_now
                modified = True

        # check to see if the only modifications were some deletions (which are not captured in the loop above)
        if len(incoming) != original_number_of_keys:
            modified = True

        if modified:
            # we have some modifications create a log message
            if existing is not None:
                # generate an update log message
                changed_data[
                    "trustedRelays"] = f"from {existing} to {incoming}"
                existing.value = incoming
                existing.save()
            else:
                # first time we set trusted relays, generate a create log message
                changed_data["trustedRelays"] = f"to {incoming}"
                OrganizationOption.objects.set_value(organization=organization,
                                                     key=option_key,
                                                     value=incoming)

        return incoming

    def save(self):
        from sentry import features

        org = self.context["organization"]
        changed_data = {}
        if not hasattr(org, "__data"):
            update_tracked_data(org)

        for key, option, type_, default_value in ORG_OPTIONS:
            if key not in self.initial_data:
                continue
            try:
                option_inst = OrganizationOption.objects.get(organization=org,
                                                             key=option)
                update_tracked_data(option_inst)
            except OrganizationOption.DoesNotExist:
                OrganizationOption.objects.set_value(
                    organization=org,
                    key=option,
                    value=type_(self.initial_data[key]))

                if self.initial_data[key] != default_value:
                    changed_data[key] = f"to {self.initial_data[key]}"
            else:
                option_inst.value = self.initial_data[key]
                # check if ORG_OPTIONS changed
                if has_changed(option_inst, "value"):
                    old_val = old_value(option_inst, "value")
                    changed_data[
                        key] = f"from {old_val} to {option_inst.value}"
                option_inst.save()

        trusted_realy_info = self.validated_data.get("trustedRelays")
        if trusted_realy_info is not None:
            self.save_trusted_relays(trusted_realy_info, changed_data, org)

        if "openMembership" in self.initial_data:
            org.flags.allow_joinleave = self.initial_data["openMembership"]
        if "allowSharedIssues" in self.initial_data:
            org.flags.disable_shared_issues = not self.initial_data[
                "allowSharedIssues"]
        if "enhancedPrivacy" in self.initial_data:
            org.flags.enhanced_privacy = self.initial_data["enhancedPrivacy"]
        if "isEarlyAdopter" in self.initial_data:
            org.flags.early_adopter = self.initial_data["isEarlyAdopter"]
        if "require2FA" in self.initial_data:
            org.flags.require_2fa = self.initial_data["require2FA"]
        if (features.has("organizations:required-email-verification", org)
                and "requireEmailVerification" in self.initial_data):
            org.flags.require_email_verification = self.initial_data[
                "requireEmailVerification"]
        if "name" in self.initial_data:
            org.name = self.initial_data["name"]
        if "slug" in self.initial_data:
            org.slug = self.initial_data["slug"]

        org_tracked_field = {
            "name": org.name,
            "slug": org.slug,
            "default_role": org.default_role,
            "flag_field": {
                "allow_joinleave": org.flags.allow_joinleave.is_set,
                "enhanced_privacy": org.flags.enhanced_privacy.is_set,
                "disable_shared_issues":
                org.flags.disable_shared_issues.is_set,
                "early_adopter": org.flags.early_adopter.is_set,
                "require_2fa": org.flags.require_2fa.is_set,
            },
        }

        # check if fields changed
        for f, v in org_tracked_field.items():
            if f != "flag_field":
                if has_changed(org, f):
                    old_val = old_value(org, f)
                    changed_data[f] = f"from {old_val} to {v}"
            else:
                # check if flag fields changed
                for f, v in org_tracked_field["flag_field"].items():
                    if flag_has_changed(org, f):
                        changed_data[f] = f"to {v}"

        org.save()

        if "avatar" in self.initial_data or "avatarType" in self.initial_data:
            OrganizationAvatar.save_avatar(
                relation={"organization": org},
                type=self.initial_data.get("avatarType", "upload"),
                avatar=self.initial_data.get("avatar"),
                filename=f"{org.slug}.png",
            )
        if self.initial_data.get("require2FA") is True:
            org.handle_2fa_required(self.context["request"])
        if (features.has("organizations:required-email-verification", org)
                and self.initial_data.get("requireEmailVerification") is True):
            org.handle_email_verification_required(self.context["request"])
        return org, changed_data
示例#9
0
class DiscoverQuerySerializer(serializers.Serializer):
    projects = ListField(child=serializers.IntegerField(),
                         required=True,
                         allow_null=False)
    start = serializers.CharField(required=False, allow_null=True)
    end = serializers.CharField(required=False, allow_null=True)
    range = serializers.CharField(required=False, allow_null=True)
    statsPeriod = serializers.CharField(required=False, allow_null=True)
    statsPeriodStart = serializers.CharField(required=False, allow_null=True)
    statsPeriodEnd = serializers.CharField(required=False, allow_null=True)
    fields = ListField(child=serializers.CharField(),
                       required=False,
                       default=[])
    conditionFields = ListField(child=ListField(),
                                required=False,
                                allow_null=True)
    limit = EmptyIntegerField(min_value=0,
                              max_value=10000,
                              required=False,
                              allow_null=True)
    rollup = EmptyIntegerField(required=False, allow_null=True)
    orderby = serializers.CharField(required=False,
                                    default="",
                                    allow_blank=True)
    conditions = ListField(child=ListField(), required=False, allow_null=True)
    aggregations = ListField(child=ListField(), required=False, default=[])
    groupby = ListField(child=serializers.CharField(),
                        required=False,
                        allow_null=True)
    turbo = serializers.BooleanField(required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        data = kwargs["data"]

        fields = data.get("fields") or []

        match = next(
            (self.get_array_field(field).group(1)
             for field in fields if self.get_array_field(field) is not None),
            None,
        )
        self.arrayjoin = match if match else None

    def validate(self, data):
        data["arrayjoin"] = self.arrayjoin

        # prevent conflicting date ranges from being supplied
        date_fields = ["start", "statsPeriod", "range", "statsPeriodStart"]
        date_fields_provided = len(
            [data.get(f) for f in date_fields if data.get(f) is not None])
        if date_fields_provided == 0:
            raise serializers.ValidationError("You must specify a date filter")
        elif date_fields_provided > 1:
            raise serializers.ValidationError(
                "Conflicting date filters supplied")

        if not data.get("fields") and not data.get("aggregations"):
            raise serializers.ValidationError(
                "Specify at least one field or aggregation")

        try:
            start, end = get_date_range_from_params(
                {
                    "start": data.get("start"),
                    "end": data.get("end"),
                    "statsPeriod": data.get("statsPeriod")
                    or data.get("range"),
                    "statsPeriodStart": data.get("statsPeriodStart"),
                    "statsPeriodEnd": data.get("statsPeriodEnd"),
                },
                optional=True,
            )
        except InvalidParams as e:
            raise serializers.ValidationError(str(e))

        if start is None or end is None:
            raise serializers.ValidationError(
                "Either start and end dates or range is required")

        data["start"] = start
        data["end"] = end

        return data

    def validate_conditions(self, value):
        # Handle error (exception_stacks), stack(exception_frames)
        return [self.get_condition(condition) for condition in value]

    def validate_aggregations(self, value):
        valid_functions = {"count()", "uniq", "avg", "sum"}
        requested_functions = {agg[0] for agg in value}

        if not requested_functions.issubset(valid_functions):
            invalid_functions = ", ".join(requested_functions -
                                          valid_functions)

            raise serializers.ValidationError(
                f"Invalid aggregate function - {invalid_functions}")

        return value

    def get_array_field(self, field):
        pattern = r"^(error|stack)\..+"
        term = re.search(pattern, field)
        if term and SENTRY_SNUBA_MAP.get(field):
            return term
        return None

    def get_condition(self, condition):
        array_field = self.get_array_field(condition[0])
        has_equality_operator = condition[1] in ("=", "!=")

        # Cast boolean values to 1 / 0
        if isinstance(condition[2], bool):
            condition[2] = int(condition[2])

        # Strip double quotes on strings
        if isinstance(condition[2], str):
            match = re.search(r'^"(.*)"$', condition[2])
            if match:
                condition[2] = match.group(1)

        # Apply has function to any array field if it's = / != and not part of arrayjoin
        if array_field and has_equality_operator and (array_field.group(1) !=
                                                      self.arrayjoin):
            value = condition[2]

            if isinstance(value, str):
                value = f"'{value}'"

            bool_value = 1 if condition[1] == "=" else 0

            return [["has", [array_field.group(0), value]], "=", bool_value]

        return condition
示例#10
0
class RateLimitSerializer(serializers.Serializer):
    count = EmptyIntegerField(min_value=0, required=False, allow_null=True)
    window = EmptyIntegerField(min_value=0,
                               max_value=60 * 60 * 24,
                               required=False,
                               allow_null=True)
示例#11
0
class OrganizationSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=64)
    slug = serializers.RegexField(r"^[a-z0-9_\-]+$", max_length=50)
    accountRateLimit = EmptyIntegerField(
        min_value=0, max_value=1000000, required=False, allow_null=True
    )
    projectRateLimit = EmptyIntegerField(
        min_value=50, max_value=100, required=False, allow_null=True
    )
    avatar = AvatarField(required=False, allow_null=True)
    avatarType = serializers.ChoiceField(
        choices=(("upload", "upload"), ("letter_avatar", "letter_avatar")),
        required=False,
        allow_null=True,
    )

    openMembership = serializers.BooleanField(required=False)
    allowSharedIssues = serializers.BooleanField(required=False)
    enhancedPrivacy = serializers.BooleanField(required=False)
    dataScrubber = serializers.BooleanField(required=False)
    dataScrubberDefaults = serializers.BooleanField(required=False)
    sensitiveFields = ListField(child=serializers.CharField(), required=False)
    safeFields = ListField(child=serializers.CharField(), required=False)
    storeCrashReports = serializers.IntegerField(min_value=-1, max_value=20, required=False)
    attachmentsRole = serializers.CharField(required=True)
    scrubIPAddresses = serializers.BooleanField(required=False)
    scrapeJavaScript = serializers.BooleanField(required=False)
    isEarlyAdopter = serializers.BooleanField(required=False)
    require2FA = serializers.BooleanField(required=False)
    trustedRelays = ListField(child=serializers.CharField(), required=False)
    allowJoinRequests = serializers.BooleanField(required=False)

    @memoize
    def _has_legacy_rate_limits(self):
        org = self.context["organization"]
        return OrganizationOption.objects.filter(
            organization=org, key__in=LEGACY_RATE_LIMIT_OPTIONS
        ).exists()

    def _has_sso_enabled(self):
        org = self.context["organization"]
        return AuthProvider.objects.filter(organization=org).exists()

    def validate_slug(self, value):
        # Historically, the only check just made sure there was more than 1
        # character for the slug, but since then, there are many slugs that
        # fit within this new imposed limit. We're not fixing existing, but
        # just preventing new bad values.
        if len(value) < 3:
            raise serializers.ValidationError(
                'This slug "%s" is too short. Minimum of 3 characters.' % (value,)
            )
        if value in RESERVED_ORGANIZATION_SLUGS:
            raise serializers.ValidationError(
                'This slug "%s" is reserved and not allowed.' % (value,)
            )
        qs = Organization.objects.filter(slug=value).exclude(id=self.context["organization"].id)
        if qs.exists():
            raise serializers.ValidationError('The slug "%s" is already in use.' % (value,))
        return value

    def validate_sensitiveFields(self, value):
        if value and not all(value):
            raise serializers.ValidationError("Empty values are not allowed.")
        return value

    def validate_safeFields(self, value):
        if value and not all(value):
            raise serializers.ValidationError("Empty values are not allowed.")
        return value

    def validate_attachmentsRole(self, value):
        try:
            roles.get(value)
        except KeyError:
            raise serializers.ValidationError("Invalid role")
        return value

    def validate_require2FA(self, value):
        user = self.context["user"]
        has_2fa = Authenticator.objects.user_has_2fa(user)
        if value and not has_2fa:
            raise serializers.ValidationError(ERR_NO_2FA)

        if value and self._has_sso_enabled():
            raise serializers.ValidationError(ERR_SSO_ENABLED)
        return value

    def validate_trustedRelays(self, value):
        from sentry import features

        organization = self.context["organization"]
        request = self.context["request"]
        has_relays = features.has("organizations:relay", organization, actor=request.user)
        if not has_relays:
            raise serializers.ValidationError(
                "Organization does not have the relay feature enabled"
            )
        return value

    def validate_accountRateLimit(self, value):
        if not self._has_legacy_rate_limits:
            raise serializers.ValidationError(
                "The accountRateLimit option cannot be configured for this organization"
            )
        return value

    def validate_projectRateLimit(self, value):
        if not self._has_legacy_rate_limits:
            raise serializers.ValidationError(
                "The accountRateLimit option cannot be configured for this organization"
            )
        return value

    def validate(self, attrs):
        attrs = super(OrganizationSerializer, self).validate(attrs)
        if attrs.get("avatarType") == "upload":
            has_existing_file = OrganizationAvatar.objects.filter(
                organization=self.context["organization"], file__isnull=False
            ).exists()
            if not has_existing_file and not attrs.get("avatar"):
                raise serializers.ValidationError(
                    {"avatarType": "Cannot set avatarType to upload without avatar"}
                )
        return attrs

    def save(self):
        org = self.context["organization"]
        changed_data = {}

        for key, option, type_, default_value in ORG_OPTIONS:
            if key not in self.initial_data:
                continue
            try:
                option_inst = OrganizationOption.objects.get(organization=org, key=option)
            except OrganizationOption.DoesNotExist:
                OrganizationOption.objects.set_value(
                    organization=org, key=option, value=type_(self.initial_data[key])
                )

                if self.initial_data[key] != default_value:
                    changed_data[key] = u"to {}".format(self.initial_data[key])
            else:
                option_inst.value = self.initial_data[key]
                # check if ORG_OPTIONS changed
                if option_inst.has_changed("value"):
                    old_val = option_inst.old_value("value")
                    changed_data[key] = u"from {} to {}".format(old_val, option_inst.value)
                option_inst.save()

        if "openMembership" in self.initial_data:
            org.flags.allow_joinleave = self.initial_data["openMembership"]
        if "allowSharedIssues" in self.initial_data:
            org.flags.disable_shared_issues = not self.initial_data["allowSharedIssues"]
        if "enhancedPrivacy" in self.initial_data:
            org.flags.enhanced_privacy = self.initial_data["enhancedPrivacy"]
        if "isEarlyAdopter" in self.initial_data:
            org.flags.early_adopter = self.initial_data["isEarlyAdopter"]
        if "require2FA" in self.initial_data:
            org.flags.require_2fa = self.initial_data["require2FA"]
        if "name" in self.initial_data:
            org.name = self.initial_data["name"]
        if "slug" in self.initial_data:
            org.slug = self.initial_data["slug"]

        org_tracked_field = {
            "name": org.name,
            "slug": org.slug,
            "default_role": org.default_role,
            "flag_field": {
                "allow_joinleave": org.flags.allow_joinleave.is_set,
                "enhanced_privacy": org.flags.enhanced_privacy.is_set,
                "disable_shared_issues": org.flags.disable_shared_issues.is_set,
                "early_adopter": org.flags.early_adopter.is_set,
                "require_2fa": org.flags.require_2fa.is_set,
            },
        }

        # check if fields changed
        for f, v in six.iteritems(org_tracked_field):
            if f != "flag_field":
                if org.has_changed(f):
                    old_val = org.old_value(f)
                    changed_data[f] = u"from {} to {}".format(old_val, v)
            else:
                # check if flag fields changed
                for f, v in six.iteritems(org_tracked_field["flag_field"]):
                    if org.flag_has_changed(f):
                        changed_data[f] = u"to {}".format(v)

        org.save()

        if "avatar" in self.initial_data or "avatarType" in self.initial_data:
            OrganizationAvatar.save_avatar(
                relation={"organization": org},
                type=self.initial_data.get("avatarType", "upload"),
                avatar=self.initial_data.get("avatar"),
                filename=u"{}.png".format(org.slug),
            )
        if "require2FA" in self.initial_data and self.initial_data["require2FA"] is True:
            org.handle_2fa_required(self.context["request"])
        return org, changed_data
示例#12
0
class DiscoverQuerySerializer(serializers.Serializer):
    projects = ListField(
        child=serializers.IntegerField(),
        required=True,
        allow_null=False,
    )
    start = serializers.CharField(required=False, allow_null=True)
    end = serializers.CharField(required=False, allow_null=True)
    range = serializers.CharField(required=False, allow_null=True)
    statsPeriod = serializers.CharField(required=False, allow_null=True)
    statsPeriodStart = serializers.CharField(required=False, allow_null=True)
    statsPeriodEnd = serializers.CharField(required=False, allow_null=True)
    fields = ListField(
        child=serializers.CharField(),
        required=False,
        default=[],
    )
    conditionFields = ListField(
        child=ListField(),
        required=False,
        allow_null=True,
    )
    limit = EmptyIntegerField(min_value=0, max_value=10000, required=False, allow_null=True)
    rollup = EmptyIntegerField(required=False, allow_null=True)
    orderby = serializers.CharField(required=False, default="", allow_blank=True)
    conditions = ListField(
        child=ListField(),
        required=False,
        allow_null=True,
    )
    aggregations = ListField(
        child=ListField(),
        required=False,
        default=[]
    )
    groupby = ListField(
        child=serializers.CharField(),
        required=False,
        allow_null=True,
    )
    turbo = serializers.BooleanField(required=False)

    def __init__(self, *args, **kwargs):
        super(DiscoverQuerySerializer, self).__init__(*args, **kwargs)

        data = kwargs['data']

        fields = data.get('fields') or []

        match = next(
            (
                self.get_array_field(field).group(1)
                for field
                in fields
                if self.get_array_field(field) is not None
            ),
            None
        )
        self.arrayjoin = match if match else None

    def validate(self, data):
        data['arrayjoin'] = self.arrayjoin

        # prevent conflicting date ranges from being supplied
        date_fields = ['start', 'statsPeriod', 'range', 'statsPeriodStart']
        date_fields_provided = len([data.get(f) for f in date_fields if data.get(f) is not None])
        if date_fields_provided == 0:
            raise serializers.ValidationError('You must specify a date filter')
        elif date_fields_provided > 1:
            raise serializers.ValidationError('Conflicting date filters supplied')

        if not data.get('fields') and not data.get('aggregations'):
            raise serializers.ValidationError('Specify at least one field or aggregation')

        try:
            start, end = get_date_range_from_params({
                'start': data.get('start'),
                'end': data.get('end'),
                'statsPeriod': data.get('statsPeriod') or data.get('range'),
                'statsPeriodStart': data.get('statsPeriodStart'),
                'statsPeriodEnd': data.get('statsPeriodEnd'),
            }, optional=True)
        except InvalidParams as exc:
            raise serializers.ValidationError(exc.message)

        if start is None or end is None:
            raise serializers.ValidationError('Either start and end dates or range is required')

        data['start'] = start
        data['end'] = end

        return data

    def validate_conditions(self, value):
        # Handle error (exception_stacks), stack(exception_frames)
        return [self.get_condition(condition) for condition in value]

    def validate_aggregations(self, value):
        valid_functions = set(['count()', 'uniq', 'avg'])
        requested_functions = set(agg[0] for agg in value)

        if not requested_functions.issubset(valid_functions):
            invalid_functions = ', '.join((requested_functions - valid_functions))

            raise serializers.ValidationError(
                u'Invalid aggregate function - {}'.format(invalid_functions)
            )

        return value

    def get_array_field(self, field):
        pattern = r"^(error|stack)\..+"
        term = re.search(pattern, field)
        if term and SENTRY_SNUBA_MAP.get(field):
            return term
        return None

    def get_condition(self, condition):
        array_field = self.get_array_field(condition[0])
        has_equality_operator = condition[1] in ('=', '!=')

        # Cast boolean values to 1 / 0
        if isinstance(condition[2], bool):
            condition[2] = int(condition[2])

        # Strip double quotes on strings
        if isinstance(condition[2], six.string_types):
            match = re.search(r'^"(.*)"$', condition[2])
            if match:
                condition[2] = match.group(1)

        # Apply has function to any array field if it's = / != and not part of arrayjoin
        if array_field and has_equality_operator and (array_field.group(1) != self.arrayjoin):
            value = condition[2]

            if (isinstance(value, six.string_types)):
                value = u"'{}'".format(value)

            bool_value = 1 if condition[1] == '=' else 0

            return [['has', [array_field.group(0), value]], '=', bool_value]

        return condition