예제 #1
0
 def get(self, request: Request, project) -> Response:
     context = serialize(
         [plugin for plugin in plugins.configurable_for_project(project, version=None)],
         request.user,
         PluginSerializer(project),
     )
     return Response(context)
    def plugin_issues(self, request, group, plugin_issues, **kwargs):
        if not self.is_configured(request=request, project=group.project):
            return plugin_issues
        prefix = self.get_conf_key()
        issue_id = GroupMeta.objects.get_value(group, '%s:tid' % prefix, None)
        item = {
            'slug': self.slug,
            'allowed_actions': self.allowed_actions,
            # TODO(dcramer): remove in Sentry 8.22+
            'title': self.get_title(),
            'name': self.get_title(),
            'shortName': self.get_short_title(),
        }
        if issue_id:
            item['issue'] = {
                'issue_id': issue_id,
                'url': self.get_issue_url(group=group, issue_id=issue_id),
                'label': self.get_issue_label(group=group, issue_id=issue_id),
            }

        item.update(
            PluginSerializer(group.project).serialize(self, None,
                                                      request.user))
        plugin_issues.append(item)
        return plugin_issues
예제 #3
0
    def get(self, request, organization):
        all_plugins = dict([(p.slug, p) for p in plugins.all()])

        if 'plugins' in request.GET:
            if request.GET.get('plugins') == '_all':
                return Response(
                    serialize([p for p in plugins.all()], request.user,
                              PluginSerializer()))

            desired_plugins = set(request.GET.getlist('plugins'))
        else:
            desired_plugins = set(all_plugins.keys())

        # Ignore plugins that are not available to this Sentry install.
        desired_plugins = desired_plugins & set(all_plugins.keys())

        # Each tuple represents an enabled Plugin (of only the ones we care
        # about) and its corresponding Project.
        enabled_plugins = ProjectOption.objects.filter(
            key__in=['%s:enabled' % slug for slug in desired_plugins],
            project__organization=organization,
        ).select_related('project')

        resources = []

        for project_option in enabled_plugins:
            resources.append(
                serialize(
                    all_plugins[project_option.key.split(':')[0]],
                    request.user,
                    OrganizationPluginSerializer(project_option.project),
                ))

        return Response(resources)
예제 #4
0
 def _get_context_plugins(self, request, group):
     project = group.project
     return serialize([
         plugin for plugin in plugins.for_project(project, version=None)
         if plugin.has_project_conf()
         and hasattr(plugin, 'get_custom_contexts')
         and plugin.get_custom_contexts()
     ], request.user, PluginSerializer(project))
예제 #5
0
    def get(self, request, project, plugin_id):
        plugin = self._get_plugin(plugin_id)

        try:
            context = serialize(plugin, request.user, PluginWithConfigSerializer(project))
        except PluginIdentityRequired as e:
            context = serialize(plugin, request.user, PluginSerializer(project))
            context["config_error"] = six.text_type(e)
            context["auth_url"] = reverse("socialauth_associate", args=[plugin.slug])

        return Response(context)
예제 #6
0
    def get(self, request, project):
        """
        Retrieve a Project
        ``````````````````

        Return details on an individual project.

        :pparam string organization_slug: the slug of the organization the
                                          project belongs to.
        :pparam string project_slug: the slug of the project to delete.
        :auth: required
        """
        data = serialize(project, request.user)
        data['options'] = {
            'sentry:origins': '\n'.join(project.get_option('sentry:origins', ['*']) or []),
            'sentry:resolve_age': int(project.get_option('sentry:resolve_age', 0)),
            'sentry:scrub_data': bool(project.get_option('sentry:scrub_data', True)),
            'sentry:scrub_defaults': bool(project.get_option('sentry:scrub_defaults', True)),
            'sentry:safe_fields': project.get_option('sentry:safe_fields', []),
            'sentry:sensitive_fields': project.get_option('sentry:sensitive_fields', []),
            'sentry:csp_ignored_sources_defaults': bool(project.get_option('sentry:csp_ignored_sources_defaults', True)),
            'sentry:csp_ignored_sources': '\n'.join(project.get_option('sentry:csp_ignored_sources', []) or []),
            'sentry:default_environment': project.get_option('sentry:default_environment'),
            'sentry:reprocessing_show_hint': bool(project.get_option('sentry:reprocessing_show_hint', True)),
            'sentry:reprocessing_active': bool(project.get_option('sentry:reprocessing_active', False)),
            'filters:blacklisted_ips': '\n'.join(project.get_option('sentry:blacklisted_ips', [])),
            'feedback:branding': project.get_option('feedback:branding', '1') == '1',
        }
        data['plugins'] = serialize([
            plugin
            for plugin in plugins.configurable_for_project(project, version=None)
            if plugin.has_project_conf()
        ], request.user, PluginSerializer(project))
        data['team'] = serialize(project.team, request.user)
        data['organization'] = serialize(project.organization, request.user)

        data.update({
            'digestsMinDelay': project.get_option(
                'digests:mail:minimum_delay', digests.minimum_delay,
            ),
            'digestsMaxDelay': project.get_option(
                'digests:mail:maximum_delay', digests.maximum_delay,
            ),
            'subjectPrefix': project.get_option('mail:subject_prefix'),
            'subjectTemplate': project.get_option('mail:subject_template') or DEFAULT_SUBJECT_TEMPLATE.template,
        })

        include = set(filter(bool, request.GET.get('include', '').split(',')))
        if 'stats' in include:
            data['stats'] = {
                'unresolved': self._get_unresolved_count(project),
            }

        return Response(data)
