def get_max_crashreports(model): value = model.get_option("sentry:store_crash_reports") return convert_crashreport_count(value)
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 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"), "symbolSources": attrs["options"].get("sentry:symbol_sources"), "dynamicSampling": get_value_with_default("sentry:dynamic_sampling"), "breakdowns": get_value_with_default("sentry:breakdowns"), }) return data
def serialize(self, obj, attrs, user, access): from sentry import experiments onboarding_tasks = list( OrganizationOnboardingTask.objects.filter( organization=obj).select_related("user")) experiment_assignments = experiments.all(org=obj, actor=user) context = super(DetailedOrganizationSerializer, self).serialize(obj, attrs, user) max_rate = quotas.get_maximum_quota(obj) context["experiments"] = experiment_assignments context["quota"] = { "maxRate": max_rate[0], "maxRateInterval": max_rate[1], "accountLimit": int( OrganizationOption.objects.get_value( organization=obj, key="sentry:account-rate-limit", default=ACCOUNT_RATE_LIMIT_DEFAULT, )), "projectLimit": int( OrganizationOption.objects.get_value( organization=obj, key="sentry:project-rate-limit", default=PROJECT_RATE_LIMIT_DEFAULT, )), } context.update({ "isDefault": obj.is_default, "defaultRole": obj.default_role, "availableRoles": [{ "id": r.id, "name": r.name } for r in roles.get_all()], "openMembership": bool(obj.flags.allow_joinleave), "require2FA": bool(obj.flags.require_2fa), "allowSharedIssues": not obj.flags.disable_shared_issues, "enhancedPrivacy": bool(obj.flags.enhanced_privacy), "dataScrubber": bool( obj.get_option("sentry:require_scrub_data", REQUIRE_SCRUB_DATA_DEFAULT)), "dataScrubberDefaults": bool( obj.get_option("sentry:require_scrub_defaults", REQUIRE_SCRUB_DEFAULTS_DEFAULT)), "sensitiveFields": obj.get_option("sentry:sensitive_fields", SENSITIVE_FIELDS_DEFAULT) or [], "safeFields": obj.get_option("sentry:safe_fields", SAFE_FIELDS_DEFAULT) or [], "storeCrashReports": convert_crashreport_count( obj.get_option("sentry:store_crash_reports")), "attachmentsRole": six.text_type( obj.get_option("sentry:attachments_role", ATTACHMENTS_ROLE_DEFAULT)), "debugFilesRole": six.text_type( obj.get_option("sentry:debug_files_role", DEBUG_FILES_ROLE_DEFAULT)), "eventsMemberAdmin": bool( obj.get_option("sentry:events_member_admin", EVENTS_MEMBER_ADMIN_DEFAULT)), "scrubIPAddresses": bool( obj.get_option("sentry:require_scrub_ip_address", REQUIRE_SCRUB_IP_ADDRESS_DEFAULT)), "scrapeJavaScript": bool( obj.get_option("sentry:scrape_javascript", SCRAPE_JAVASCRIPT_DEFAULT)), "allowJoinRequests": bool(obj.get_option("sentry:join_requests", JOIN_REQUESTS_DEFAULT)), "relayPiiConfig": six.text_type(obj.get_option("sentry:relay_pii_config") or u"") or None, "apdexThreshold": int( obj.get_option("sentry:apdex_threshold", APDEX_THRESHOLD_DEFAULT)), }) trusted_relays_raw = obj.get_option("sentry:trusted-relays") or [] # serialize trusted relays info into their external form context["trustedRelays"] = [ TrustedRelaySerializer(raw).data for raw in trusted_relays_raw ] context["access"] = access.scopes if access.role is not None: context["role"] = access.role context[ "pendingAccessRequests"] = OrganizationAccessRequest.objects.filter( team__organization=obj).count() context["onboardingTasks"] = serialize(onboarding_tasks, user, OnboardingTasksSerializer()) return context
def get_max_crashreports(model, allow_none=False): value = model.get_option("sentry:store_crash_reports") return convert_crashreport_count(value, allow_none=allow_none)