示例#1
0
    def test_non_org_rename_403(self):
        org = self.create_organization()
        team = self.create_team(organization=org, name="foo", slug="foo")
        user = self.create_user(is_superuser=False)
        self.create_member(user=user, organization=org, role="member", teams=[team])

        other_org = self.create_organization()
        other_project = self.create_project(organization=other_org)
        ProjectRedirect.record(other_project, "old_slug")
        self.login_as(user=user)

        self.get_valid_response(other_org.slug, "old_slug", status_code=403)
示例#2
0
    def test_record(self):
        org = self.create_organization()
        project = self.create_project(organization=org)
        ProjectRedirect.record(project, "old_slug")

        assert ProjectRedirect.objects.filter(redirect_slug="old_slug", project=project).exists()

        # Recording the same historic slug on a different project updates the
        # project pointer.
        project2 = self.create_project(organization=org)
        ProjectRedirect.record(project2, "old_slug")

        assert not ProjectRedirect.objects.filter(
            redirect_slug="old_slug", project=project
        ).exists()

        assert ProjectRedirect.objects.filter(redirect_slug="old_slug", project=project2).exists()
示例#3
0
    def test_non_org_rename_403(self):
        org = self.create_organization()
        team = self.create_team(organization=org, name="foo", slug="foo")
        user = self.create_user(is_superuser=False)
        self.create_member(user=user, organization=org, role="member", teams=[team])

        other_org = self.create_organization()
        other_project = self.create_project(organization=other_org)
        ProjectRedirect.record(other_project, "old_slug")

        url = reverse(
            "sentry-api-0-project-details",
            kwargs={"organization_slug": other_org.slug, "project_slug": "old_slug"},
        )
        self.login_as(user=user)
        response = self.client.get(url)
        assert response.status_code == 403
示例#4
0
    def test_record(self):
        org = self.create_organization()
        project = self.create_project(organization=org)
        ProjectRedirect.record(project, 'old_slug')

        assert ProjectRedirect.objects.filter(
            redirect_slug='old_slug',
            project=project,
        ).exists()

        # Recording the same historic slug on a different project updates the
        # project pointer.
        project2 = self.create_project(organization=org)
        ProjectRedirect.record(project2, 'old_slug')

        assert not ProjectRedirect.objects.filter(
            redirect_slug='old_slug',
            project=project,
        ).exists()

        assert ProjectRedirect.objects.filter(
            redirect_slug='old_slug',
            project=project2,
        ).exists()