예제 #7
0
    def get(self, request, project):
        """
        Retrieve a Project
        ``````````````````

        Return details on an individual project.

        :pparam string organization_slug: the slug of the organization the
                                          project belongs to.
        :pparam string project_slug: the slug of the project to delete.
        :auth: required
        """
        data = serialize(project, request.user)
        data['options'] = {
            'sentry:origins':
            '\n'.join(project.get_option('sentry:origins', ['*']) or []),
            'sentry:resolve_age':
            int(project.get_option('sentry:resolve_age', 0)),
            'sentry:scrub_data':
            bool(project.get_option('sentry:scrub_data', True)),
            'sentry:scrub_defaults':
            bool(project.get_option('sentry:scrub_defaults', True)),
            'sentry:sensitive_fields':
            project.get_option('sentry:sensitive_fields', []),
            'sentry:csp_ignored_sources_defaults':
            bool(
                project.get_option('sentry:csp_ignored_sources_defaults',
                                   True)),
            'sentry:csp_ignored_sources':
            '\n'.join(
                project.get_option('sentry:csp_ignored_sources', []) or []),
            'sentry:default_environment':
            project.get_option('sentry:default_environment'),
            'feedback:branding':
            project.get_option('feedback:branding', '1') == '1',
        }
        data['plugins'] = serialize([
            plugin for plugin in plugins.configurable_for_project(
                project, version=None) if plugin.has_project_conf()
        ], request.user, PluginSerializer(project))
        data['team'] = serialize(project.team, request.user)
        data['organization'] = serialize(project.organization, request.user)

        include = set(filter(bool, request.GET.get('include', '').split(',')))
        if 'stats' in include:
            data['stats'] = {
                'unresolved': self._get_unresolved_count(project),
            }

        return Response(data)
    def get(self, request, project, plugin_id):
        plugin = self._get_plugin(plugin_id)

        try:
            context = serialize(plugin, request.user,
                                PluginWithConfigSerializer(project))
        except PluginIdentityRequired as e:
            context = serialize(plugin, request.user,
                                PluginSerializer(project))
            context['config_error'] = e.message
            context['auth_url'] = reverse('socialauth_associate',
                                          args=[plugin.slug])

        return Response(context)
    def get(self, request: Request, project, plugin_id) -> Response:
        plugin = self._get_plugin(plugin_id)

        try:
            context = serialize(plugin, request.user,
                                PluginWithConfigSerializer(project))
        except PluginIdentityRequired as e:
            context = serialize(plugin, request.user,
                                PluginSerializer(project))
            context["config_error"] = str(e)
            context["auth_url"] = reverse("socialauth_associate",
                                          args=[plugin.slug])

        if context["isDeprecated"]:
            raise Http404
        return Response(context)
