class CommitSerializer(serializers.Serializer): id = serializers.CharField(max_length=64) repository = serializers.CharField( max_length=64, required=False, allow_null=True, allow_blank=True, ) message = serializers.CharField(required=False, allow_null=True, allow_blank=True) author_name = serializers.CharField( max_length=128, required=False, allow_null=True, allow_blank=True, ) author_email = serializers.EmailField( max_length=75, required=False, allow_null=True, allow_blank=True, ) timestamp = serializers.DateTimeField(required=False, allow_null=True) patch_set = ListField( child=CommitPatchSetSerializer(required=False), required=False, allow_null=True, )
class ServiceHookValidator(serializers.Serializer): url = serializers.URLField(required=True) events = ListField(child=serializers.CharField(max_length=255), required=False) version = serializers.ChoiceField(choices=((0, "0"), ), required=False, default=0) isActive = serializers.BooleanField(required=False, default=True) def validate_events(self, value): if value: for event in value: if event not in SERVICE_HOOK_EVENTS: raise serializers.ValidationError( f"Invalid event name: {event}") return value
class ServiceHookValidator(serializers.Serializer): url = serializers.URLField(required=True) events = ListField( child=serializers.CharField(max_length=255), required=True, ) version = serializers.ChoiceField(choices=((0, '0'), ), required=False, default=0) def validate_events(self, attrs, source): value = attrs[source] if value: for event in value: if event not in SERVICE_HOOK_EVENTS: raise serializers.ValidationError( 'Invalid event name: {}'.format(event)) return attrs
class CommentSerializer(serializers.Serializer, MentionsMixin): comment = serializers.CharField(required=True) mentions = ListField(child=ActorField(), required=False) external_id = serializers.CharField(allow_null=True, required=False)
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")
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")
class RuleSerializer(serializers.Serializer): name = serializers.CharField(max_length=64) environment = serializers.CharField(max_length=64, required=False, allow_null=True) actionMatch = serializers.ChoiceField(choices=(("all", "all"), ("any", "any"), ("none", "none"))) filterMatch = serializers.ChoiceField(choices=(("all", "all"), ("any", "any"), ("none", "none")), required=False) actions = ListField(child=RuleNodeField(type="action/event"), required=False) conditions = ListField(child=RuleNodeField(type="condition/event"), required=False) filters = ListField(child=RuleNodeField(type="filter/event"), required=False) frequency = serializers.IntegerField(min_value=5, max_value=60 * 24 * 30) owner = ActorField(required=False, allow_null=True) def validate_environment(self, environment): if environment is None: return environment try: environment = Environment.get_for_organization_id( self.context["project"].organization_id, environment).id except Environment.DoesNotExist: raise serializers.ValidationError( "This environment has not been created.") return environment def validate(self, attrs): # XXX(meredith): For rules that have the Slack integration as an action # we need to check if the channel_id needs to be looked up via an async task. # If the "pending_save" attribute is set we want to bubble that up to the # project_rule(_details) endpoints by setting it on attrs actions = attrs.get("actions", tuple()) for action in actions: # XXX(colleen): For ticket rules we need to ensure the user has # at least done minimal configuration if action["id"] in TICKET_ACTIONS: if not action.get("dynamic_form_fields"): raise serializers.ValidationError( {"actions": "Must configure issue link settings."}) # remove this attribute because we don't want it to be saved in the rule if action.pop("pending_save", None): attrs["pending_save"] = True break # ensure that if filters are passed in that a filterMatch is also supplied filters = attrs.get("filters") if filters: filter_match = attrs.get("filterMatch") if not filter_match: raise serializers.ValidationError({ "filterMatch": "Must select a filter match (all, any, none) if filters are supplied." }) # ensure that if a user has alert-filters enabled, they do not use old conditions project = self.context["project"] conditions = attrs.get("conditions", tuple()) project_has_filters = features.has("projects:alert-filters", project) if project_has_filters: old_conditions = [ condition for condition in conditions if condition["id"] in MIGRATED_CONDITIONS ] if old_conditions: raise serializers.ValidationError({ "conditions": "Conditions evaluating an event attribute, tag, or level are outdated please use an appropriate filter instead." }) # ensure that if a user has alert-filters enabled, they do not use a 'none' match on conditions if project_has_filters and attrs.get("actionMatch") == "none": raise serializers.ValidationError({ "conditions": "The 'none' match on conditions is outdated and no longer supported." }) return attrs def save(self, rule): rule.project = self.context["project"] if "environment" in self.validated_data: environment = self.validated_data["environment"] rule.environment_id = int( environment) if environment else environment if self.validated_data.get("name"): rule.label = self.validated_data["name"] if self.validated_data.get("actionMatch"): rule.data["action_match"] = self.validated_data["actionMatch"] if self.validated_data.get("filterMatch"): rule.data["filter_match"] = self.validated_data["filterMatch"] if self.validated_data.get("actions") is not None: rule.data["actions"] = self.validated_data["actions"] if self.validated_data.get("conditions") is not None: rule.data["conditions"] = self.validated_data["conditions"] if self.validated_data.get("frequency"): rule.data["frequency"] = self.validated_data["frequency"] if self.validated_data.get("owner"): rule.owner = self.validated_data["owner"].resolve_to_actor() rule.save() return rule