projectSecurityAndPrivacyGroups.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import {hasEveryAccess} from 'sentry/components/acl/access';
  2. import type {JsonFormObject} from 'sentry/components/forms/types';
  3. import Link from 'sentry/components/links/link';
  4. import {t, tct} from 'sentry/locale';
  5. import type {Organization} from 'sentry/types/organization';
  6. import type {Project} from 'sentry/types/project';
  7. import {convertMultilineFieldValue, extractMultilineFields} from 'sentry/utils';
  8. import {
  9. formatStoreCrashReports,
  10. getStoreCrashReportsValues,
  11. SettingScope,
  12. } from 'sentry/utils/crashReports';
  13. // Export route to make these forms searchable by label/help
  14. export const route = '/settings/:orgId/projects/:projectId/security-and-privacy/';
  15. // Check if a field has been set AND IS TRUTHY at the organization level.
  16. const hasOrgOverride = ({
  17. organization,
  18. name,
  19. }: {
  20. name: string;
  21. organization: Organization;
  22. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  23. }) => organization[name];
  24. function hasProjectWriteAndOrgOverride({
  25. organization,
  26. project,
  27. name,
  28. }: {
  29. name: string;
  30. organization: Organization;
  31. project: Project;
  32. }) {
  33. if (hasOrgOverride({organization, name})) {
  34. return true;
  35. }
  36. return !hasEveryAccess(['project:write'], {organization, project});
  37. }
  38. function projectWriteAndOrgOverrideDisabledReason({
  39. organization,
  40. name,
  41. project,
  42. }: {
  43. name: string;
  44. organization: Organization;
  45. project: Project;
  46. }) {
  47. if (hasOrgOverride({organization, name})) {
  48. return t(
  49. "This option is enforced by your organization's settings and cannot be customized per-project."
  50. );
  51. }
  52. if (!hasEveryAccess(['project:write'], {organization, project})) {
  53. return t("You do not have permission to modify this project's setting.");
  54. }
  55. return null;
  56. }
  57. const formGroups: JsonFormObject[] = [
  58. {
  59. title: t('Security & Privacy'),
  60. fields: [
  61. {
  62. // The project settings cannot be overridden by the organization settings.
  63. // This field is only disabled if the user does not have permission to edit.
  64. name: 'storeCrashReports',
  65. type: 'select',
  66. label: t('Store Minidumps As Attachments'),
  67. help: ({organization}) =>
  68. tct(
  69. 'Store minidumps as attachments for improved processing and download in issue details. Overrides [organizationSettingsLink: organization settings].',
  70. {
  71. organizationSettingsLink: (
  72. <Link to={`/settings/${organization.slug}/security-and-privacy/`} />
  73. ),
  74. }
  75. ),
  76. visible: ({features}) => features.has('event-attachments'),
  77. placeholder: ({organization, name, model}) => {
  78. const value = model.getValue(name);
  79. // empty value means that this project should inherit organization settings
  80. if (value === null) {
  81. return tct('Inherit organization settings ([organizationValue])', {
  82. organizationValue: formatStoreCrashReports(organization.storeCrashReports),
  83. });
  84. }
  85. // HACK: some organization can have limit of stored crash reports a number that's not in the options (legacy reasons),
  86. // we therefore display it in a placeholder
  87. return formatStoreCrashReports(value);
  88. },
  89. choices: ({organization}) =>
  90. getStoreCrashReportsValues(SettingScope.PROJECT).map(value => [
  91. value,
  92. formatStoreCrashReports(value, organization.storeCrashReports),
  93. ]),
  94. },
  95. ],
  96. },
  97. {
  98. title: t('Data Scrubbing'),
  99. fields: [
  100. {
  101. name: 'dataScrubber',
  102. type: 'boolean',
  103. label: t('Data Scrubber'),
  104. disabled: hasProjectWriteAndOrgOverride,
  105. disabledReason: projectWriteAndOrgOverrideDisabledReason,
  106. help: t('Enable server-side data scrubbing'),
  107. 'aria-label': t('Enable server-side data scrubbing'),
  108. // `props` are the props given to FormField
  109. setValue: (val, props) => props.organization?.[props.name] || val,
  110. confirm: {
  111. false: t('Are you sure you want to disable server-side data scrubbing?'),
  112. },
  113. },
  114. {
  115. name: 'dataScrubberDefaults',
  116. type: 'boolean',
  117. disabled: hasProjectWriteAndOrgOverride,
  118. disabledReason: projectWriteAndOrgOverrideDisabledReason,
  119. label: t('Use Default Scrubbers'),
  120. help: t(
  121. 'Apply default scrubbers to prevent things like passwords and credit cards from being stored'
  122. ),
  123. 'aria-label': t(
  124. 'Enable to apply default scrubbers to prevent things like passwords and credit cards from being stored'
  125. ),
  126. // `props` are the props given to FormField
  127. setValue: (val, props) => props.organization?.[props.name] || val,
  128. confirm: {
  129. false: t('Are you sure you want to disable using default scrubbers?'),
  130. },
  131. },
  132. {
  133. name: 'scrubIPAddresses',
  134. type: 'boolean',
  135. disabled: hasProjectWriteAndOrgOverride,
  136. disabledReason: projectWriteAndOrgOverrideDisabledReason,
  137. // `props` are the props given to FormField
  138. setValue: (val, props) => props.organization?.[props.name] || val,
  139. label: t('Prevent Storing of IP Addresses'),
  140. help: t('Preventing IP addresses from being stored for new events'),
  141. 'aria-label': t(
  142. 'Enable to prevent IP addresses from being stored for new events'
  143. ),
  144. confirm: {
  145. false: t('Are you sure you want to disable scrubbing IP addresses?'),
  146. },
  147. },
  148. {
  149. name: 'sensitiveFields',
  150. type: 'string',
  151. multiline: true,
  152. autosize: true,
  153. maxRows: 10,
  154. rows: 1,
  155. placeholder: t('email'),
  156. label: t('Additional Sensitive Fields'),
  157. help: t(
  158. 'Additional field names to match against when scrubbing data. Separate multiple entries with a newline'
  159. ),
  160. 'aria-label': t(
  161. 'Enter additional field names to match against when scrubbing data. Separate multiple entries with a newline'
  162. ),
  163. getValue: val => extractMultilineFields(val),
  164. setValue: val => convertMultilineFieldValue(val),
  165. },
  166. {
  167. name: 'safeFields',
  168. type: 'string',
  169. multiline: true,
  170. autosize: true,
  171. maxRows: 10,
  172. rows: 1,
  173. placeholder: t('business-email'),
  174. label: t('Safe Fields'),
  175. help: t(
  176. 'Field names which data scrubbers should ignore. Separate multiple entries with a newline'
  177. ),
  178. 'aria-label': t(
  179. 'Enter field names which data scrubbers should ignore. Separate multiple entries with a newline'
  180. ),
  181. getValue: val => extractMultilineFields(val),
  182. setValue: val => convertMultilineFieldValue(val),
  183. },
  184. ],
  185. },
  186. ];
  187. export default formGroups;