예제 #10
0
    def handle(self, request, organization, team, project):
        if request.method == 'POST':
            op = request.POST.get('op')
            if op == 'enable':
                self._handle_enable_plugin(request, project)
                return HttpResponseRedirect(request.path)
            elif op == 'disable':
                self._handle_disable_plugin(request, project)
                return HttpResponseRedirect(request.path)

        enabled_plugins = []
        other_plugins = []
        issue_v2_plugins = []
        for plugin in self._iter_plugins():
            if plugin.is_enabled(project):
                if isinstance(plugin, IssueTrackingPlugin2):
                    issue_v2_plugins.append(plugin)
                    continue
                content = plugin.get_issue_doc_html()

                form = plugin.project_conf_form
                if form is not None:
                    view = plugin.configure(request=request, project=project)
                    if isinstance(view, HttpResponse):
                        return view
                elif content:
                    enabled_plugins.append((plugin, mark_safe(content)))
                enabled_plugins.append((plugin, mark_safe(content + view)))
            elif plugin.can_configure_for_project(project):
                other_plugins.append(plugin)

        context = {
            'page':
            'issue-tracking',
            'enabled_plugins':
            enabled_plugins,
            'other_plugins':
            other_plugins,
            'issue_v2_plugins':
            serialize(issue_v2_plugins, request.user,
                      PluginSerializer(project=project)),
        }

        return self.respond('sentry/project-issue-tracking.html', context)
예제 #11
0
    def plugin_issues(self, request, group, plugin_issues, **kwargs):
        if not self.is_configured(request=request, project=group.project):
            return plugin_issues

        item = {
            'slug': self.slug,
            'allowed_actions': self.allowed_actions,
            'title': self.get_title()
        }
        issue = self.build_issue(group)
        if issue:
            item['issue'] = {
                'issue_id': issue.get('id'),
                'url': self._get_issue_url_compat(group, issue),
                'label': self._get_issue_label_compat(group, issue),
            }

        item.update(PluginSerializer(group.project).serialize(self, None, request.user))
        plugin_issues.append(item)
        return plugin_issues
예제 #12
0
    def plugin_issues(self, request, group, plugin_issues, **kwargs):
        if not self.is_configured(request=request, project=group.project):
            return plugin_issues

        item = {
            "slug": self.slug,
            "allowed_actions": self.allowed_actions,
            "title": self.get_title(),
        }
        issue = self.build_issue(group)
        if issue:
            item["issue"] = {
                "issue_id": issue.get("id"),
                "url": self._get_issue_url_compat(group, issue),
                "label": self._get_issue_label_compat(group, issue),
            }

        item.update(PluginSerializer(group.project).serialize(self, None, request.user))
        plugin_issues.append(item)
        return plugin_issues
예제 #13
0
파일: project.py 프로젝트: yatelu1/sentry
    def serialize(self, obj, attrs, user):
        from sentry.plugins import plugins

        data = super(DetailedProjectSerializer, self).serialize(
            obj, attrs, user
        )
        data.update({
            'latestRelease': attrs['latest_release'],
            'options': {
                'sentry:origins': '\n'.join(attrs['options'].get('sentry:origins', ['*']) or []),
                'sentry:resolve_age': int(attrs['options'].get('sentry:resolve_age', 0)),
                'sentry:scrub_data': bool(attrs['options'].get('sentry:scrub_data', True)),
                'sentry:scrub_defaults': bool(attrs['options'].get('sentry:scrub_defaults', True)),
                'sentry:safe_fields': attrs['options'].get('sentry:safe_fields', []),
                'sentry:sensitive_fields': attrs['options'].get('sentry:sensitive_fields', []),
                'sentry:csp_ignored_sources_defaults': bool(attrs['options'].get('sentry:csp_ignored_sources_defaults', True)),
                'sentry:csp_ignored_sources': '\n'.join(attrs['options'].get('sentry:csp_ignored_sources', []) or []),
                'sentry:reprocessing_active': bool(attrs['options'].get('sentry:reprocessing_active', False)),
                'filters:blacklisted_ips': '\n'.join(attrs['options'].get('sentry:blacklisted_ips', [])),
                'feedback:branding': attrs['options'].get('feedback:branding', '1') == '1',
            },
            'digestsMinDelay': attrs['options'].get(
                'digests:mail:minimum_delay', digests.minimum_delay,
            ),
            'digestsMaxDelay': attrs['options'].get(
                'digests:mail:maximum_delay', digests.maximum_delay,
            ),
            'subjectPrefix': attrs['options'].get('mail:subject_prefix'),
            'subjectTemplate': attrs['options'].get('mail:subject_template') or DEFAULT_SUBJECT_TEMPLATE.template,
            'organization': attrs['org'],
            'plugins': serialize([
                plugin
                for plugin in plugins.configurable_for_project(obj, version=None)
                if plugin.has_project_conf()
            ], user, PluginSerializer(obj)),
            'platforms': attrs['platforms'],
            'processingIssues': attrs['processing_issues'],
            'defaultEnvironment': attrs['options'].get('default_environment'),
        })
        return data
