Example #1
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")
Example #2
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")