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)
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()
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
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()
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)
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)
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)