예제 #14
0
    def serialize(self, obj, attrs, user):
        from sentry.plugins.base import plugins

        def get_value_with_default(key):
            value = attrs["options"].get(key)
            if value is not None:
                return value
            return projectoptions.get_well_known_default(
                key, epoch=attrs["options"].get("sentry:option-epoch"))

        data = super(DetailedProjectSerializer,
                     self).serialize(obj, attrs, user)
        data.update({
            "latestRelease":
            attrs["latest_release"],
            "options": {
                "sentry:csp_ignored_sources_defaults":
                bool(attrs["options"].get(
                    "sentry:csp_ignored_sources_defaults", True)),
                "sentry:csp_ignored_sources":
                "\n".join(
                    attrs["options"].get("sentry:csp_ignored_sources", [])
                    or []),
                "sentry:reprocessing_active":
                bool(attrs["options"].get("sentry:reprocessing_active",
                                          False)),
                "filters:blacklisted_ips":
                "\n".join(attrs["options"].get("sentry:blacklisted_ips", [])),
                u"filters:{}".format(FilterTypes.RELEASES):
                "\n".join(attrs["options"].get(
                    u"sentry:{}".format(FilterTypes.RELEASES), [])),
                u"filters:{}".format(FilterTypes.ERROR_MESSAGES):
                "\n".join(attrs["options"].get(
                    u"sentry:{}".format(FilterTypes.ERROR_MESSAGES), [])),
                "feedback:branding":
                attrs["options"].get("feedback:branding", "1") == "1",
            },
            "digestsMinDelay":
            attrs["options"].get("digests:mail:minimum_delay",
                                 digests.minimum_delay),
            "digestsMaxDelay":
            attrs["options"].get("digests:mail:maximum_delay",
                                 digests.maximum_delay),
            "subjectPrefix":
            attrs["options"].get("mail:subject_prefix",
                                 options.get("mail.subject-prefix")),
            "allowedDomains":
            attrs["options"].get("sentry:origins", ["*"]),
            "resolveAge":
            int(attrs["options"].get("sentry:resolve_age", 0)),
            "dataScrubber":
            bool(attrs["options"].get("sentry:scrub_data", True)),
            "dataScrubberDefaults":
            bool(attrs["options"].get("sentry:scrub_defaults", True)),
            "safeFields":
            attrs["options"].get("sentry:safe_fields", []),
            "storeCrashReports":
            bool(attrs["options"].get("sentry:store_crash_reports", False)),
            "sensitiveFields":
            attrs["options"].get("sentry:sensitive_fields", []),
            "subjectTemplate":
            attrs["options"].get("mail:subject_template")
            or DEFAULT_SUBJECT_TEMPLATE.template,
            "securityToken":
            attrs["options"].get("sentry:token") or obj.get_security_token(),
            "securityTokenHeader":
            attrs["options"].get("sentry:token_header"),
            "verifySSL":
            bool(attrs["options"].get("sentry:verify_ssl", False)),
            "scrubIPAddresses":
            bool(attrs["options"].get("sentry:scrub_ip_address", False)),
            "scrapeJavaScript":
            bool(attrs["options"].get("sentry:scrape_javascript", True)),
            "groupingConfig":
            get_value_with_default("sentry:grouping_config"),
            "groupingEnhancements":
            get_value_with_default("sentry:grouping_enhancements"),
            "groupingEnhancementsBase":
            get_value_with_default("sentry:grouping_enhancements_base"),
            "fingerprintingRules":
            get_value_with_default("sentry:fingerprinting_rules"),
            "organization":
            attrs["org"],
            "plugins":
            serialize(
                [
                    plugin for plugin in plugins.configurable_for_project(
                        obj, version=None) if plugin.has_project_conf()
                ],
                user,
                PluginSerializer(obj),
            ),
            "platforms":
            attrs["platforms"],
            "processingIssues":
            attrs["processing_issues"],
            "defaultEnvironment":
            attrs["options"].get("sentry:default_environment"),
            "relayPiiConfig":
            attrs["options"].get("sentry:relay_pii_config"),
            "builtinSymbolSources":
            get_value_with_default("sentry:builtin_symbol_sources"),
            "symbolSources":
            attrs["options"].get("sentry:symbol_sources"),
        })
        return data