示例#5
0
    def put(self, request, project):
        """
        Update a Project
        ````````````````

        Update various attributes and configurable settings for the given
        project.  Only supplied values are updated.

        :pparam string organization_slug: the slug of the organization the
                                          project belongs to.
        :pparam string project_slug: the slug of the project to delete.
        :param string name: the new name for the project.
        :param string slug: the new slug for the project.
        :param string team: the slug of new team for the project. Note, will be deprecated
                            soon when multiple teams can have access to a project.
        :param string platform: the new platform for the project.
        :param boolean isBookmarked: in case this API call is invoked with a
                                     user context this allows changing of
                                     the bookmark flag.
        :param int digestsMinDelay:
        :param int digestsMaxDelay:
        :auth: required
        """
        has_project_write = (
            (request.auth and request.auth.has_scope('project:write'))
            or (request.access and request.access.has_scope('project:write')))

        changed_proj_settings = {}

        if has_project_write:
            serializer_cls = ProjectAdminSerializer
        else:
            serializer_cls = ProjectMemberSerializer

        serializer = serializer_cls(
            data=request.DATA,
            partial=True,
            context={
                'project': project,
                'request': request,
            },
        )
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        if not has_project_write:
            for key in six.iterkeys(ProjectAdminSerializer.base_fields):
                if request.DATA.get(key) and not result.get(key):
                    return Response(
                        {
                            'detail': [
                                'You do not have permission to perform this action.'
                            ]
                        },
                        status=403)

        changed = False

        old_slug = None
        if result.get('slug'):
            old_slug = project.slug
            project.slug = result['slug']
            changed = True
            changed_proj_settings['new_slug'] = project.slug

        if result.get('name'):
            project.name = result['name']
            changed = True
            changed_proj_settings['new_project'] = project.name

        old_team_id = None
        new_team = None
        if result.get('team'):
            if features.has('organizations:new-teams',
                            project.organization,
                            actor=request.user):
                return Response(
                    {
                        'detail': [
                            'Editing a team via this endpoint has been deprecated.'
                        ]
                    },
                    status=400)

            team_list = [
                t for t in Team.objects.get_for_user(
                    organization=project.organization,
                    user=request.user,
                ) if request.access.has_team_scope(t, 'project:write')
                if t.slug == result['team']
            ]
            if not team_list:
                return Response({'detail': ['The new team is not found.']},
                                status=400)
            # TODO(jess): update / deprecate this functionality
            try:
                old_team_id = project.teams.values_list('id', flat=True)[0]
            except IndexError:
                pass

            new_team = team_list[0]
            changed = True

        if result.get('platform'):
            project.platform = result['platform']
            changed = True

        if changed:
            project.save()
            if old_team_id is not None:
                ProjectTeam.objects.filter(
                    project=project,
                    team_id=old_team_id,
                ).update(team=new_team)

            if old_slug:
                ProjectRedirect.record(project, old_slug)

        if result.get('isBookmarked'):
            try:
                with transaction.atomic():
                    ProjectBookmark.objects.create(
                        project_id=project.id,
                        user=request.user,
                    )
            except IntegrityError:
                pass
        elif result.get('isBookmarked') is False:
            ProjectBookmark.objects.filter(
                project_id=project.id,
                user=request.user,
            ).delete()

        if result.get('digestsMinDelay'):
            project.update_option('digests:mail:minimum_delay',
                                  result['digestsMinDelay'])
        if result.get('digestsMaxDelay'):
            project.update_option('digests:mail:maximum_delay',
                                  result['digestsMaxDelay'])
        if result.get('subjectPrefix') is not None:
            project.update_option('mail:subject_prefix',
                                  result['subjectPrefix'])
        if result.get('subjectTemplate'):
            project.update_option('mail:subject_template',
                                  result['subjectTemplate'])
        if result.get('defaultEnvironment') is not None:
            project.update_option('sentry:default_environment',
                                  result['defaultEnvironment'])
        if result.get('scrubIPAddresses') is not None:
            project.update_option('sentry:scrub_ip_address',
                                  result['scrubIPAddresses'])
        if result.get('securityToken') is not None:
            project.update_option('sentry:token', result['securityToken'])
        if result.get('securityTokenHeader') is not None:
            project.update_option('sentry:token_header',
                                  result['securityTokenHeader'])
        if result.get('verifySSL') is not None:
            project.update_option('sentry:verify_ssl', result['verifySSL'])
        if result.get('dataScrubber') is not None:
            project.update_option('sentry:scrub_data', result['dataScrubber'])
        if result.get('dataScrubberDefaults') is not None:
            project.update_option('sentry:scrub_defaults',
                                  result['dataScrubberDefaults'])
        if result.get('sensitiveFields') is not None:
            project.update_option('sentry:sensitive_fields',
                                  result['sensitiveFields'])
        if result.get('safeFields') is not None:
            project.update_option('sentry:safe_fields', result['safeFields'])
        # resolveAge can be None
        if 'resolveAge' in result:
            project.update_option(
                'sentry:resolve_age', 0 if result.get('resolveAge') is None
                else int(result['resolveAge']))
        if result.get('scrapeJavaScript') is not None:
            project.update_option('sentry:scrape_javascript',
                                  result['scrapeJavaScript'])
        if result.get('allowedDomains'):
            project.update_option('sentry:origins', result['allowedDomains'])

        if result.get('isSubscribed'):
            UserOption.objects.set_value(user=request.user,
                                         key='mail:alert',
                                         value=1,
                                         project=project)
        elif result.get('isSubscribed') is False:
            UserOption.objects.set_value(user=request.user,
                                         key='mail:alert',
                                         value=0,
                                         project=project)

        # TODO(dcramer): rewrite options to use standard API config
        if has_project_write:
            options = request.DATA.get('options', {})
            if 'sentry:origins' in options:
                project.update_option(
                    'sentry:origins',
                    clean_newline_inputs(options['sentry:origins']))
            if 'sentry:resolve_age' in options:
                project.update_option('sentry:resolve_age',
                                      int(options['sentry:resolve_age']))
            if 'sentry:scrub_data' in options:
                project.update_option('sentry:scrub_data',
                                      bool(options['sentry:scrub_data']))
            if 'sentry:scrub_defaults' in options:
                project.update_option('sentry:scrub_defaults',
                                      bool(options['sentry:scrub_defaults']))
            if 'sentry:safe_fields' in options:
                project.update_option(
                    'sentry:safe_fields',
                    [s.strip().lower() for s in options['sentry:safe_fields']])
            if 'sentry:sensitive_fields' in options:
                project.update_option('sentry:sensitive_fields', [
                    s.strip().lower()
                    for s in options['sentry:sensitive_fields']
                ])
            if 'sentry:scrub_ip_address' in options:
                project.update_option(
                    'sentry:scrub_ip_address',
                    bool(options['sentry:scrub_ip_address']),
                )
            if 'mail:subject_prefix' in options:
                project.update_option(
                    'mail:subject_prefix',
                    options['mail:subject_prefix'],
                )
            if 'sentry:default_environment' in options:
                project.update_option(
                    'sentry:default_environment',
                    options['sentry:default_environment'],
                )
            if 'sentry:csp_ignored_sources_defaults' in options:
                project.update_option(
                    'sentry:csp_ignored_sources_defaults',
                    bool(options['sentry:csp_ignored_sources_defaults']))
            if 'sentry:csp_ignored_sources' in options:
                project.update_option(
                    'sentry:csp_ignored_sources',
                    clean_newline_inputs(
                        options['sentry:csp_ignored_sources']))
            if 'sentry:blacklisted_ips' in options:
                project.update_option(
                    'sentry:blacklisted_ips',
                    clean_newline_inputs(options['sentry:blacklisted_ips']),
                )
            if 'feedback:branding' in options:
                project.update_option(
                    'feedback:branding',
                    '1' if options['feedback:branding'] else '0')
            if 'sentry:reprocessing_active' in options:
                project.update_option(
                    'sentry:reprocessing_active',
                    bool(options['sentry:reprocessing_active']))
            if 'filters:blacklisted_ips' in options:
                project.update_option(
                    'sentry:blacklisted_ips',
                    clean_newline_inputs(options['filters:blacklisted_ips']))
            if 'filters:{}'.format(FilterTypes.RELEASES) in options:
                if features.has('projects:custom-inbound-filters',
                                project,
                                actor=request.user):
                    project.update_option(
                        'sentry:{}'.format(FilterTypes.RELEASES),
                        clean_newline_inputs(options['filters:{}'.format(
                            FilterTypes.RELEASES)]))
                else:
                    return Response(
                        {'detail': ['You do not have that feature enabled']},
                        status=400)
            if 'filters:{}'.format(FilterTypes.ERROR_MESSAGES) in options:
                if features.has('projects:custom-inbound-filters',
                                project,
                                actor=request.user):
                    project.update_option(
                        'sentry:{}'.format(FilterTypes.ERROR_MESSAGES),
                        clean_newline_inputs(options['filters:{}'.format(
                            FilterTypes.ERROR_MESSAGES)],
                                             case_insensitive=False))
                else:
                    return Response(
                        {'detail': ['You do not have that feature enabled']},
                        status=400)

            self.create_audit_entry(request=request,
                                    organization=project.organization,
                                    target_object=project.id,
                                    event=AuditLogEntryEvent.PROJECT_EDIT,
                                    data=changed_proj_settings)

        data = serialize(project, request.user, DetailedProjectSerializer())
        return Response(data)