예제 #15
0
    def serialize(self, obj, attrs, user):
        from sentry.plugins import plugins

        data = super(DetailedProjectSerializer,
                     self).serialize(obj, attrs, user)
        data.update(
            {
                'latestRelease':
                attrs['latest_release'],
                'options': {
                    'sentry:csp_ignored_sources_defaults':
                    bool(attrs['options'].get(
                        'sentry:csp_ignored_sources_defaults', True)),
                    'sentry:csp_ignored_sources':
                    '\n'.join(attrs['options'].get(
                        'sentry:csp_ignored_sources', []) or []),
                    'sentry:reprocessing_active':
                    bool(attrs['options'].get(
                        'sentry:reprocessing_active', False)),
                    'filters:blacklisted_ips':
                    '\n'.join(attrs['options'].get(
                        'sentry:blacklisted_ips', [])),
                    u'filters:{}'.format(FilterTypes.RELEASES):
                    '\n'.join(attrs['options'].get(
                        u'sentry:{}'.format(FilterTypes.RELEASES), [])),
                    u'filters:{}'.format(FilterTypes.ERROR_MESSAGES):
                    '\n'.
                    join(attrs['options'].get(u'sentry:{}'.format(
                        FilterTypes.ERROR_MESSAGES), [])),
                    'feedback:branding':
                    attrs['options'].get('feedback:branding', '1') == '1',
                },
                'digestsMinDelay':
                attrs['options'].get(
                    'digests:mail:minimum_delay',
                    digests.minimum_delay,
                ),
                'digestsMaxDelay':
                attrs['options'].get(
                    'digests:mail:maximum_delay',
                    digests.maximum_delay,
                ),
                'subjectPrefix':
                attrs['options'].get('mail:subject_prefix', options.get('mail.subject-prefix')),
                'allowedDomains':
                attrs['options'].get(
                    'sentry:origins', ['*']),
                'resolveAge':
                int(attrs['options'].get('sentry:resolve_age', 0)),
                'dataScrubber':
                bool(attrs['options'].get('sentry:scrub_data', True)),
                'dataScrubberDefaults':
                bool(attrs['options'].get('sentry:scrub_defaults', True)),
                'safeFields':
                attrs['options'].get('sentry:safe_fields', []),
                'storeCrashReports': bool(attrs['options'].get('sentry:store_crash_reports', False)),
                'sensitiveFields':
                attrs['options'].get('sentry:sensitive_fields', []),
                'subjectTemplate':
                attrs['options'].get(
                    'mail:subject_template') or DEFAULT_SUBJECT_TEMPLATE.template,
                'securityToken': attrs['options'].get('sentry:token') or obj.get_security_token(),
                'securityTokenHeader': attrs['options'].get('sentry:token_header'),
                'verifySSL': bool(attrs['options'].get('sentry:verify_ssl', False)),
                'scrubIPAddresses': bool(attrs['options'].get('sentry:scrub_ip_address', False)),
                'scrapeJavaScript': bool(attrs['options'].get('sentry:scrape_javascript', True)),
                'organization':
                attrs['org'],
                'plugins':
                serialize(
                    [
                        plugin for plugin in plugins.configurable_for_project(obj, version=None)
                        if plugin.has_project_conf()
                    ], user, PluginSerializer(obj)
                ),
                'platforms': attrs['platforms'],
                'processingIssues': attrs['processing_issues'],
                'defaultEnvironment': attrs['options'].get('sentry:default_environment'),
                'relayPiiConfig': attrs['options'].get('sentry:relay_pii_config'),
            }
        )
        return data
예제 #16
0
 def get(self, request, project):
     context = serialize([
         plugin for plugin in plugins.configurable_for_project(
             project, version=None) if plugin.has_project_conf()
     ], request.user, PluginSerializer(project))
     return Response(context)
예제 #17
0
    def get(self, request, organization):

        """
        List one or more plugin configurations, including a `projectList` for each plugin which contains
        all the projects that have that specific plugin both configured and enabled.

        - similar to the `OrganizationPluginsEndpoint`, and can eventually replace it

        :qparam plugins array[string]: an optional list of plugin ids (slugs) if you want specific plugins.
                                    If not set, will return configurations for all plugins.
        """

        desired_plugins = []
        for slug in request.GET.getlist("plugins") or ():
            # if the user request a plugin that doesn't exist, throw 404
            try:
                desired_plugins.append(plugins.get(slug))
            except KeyError:
                return Response({"detail": "Plugin %s not found" % slug}, status=404)

        # if no plugins were specified, grab all plugins but limit by those that have the ability to be configured
        if not desired_plugins:
            desired_plugins = list(plugins.plugin_that_can_be_configured())

        # `keys_to_check` are the ProjectOption keys that tell us if a plugin is enabled (e.g. `plugin:enabled`) or are
        # configured properly, meaning they have the required information - plugin.required_field - needed for the
        # plugin to work (ex:`opsgenie:api_key`)
        keys_to_check = []
        for plugin in desired_plugins:
            keys_to_check.append("%s:enabled" % plugin.slug)
            if plugin.required_field:
                keys_to_check.append("%s:%s" % (plugin.slug, plugin.required_field))

        # Get all the project options for org that have truthy values
        project_options = ProjectOption.objects.filter(
            key__in=keys_to_check, project__organization=organization
        ).exclude(value__in=[False, ""])

        """
        This map stores info about whether a plugin is configured and/or enabled
        {
            "plugin_slug": {
                "project_id": { "enabled": True, "configured": False },
            },
        }
        """
        info_by_plugin_project = {}
        for project_option in project_options:
            [slug, field] = project_option.key.split(":")
            project_id = project_option.project_id

            # first add to the set of all projects by plugin
            info_by_plugin_project.setdefault(slug, {}).setdefault(
                project_id, {"enabled": False, "configured": False}
            )

            # next check if enabled
            if field == "enabled":
                info_by_plugin_project[slug][project_id]["enabled"] = True
            # if the projectoption is not the enable field, it's configuration field
            else:
                info_by_plugin_project[slug][project_id]["configured"] = True

        # get the IDs of all projects for found project options and grab them from the DB
        project_id_set = set([project_option.project_id for project_option in project_options])
        projects = Project.objects.filter(id__in=project_id_set, status=ObjectStatus.VISIBLE)

        # create a key/value map of our projects
        project_map = {project.id: project for project in projects}

        # iterate through the desired plugins and serialize them
        serialized_plugins = []
        for plugin in desired_plugins:
            serialized_plugin = serialize(plugin, request.user, PluginSerializer())

            serialized_plugin["projectList"] = []

            info_by_project = info_by_plugin_project.get(plugin.slug, {})

            # iterate through the projects
            for project_id, plugin_info in six.iteritems(info_by_project):
                # if the project is being deleted
                if project_id not in project_map:
                    continue
                project = project_map[project_id]

                # only include plugins which are configured
                if not plugin_info["configured"]:
                    continue

                serialized_plugin["projectList"].append(
                    {
                        "projectId": project.id,
                        "projectSlug": project.slug,
                        "projectName": project.name,  # TODO(steve): do we need?
                        "enabled": plugin_info["enabled"],
                        "configured": plugin_info["configured"],  # TODO(steve): do we need?
                        "projectPlatform": project.platform,
                    }
                )
            # sort by the projectSlug
            serialized_plugin["projectList"].sort(key=lambda x: x["projectSlug"])
            serialized_plugins.append(serialized_plugin)

        return Response(serialized_plugins)