示例#6
0
    def put(self, request: Request, project) -> Response:
        """
        Update a Project
        ````````````````

        Update various attributes and configurable settings for the given
        project.  Only supplied values are updated.

        :pparam string organization_slug: the slug of the organization the
                                          project belongs to.
        :pparam string project_slug: the slug of the project to update.
        :param string name: the new name for the project.
        :param string slug: the new slug for the project.
        :param string team: the slug of new team for the project. Note, will be deprecated
                            soon when multiple teams can have access to a project.
        :param string platform: the new platform for the project.
        :param boolean isBookmarked: in case this API call is invoked with a
                                     user context this allows changing of
                                     the bookmark flag.
        :param int digestsMinDelay:
        :param int digestsMaxDelay:
        :auth: required
        """
        has_project_write = (request.auth and request.auth.has_scope("project:write")) or (
            request.access and request.access.has_scope("project:write")
        )

        changed_proj_settings = {}

        if has_project_write:
            serializer_cls = ProjectAdminSerializer
        else:
            serializer_cls = ProjectMemberSerializer

        serializer = serializer_cls(
            data=request.data, partial=True, context={"project": project, "request": request}
        )
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data

        allow_dynamic_sampling = features.has(
            "organizations:filters-and-sampling", project.organization, actor=request.user
        )

        allow_dynamic_sampling_error_rules = features.has(
            "organizations:filters-and-sampling-error-rules",
            project.organization,
            actor=request.user,
        )

        if not allow_dynamic_sampling and result.get("dynamicSampling"):
            # trying to set dynamic sampling with feature disabled
            return Response(
                {"detail": ["You do not have permission to set dynamic sampling."]},
                status=403,
            )

        if not has_project_write:
            # options isn't part of the serializer, but should not be editable by members
            for key in chain(ProjectAdminSerializer().fields.keys(), ["options"]):
                if request.data.get(key) and not result.get(key):
                    return Response(
                        {"detail": ["You do not have permission to perform this action."]},
                        status=403,
                    )

        changed = False

        old_slug = None
        if result.get("slug"):
            old_slug = project.slug
            project.slug = result["slug"]
            changed = True
            changed_proj_settings["new_slug"] = project.slug
            changed_proj_settings["old_slug"] = old_slug

        if result.get("name"):
            project.name = result["name"]
            changed = True
            changed_proj_settings["new_project"] = project.name

        old_team_id = None
        new_team = None
        if result.get("team"):
            return Response(
                {"detail": ["Editing a team via this endpoint has been deprecated."]}, status=400
            )

        if result.get("platform"):
            project.platform = result["platform"]
            changed = True

        if changed:
            project.save()
            if old_team_id is not None:
                ProjectTeam.objects.filter(project=project, team_id=old_team_id).update(
                    team=new_team
                )

            if old_slug:
                ProjectRedirect.record(project, old_slug)

        if result.get("isBookmarked"):
            try:
                with transaction.atomic():
                    ProjectBookmark.objects.create(project_id=project.id, user=request.user)
            except IntegrityError:
                pass
        elif result.get("isBookmarked") is False:
            ProjectBookmark.objects.filter(project_id=project.id, user=request.user).delete()

        if result.get("digestsMinDelay"):
            project.update_option("digests:mail:minimum_delay", result["digestsMinDelay"])
        if result.get("digestsMaxDelay"):
            project.update_option("digests:mail:maximum_delay", result["digestsMaxDelay"])
        if result.get("subjectPrefix") is not None:
            if project.update_option("mail:subject_prefix", result["subjectPrefix"]):
                changed_proj_settings["mail:subject_prefix"] = result["subjectPrefix"]
        if result.get("subjectTemplate"):
            project.update_option("mail:subject_template", result["subjectTemplate"])
        if result.get("scrubIPAddresses") is not None:
            if project.update_option("sentry:scrub_ip_address", result["scrubIPAddresses"]):
                changed_proj_settings["sentry:scrub_ip_address"] = result["scrubIPAddresses"]
        if result.get("groupingConfig") is not None:
            if project.update_option("sentry:grouping_config", result["groupingConfig"]):
                changed_proj_settings["sentry:grouping_config"] = result["groupingConfig"]
        if result.get("groupingEnhancements") is not None:
            if project.update_option(
                "sentry:grouping_enhancements", result["groupingEnhancements"]
            ):
                changed_proj_settings["sentry:grouping_enhancements"] = result[
                    "groupingEnhancements"
                ]
        if result.get("fingerprintingRules") is not None:
            if project.update_option("sentry:fingerprinting_rules", result["fingerprintingRules"]):
                changed_proj_settings["sentry:fingerprinting_rules"] = result["fingerprintingRules"]
        if result.get("secondaryGroupingConfig") is not None:
            if project.update_option(
                "sentry:secondary_grouping_config", result["secondaryGroupingConfig"]
            ):
                changed_proj_settings["sentry:secondary_grouping_config"] = result[
                    "secondaryGroupingConfig"
                ]

        if result.get("secondaryGroupingExpiry") is not None:
            if project.update_option(
                "sentry:secondary_grouping_expiry", result["secondaryGroupingExpiry"]
            ):
                changed_proj_settings["sentry:secondary_grouping_expiry"] = result[
                    "secondaryGroupingExpiry"
                ]
        if result.get("securityToken") is not None:
            if project.update_option("sentry:token", result["securityToken"]):
                changed_proj_settings["sentry:token"] = result["securityToken"]
        if result.get("securityTokenHeader") is not None:
            if project.update_option("sentry:token_header", result["securityTokenHeader"]):
                changed_proj_settings["sentry:token_header"] = result["securityTokenHeader"]
        if result.get("verifySSL") is not None:
            if project.update_option("sentry:verify_ssl", result["verifySSL"]):
                changed_proj_settings["sentry:verify_ssl"] = result["verifySSL"]
        if result.get("dataScrubber") is not None:
            if project.update_option("sentry:scrub_data", result["dataScrubber"]):
                changed_proj_settings["sentry:scrub_data"] = result["dataScrubber"]
        if result.get("dataScrubberDefaults") is not None:
            if project.update_option("sentry:scrub_defaults", result["dataScrubberDefaults"]):
                changed_proj_settings["sentry:scrub_defaults"] = result["dataScrubberDefaults"]
        if result.get("sensitiveFields") is not None:
            if project.update_option("sentry:sensitive_fields", result["sensitiveFields"]):
                changed_proj_settings["sentry:sensitive_fields"] = result["sensitiveFields"]
        if result.get("safeFields") is not None:
            if project.update_option("sentry:safe_fields", result["safeFields"]):
                changed_proj_settings["sentry:safe_fields"] = result["safeFields"]
        if "storeCrashReports" in result is not None:
            if project.get_option("sentry:store_crash_reports") != result["storeCrashReports"]:
                changed_proj_settings["sentry:store_crash_reports"] = result["storeCrashReports"]
                if result["storeCrashReports"] is None:
                    project.delete_option("sentry:store_crash_reports")
                else:
                    project.update_option("sentry:store_crash_reports", result["storeCrashReports"])
        if result.get("relayPiiConfig") is not None:
            if project.update_option("sentry:relay_pii_config", result["relayPiiConfig"]):
                changed_proj_settings["sentry:relay_pii_config"] = (
                    result["relayPiiConfig"].strip() or None
                )
        if result.get("builtinSymbolSources") is not None:
            if project.update_option(
                "sentry:builtin_symbol_sources", result["builtinSymbolSources"]
            ):
                changed_proj_settings["sentry:builtin_symbol_sources"] = result[
                    "builtinSymbolSources"
                ]
        if result.get("symbolSources") is not None:
            if project.update_option("sentry:symbol_sources", result["symbolSources"]):
                # Redact secrets so they don't get logged directly to the Audit Log
                sources_json = result["symbolSources"] or None
                try:
                    sources = parse_sources(sources_json)
                except Exception:
                    sources = []
                redacted_sources = redact_source_secrets(sources)
                changed_proj_settings["sentry:symbol_sources"] = redacted_sources
        if "defaultEnvironment" in result:
            if result["defaultEnvironment"] is None:
                project.delete_option("sentry:default_environment")
            else:
                project.update_option("sentry:default_environment", result["defaultEnvironment"])
        # resolveAge can be None
        if "resolveAge" in result:
            if project.update_option(
                "sentry:resolve_age",
                0 if result.get("resolveAge") is None else int(result["resolveAge"]),
            ):
                changed_proj_settings["sentry:resolve_age"] = result["resolveAge"]
        if result.get("scrapeJavaScript") is not None:
            if project.update_option("sentry:scrape_javascript", result["scrapeJavaScript"]):
                changed_proj_settings["sentry:scrape_javascript"] = result["scrapeJavaScript"]
        if result.get("allowedDomains"):
            if project.update_option("sentry:origins", result["allowedDomains"]):
                changed_proj_settings["sentry:origins"] = result["allowedDomains"]

        if "isSubscribed" in result:
            NotificationSetting.objects.update_settings(
                ExternalProviders.EMAIL,
                NotificationSettingTypes.ISSUE_ALERTS,
                get_option_value_from_boolean(result.get("isSubscribed")),
                user=request.user,
                project=project,
            )

        if "dynamicSampling" in result:
            raw_dynamic_sampling = result["dynamicSampling"]
            if (
                not allow_dynamic_sampling_error_rules
                and self._dynamic_sampling_contains_error_rule(raw_dynamic_sampling)
            ):
                return Response(
                    {
                        "detail": [
                            "Dynamic Sampling only accepts rules of type transaction or trace"
                        ]
                    },
                    status=400,
                )

            fixed_rules = self._fix_rule_ids(project, raw_dynamic_sampling)
            project.update_option("sentry:dynamic_sampling", fixed_rules)

        # TODO(dcramer): rewrite options to use standard API config
        if has_project_write:
            options = request.data.get("options", {})
            if "sentry:origins" in options:
                project.update_option(
                    "sentry:origins", clean_newline_inputs(options["sentry:origins"])
                )
            if "sentry:resolve_age" in options:
                project.update_option("sentry:resolve_age", int(options["sentry:resolve_age"]))
            if "sentry:scrub_data" in options:
                project.update_option("sentry:scrub_data", bool(options["sentry:scrub_data"]))
            if "sentry:scrub_defaults" in options:
                project.update_option(
                    "sentry:scrub_defaults", bool(options["sentry:scrub_defaults"])
                )
            if "sentry:safe_fields" in options:
                project.update_option(
                    "sentry:safe_fields", [s.strip().lower() for s in options["sentry:safe_fields"]]
                )
            if "sentry:store_crash_reports" in options:
                project.update_option(
                    "sentry:store_crash_reports",
                    convert_crashreport_count(
                        options["sentry:store_crash_reports"], allow_none=True
                    ),
                )
            if "sentry:relay_pii_config" in options:
                project.update_option(
                    "sentry:relay_pii_config", options["sentry:relay_pii_config"].strip() or None
                )
            if "sentry:sensitive_fields" in options:
                project.update_option(
                    "sentry:sensitive_fields",
                    [s.strip().lower() for s in options["sentry:sensitive_fields"]],
                )
            if "sentry:scrub_ip_address" in options:
                project.update_option(
                    "sentry:scrub_ip_address", bool(options["sentry:scrub_ip_address"])
                )
            if "sentry:grouping_config" in options:
                project.update_option("sentry:grouping_config", options["sentry:grouping_config"])
            if "sentry:fingerprinting_rules" in options:
                project.update_option(
                    "sentry:fingerprinting_rules", options["sentry:fingerprinting_rules"]
                )
            if "mail:subject_prefix" in options:
                project.update_option("mail:subject_prefix", options["mail:subject_prefix"])
            if "sentry:default_environment" in options:
                project.update_option(
                    "sentry:default_environment", options["sentry:default_environment"]
                )
            if "sentry:csp_ignored_sources_defaults" in options:
                project.update_option(
                    "sentry:csp_ignored_sources_defaults",
                    bool(options["sentry:csp_ignored_sources_defaults"]),
                )
            if "sentry:csp_ignored_sources" in options:
                project.update_option(
                    "sentry:csp_ignored_sources",
                    clean_newline_inputs(options["sentry:csp_ignored_sources"]),
                )
            if "sentry:blacklisted_ips" in options:
                project.update_option(
                    "sentry:blacklisted_ips",
                    clean_newline_inputs(options["sentry:blacklisted_ips"]),
                )
            if "feedback:branding" in options:
                project.update_option(
                    "feedback:branding", "1" if options["feedback:branding"] else "0"
                )
            if "sentry:reprocessing_active" in options:
                project.update_option(
                    "sentry:reprocessing_active", bool(options["sentry:reprocessing_active"])
                )
            if "filters:blacklisted_ips" in options:
                project.update_option(
                    "sentry:blacklisted_ips",
                    clean_newline_inputs(options["filters:blacklisted_ips"]),
                )
            if f"filters:{FilterTypes.RELEASES}" in options:
                if features.has("projects:custom-inbound-filters", project, actor=request.user):
                    project.update_option(
                        f"sentry:{FilterTypes.RELEASES}",
                        clean_newline_inputs(options[f"filters:{FilterTypes.RELEASES}"]),
                    )
                else:
                    return Response(
                        {"detail": ["You do not have that feature enabled"]}, status=400
                    )
            if f"filters:{FilterTypes.ERROR_MESSAGES}" in options:
                if features.has("projects:custom-inbound-filters", project, actor=request.user):
                    project.update_option(
                        f"sentry:{FilterTypes.ERROR_MESSAGES}",
                        clean_newline_inputs(
                            options[f"filters:{FilterTypes.ERROR_MESSAGES}"],
                            case_insensitive=False,
                        ),
                    )
                else:
                    return Response(
                        {"detail": ["You do not have that feature enabled"]}, status=400
                    )
            if "copy_from_project" in result:
                if not project.copy_settings_from(result["copy_from_project"]):
                    return Response({"detail": ["Copy project settings failed."]}, status=409)

            self.create_audit_entry(
                request=request,
                organization=project.organization,
                target_object=project.id,
                event=AuditLogEntryEvent.PROJECT_EDIT,
                data=changed_proj_settings,
            )

        data = serialize(project, request.user, DetailedProjectSerializer())
        return Response(data)