예제 #18
0
    def serialize(self, obj, attrs, user):
        from sentry.plugins.base import plugins

        def get_value_with_default(key):
            value = attrs["options"].get(key)
            if value is not None:
                return value
            return projectoptions.get_well_known_default(
                key, epoch=attrs["options"].get("sentry:option-epoch")
            )

        data = super().serialize(obj, attrs, user)
        data.update(
            {
                "latestRelease": attrs["latest_release"],
                "options": {
                    "sentry:csp_ignored_sources_defaults": bool(
                        attrs["options"].get("sentry:csp_ignored_sources_defaults", True)
                    ),
                    "sentry:csp_ignored_sources": "\n".join(
                        attrs["options"].get("sentry:csp_ignored_sources", []) or []
                    ),
                    "sentry:reprocessing_active": bool(
                        attrs["options"].get("sentry:reprocessing_active", False)
                    ),
                    "filters:blacklisted_ips": "\n".join(
                        attrs["options"].get("sentry:blacklisted_ips", [])
                    ),
                    f"filters:{FilterTypes.RELEASES}": "\n".join(
                        attrs["options"].get(f"sentry:{FilterTypes.RELEASES}", [])
                    ),
                    f"filters:{FilterTypes.ERROR_MESSAGES}": "\n".join(
                        attrs["options"].get(f"sentry:{FilterTypes.ERROR_MESSAGES}", [])
                    ),
                    "feedback:branding": attrs["options"].get("feedback:branding", "1") == "1",
                },
                "digestsMinDelay": attrs["options"].get(
                    "digests:mail:minimum_delay", digests.minimum_delay
                ),
                "digestsMaxDelay": attrs["options"].get(
                    "digests:mail:maximum_delay", digests.maximum_delay
                ),
                "subjectPrefix": attrs["options"].get(
                    "mail:subject_prefix", options.get("mail.subject-prefix")
                ),
                "allowedDomains": attrs["options"].get("sentry:origins", ["*"]),
                "resolveAge": int(attrs["options"].get("sentry:resolve_age", 0)),
                "dataScrubber": bool(attrs["options"].get("sentry:scrub_data", True)),
                "dataScrubberDefaults": bool(attrs["options"].get("sentry:scrub_defaults", True)),
                "safeFields": attrs["options"].get("sentry:safe_fields", []),
                "storeCrashReports": convert_crashreport_count(
                    attrs["options"].get("sentry:store_crash_reports"), allow_none=True
                ),
                "sensitiveFields": attrs["options"].get("sentry:sensitive_fields", []),
                "subjectTemplate": attrs["options"].get("mail:subject_template")
                or DEFAULT_SUBJECT_TEMPLATE.template,
                "securityToken": attrs["options"].get("sentry:token") or obj.get_security_token(),
                "securityTokenHeader": attrs["options"].get("sentry:token_header"),
                "verifySSL": bool(attrs["options"].get("sentry:verify_ssl", False)),
                "scrubIPAddresses": bool(attrs["options"].get("sentry:scrub_ip_address", False)),
                "scrapeJavaScript": bool(attrs["options"].get("sentry:scrape_javascript", True)),
                "groupingConfig": get_value_with_default("sentry:grouping_config"),
                "groupingEnhancements": get_value_with_default("sentry:grouping_enhancements"),
                "groupingEnhancementsBase": get_value_with_default(
                    "sentry:grouping_enhancements_base"
                ),
                "secondaryGroupingExpiry": get_value_with_default(
                    "sentry:secondary_grouping_expiry"
                ),
                "secondaryGroupingConfig": get_value_with_default(
                    "sentry:secondary_grouping_config"
                ),
                "fingerprintingRules": get_value_with_default("sentry:fingerprinting_rules"),
                "organization": attrs["org"],
                "plugins": serialize(
                    [
                        plugin
                        for plugin in plugins.configurable_for_project(obj, version=None)
                        if plugin.has_project_conf()
                    ],
                    user,
                    PluginSerializer(obj),
                ),
                "platforms": attrs["platforms"],
                "processingIssues": attrs["processing_issues"],
                "defaultEnvironment": attrs["options"].get("sentry:default_environment"),
                "relayPiiConfig": attrs["options"].get("sentry:relay_pii_config"),
                "builtinSymbolSources": get_value_with_default("sentry:builtin_symbol_sources"),
                "dynamicSampling": get_value_with_default("sentry:dynamic_sampling"),
                "eventProcessing": {
                    "symbolicationDegraded": False,
                },
            }
        )
        custom_symbol_sources_json = attrs["options"].get("sentry:symbol_sources")
        try:
            sources = parse_sources(custom_symbol_sources_json, False)
        except Exception:
            # In theory sources stored on the project should be valid. If they are invalid, we don't
            # want to abort serialization just for sources, so just return an empty list instead of
            # returning sources with their secrets included.
            serialized_sources = "[]"
        else:
            redacted_sources = redact_source_secrets(sources)
            serialized_sources = json.dumps(redacted_sources)

        data.update(
            {
                "symbolSources": serialized_sources,
            }
        )

        return data