示例#7
0
    def put(self, request, project):
        """
        Update a Project
        ````````````````

        Update various attributes and configurable settings for the given
        project.  Only supplied values are updated.

        :pparam string organization_slug: the slug of the organization the
                                          project belongs to.
        :pparam string project_slug: the slug of the project to update.
        :param string name: the new name for the project.
        :param string slug: the new slug for the project.
        :param string team: the slug of new team for the project. Note, will be deprecated
                            soon when multiple teams can have access to a project.
        :param string platform: the new platform for the project.
        :param boolean isBookmarked: in case this API call is invoked with a
                                     user context this allows changing of
                                     the bookmark flag.
        :param int digestsMinDelay:
        :param int digestsMaxDelay:
        :auth: required
        """
        has_project_write = (
            (request.auth and request.auth.has_scope('project:write'))
            or (request.access and request.access.has_scope('project:write'))
        )

        changed_proj_settings = {}

        if has_project_write:
            serializer_cls = ProjectAdminSerializer
        else:
            serializer_cls = ProjectMemberSerializer

        serializer = serializer_cls(
            data=request.DATA,
            partial=True,
            context={
                'project': project,
                'request': request,
            },
        )
        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.object

        if not has_project_write:
            # options isn't part of the serializer, but should not be editable by members
            for key in chain(six.iterkeys(ProjectAdminSerializer.base_fields), ['options']):
                if request.DATA.get(key) and not result.get(key):
                    return Response(
                        {
                            'detail': ['You do not have permission to perform this action.']
                        },
                        status=403
                    )

        changed = False

        old_slug = None
        if result.get('slug'):
            old_slug = project.slug
            project.slug = result['slug']
            changed = True
            changed_proj_settings['new_slug'] = project.slug

        if result.get('name'):
            project.name = result['name']
            changed = True
            changed_proj_settings['new_project'] = project.name

        old_team_id = None
        new_team = None
        if result.get('team'):
            return Response(
                {
                    'detail': ['Editing a team via this endpoint has been deprecated.']
                },
                status=400
            )

        if result.get('platform'):
            project.platform = result['platform']
            changed = True

        if changed:
            project.save()
            if old_team_id is not None:
                ProjectTeam.objects.filter(
                    project=project,
                    team_id=old_team_id,
                ).update(team=new_team)

            if old_slug:
                ProjectRedirect.record(project, old_slug)

        if result.get('isBookmarked'):
            try:
                with transaction.atomic():
                    ProjectBookmark.objects.create(
                        project_id=project.id,
                        user=request.user,
                    )
            except IntegrityError:
                pass
        elif result.get('isBookmarked') is False:
            ProjectBookmark.objects.filter(
                project_id=project.id,
                user=request.user,
            ).delete()

        if result.get('digestsMinDelay'):
            project.update_option(
                'digests:mail:minimum_delay', result['digestsMinDelay'])
        if result.get('digestsMaxDelay'):
            project.update_option(
                'digests:mail:maximum_delay', result['digestsMaxDelay'])
        if result.get('subjectPrefix') is not None:
            if project.update_option('mail:subject_prefix',
                                     result['subjectPrefix']):
                changed_proj_settings['mail:subject_prefix'] = result['subjectPrefix']
        if result.get('subjectTemplate'):
            project.update_option('mail:subject_template',
                                  result['subjectTemplate'])
        if result.get('scrubIPAddresses') is not None:
            if project.update_option('sentry:scrub_ip_address', result['scrubIPAddresses']):
                changed_proj_settings['sentry:scrub_ip_address'] = result['scrubIPAddresses']
        if result.get('groupingConfig') is not None:
            if project.update_option('sentry:grouping_config', result['groupingConfig']):
                changed_proj_settings['sentry:grouping_config'] = result['groupingConfig']
        if result.get('groupingEnhancements') is not None:
            if project.update_option('sentry:grouping_enhancements',
                                     result['groupingEnhancements']):
                changed_proj_settings['sentry:grouping_enhancements'] = result['groupingEnhancements']
        if result.get('groupingEnhancementsBase') is not None:
            if project.update_option('sentry:grouping_enhancements_base',
                                     result['groupingEnhancementsBase']):
                changed_proj_settings['sentry:grouping_enhancements_base'] = result['groupingEnhancementsBase']
        if result.get('fingerprintingRules') is not None:
            if project.update_option('sentry:fingerprinting_rules', result['fingerprintingRules']):
                changed_proj_settings['sentry:fingerprinting_rules'] = result['fingerprintingRules']
        if result.get('securityToken') is not None:
            if project.update_option('sentry:token', result['securityToken']):
                changed_proj_settings['sentry:token'] = result['securityToken']
        if result.get('securityTokenHeader') is not None:
            if project.update_option('sentry:token_header', result['securityTokenHeader']):
                changed_proj_settings['sentry:token_header'] = result['securityTokenHeader']
        if result.get('verifySSL') is not None:
            if project.update_option('sentry:verify_ssl', result['verifySSL']):
                changed_proj_settings['sentry:verify_ssl'] = result['verifySSL']
        if result.get('dataScrubber') is not None:
            if project.update_option('sentry:scrub_data', result['dataScrubber']):
                changed_proj_settings['sentry:scrub_data'] = result['dataScrubber']
        if result.get('dataScrubberDefaults') is not None:
            if project.update_option('sentry:scrub_defaults', result['dataScrubberDefaults']):
                changed_proj_settings['sentry:scrub_defaults'] = result['dataScrubberDefaults']
        if result.get('sensitiveFields') is not None:
            if project.update_option('sentry:sensitive_fields', result['sensitiveFields']):
                changed_proj_settings['sentry:sensitive_fields'] = result['sensitiveFields']
        if result.get('safeFields') is not None:
            if project.update_option('sentry:safe_fields', result['safeFields']):
                changed_proj_settings['sentry:safe_fields'] = result['safeFields']
        if result.get('storeCrashReports') is not None:
            if project.update_option('sentry:store_crash_reports', result['storeCrashReports']):
                changed_proj_settings['sentry:store_crash_reports'] = result['storeCrashReports']
        if result.get('relayPiiConfig') is not None:
            if project.update_option('sentry:relay_pii_config', result['relayPiiConfig']):
                changed_proj_settings['sentry:relay_pii_config'] = result['relayPiiConfig'].strip(
                ) or None
        if result.get('builtinSymbolSources') is not None:
            if project.update_option('sentry:builtin_symbol_sources',
                                     result['builtinSymbolSources']):
                changed_proj_settings['sentry:builtin_symbol_sources'] = result['builtinSymbolSources']
        if result.get('symbolSources') is not None:
            if project.update_option('sentry:symbol_sources', result['symbolSources']):
                changed_proj_settings['sentry:symbol_sources'] = result['symbolSources'] or None
        if 'defaultEnvironment' in result:
            if result['defaultEnvironment'] is None:
                project.delete_option('sentry:default_environment')
            else:
                project.update_option('sentry:default_environment', result['defaultEnvironment'])
        # resolveAge can be None
        if 'resolveAge' in result:
            if project.update_option(
                'sentry:resolve_age',
                0 if result.get('resolveAge') is None else int(
                    result['resolveAge'])):
                changed_proj_settings['sentry:resolve_age'] = result['resolveAge']
        if result.get('scrapeJavaScript') is not None:
            if project.update_option('sentry:scrape_javascript', result['scrapeJavaScript']):
                changed_proj_settings['sentry:scrape_javascript'] = result['scrapeJavaScript']
        if result.get('allowedDomains'):
            if project.update_option('sentry:origins', result['allowedDomains']):
                changed_proj_settings['sentry:origins'] = result['allowedDomains']

        if result.get('isSubscribed'):
            UserOption.objects.set_value(
                user=request.user, key='mail:alert', value=1, project=project
            )
        elif result.get('isSubscribed') is False:
            UserOption.objects.set_value(
                user=request.user, key='mail:alert', value=0, project=project
            )

        # TODO(dcramer): rewrite options to use standard API config
        if has_project_write:
            options = request.DATA.get('options', {})
            if 'sentry:origins' in options:
                project.update_option(
                    'sentry:origins', clean_newline_inputs(
                        options['sentry:origins'])
                )
            if 'sentry:resolve_age' in options:
                project.update_option('sentry:resolve_age', int(
                    options['sentry:resolve_age']))
            if 'sentry:scrub_data' in options:
                project.update_option('sentry:scrub_data', bool(
                    options['sentry:scrub_data']))
            if 'sentry:scrub_defaults' in options:
                project.update_option(
                    'sentry:scrub_defaults', bool(
                        options['sentry:scrub_defaults'])
                )
            if 'sentry:safe_fields' in options:
                project.update_option(
                    'sentry:safe_fields',
                    [s.strip().lower() for s in options['sentry:safe_fields']]
                )
            if 'sentry:store_crash_reports' in options:
                project.update_option('sentry:store_crash_reports', bool(
                    options['sentry:store_crash_reports']))
            if 'sentry:relay_pii_config' in options:
                project.update_option('sentry:relay_pii_config',
                                      options['sentry:relay_pii_config'].strip() or None)
            if 'sentry:sensitive_fields' in options:
                project.update_option(
                    'sentry:sensitive_fields',
                    [s.strip().lower()
                     for s in options['sentry:sensitive_fields']]
                )
            if 'sentry:scrub_ip_address' in options:
                project.update_option(
                    'sentry:scrub_ip_address',
                    bool(options['sentry:scrub_ip_address']),
                )
            if 'sentry:grouping_config' in options:
                project.update_option(
                    'sentry:grouping_config',
                    options['sentry:grouping_config'],
                )
            if 'sentry:fingerprinting_rules' in options:
                project.update_option(
                    'sentry:fingerprinting_rules',
                    options['sentry:fingerprinting_rules'],
                )
            if 'mail:subject_prefix' in options:
                project.update_option(
                    'mail:subject_prefix',
                    options['mail:subject_prefix'],
                )
            if 'sentry:default_environment' in options:
                project.update_option(
                    'sentry:default_environment',
                    options['sentry:default_environment'],
                )
            if 'sentry:csp_ignored_sources_defaults' in options:
                project.update_option(
                    'sentry:csp_ignored_sources_defaults',
                    bool(options['sentry:csp_ignored_sources_defaults'])
                )
            if 'sentry:csp_ignored_sources' in options:
                project.update_option(
                    'sentry:csp_ignored_sources',
                    clean_newline_inputs(options['sentry:csp_ignored_sources'])
                )
            if 'sentry:blacklisted_ips' in options:
                project.update_option(
                    'sentry:blacklisted_ips',
                    clean_newline_inputs(options['sentry:blacklisted_ips']),
                )
            if 'feedback:branding' in options:
                project.update_option(
                    'feedback:branding', '1' if options['feedback:branding'] else '0'
                )
            if 'sentry:reprocessing_active' in options:
                project.update_option(
                    'sentry:reprocessing_active', bool(
                        options['sentry:reprocessing_active'])
                )
            if 'filters:blacklisted_ips' in options:
                project.update_option(
                    'sentry:blacklisted_ips',
                    clean_newline_inputs(options['filters:blacklisted_ips'])
                )
            if u'filters:{}'.format(FilterTypes.RELEASES) in options:
                if features.has('projects:custom-inbound-filters', project, actor=request.user):
                    project.update_option(
                        u'sentry:{}'.format(FilterTypes.RELEASES),
                        clean_newline_inputs(
                            options[u'filters:{}'.format(FilterTypes.RELEASES)])
                    )
                else:
                    return Response(
                        {
                            'detail': ['You do not have that feature enabled']
                        }, status=400
                    )
            if u'filters:{}'.format(FilterTypes.ERROR_MESSAGES) in options:
                if features.has('projects:custom-inbound-filters', project, actor=request.user):
                    project.update_option(
                        u'sentry:{}'.format(FilterTypes.ERROR_MESSAGES),
                        clean_newline_inputs(
                            options[u'filters:{}'.format(
                                FilterTypes.ERROR_MESSAGES)],
                            case_insensitive=False
                        )
                    )
                else:
                    return Response(
                        {
                            'detail': ['You do not have that feature enabled']
                        }, status=400
                    )
            if 'copy_from_project' in result:
                if not project.copy_settings_from(result['copy_from_project']):
                    return Response(
                        {
                            'detail': ['Copy project settings failed.']
                        }, status=409
                    )

            self.create_audit_entry(
                request=request,
                organization=project.organization,
                target_object=project.id,
                event=AuditLogEntryEvent.PROJECT_EDIT,
                data=changed_proj_settings
            )

        data = serialize(project, request.user, DetailedProjectSerializer())
        return Response(data)