You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

utils.ts 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { compact, isArray, uniq } from 'lodash';
  21. import { getUsers } from '../../api/users';
  22. import { formatMeasure } from '../../helpers/measures';
  23. import {
  24. cleanQuery,
  25. parseAsArray,
  26. parseAsBoolean,
  27. parseAsDate,
  28. parseAsString,
  29. queriesEqual,
  30. serializeDateShort,
  31. serializeString,
  32. serializeStringArray,
  33. } from '../../helpers/query';
  34. import { get, save } from '../../helpers/storage';
  35. import { isDefined } from '../../helpers/types';
  36. import {
  37. CleanCodeAttributeCategory,
  38. Facet,
  39. RawFacet,
  40. SoftwareImpactSeverity,
  41. SoftwareQuality,
  42. } from '../../types/issues';
  43. import { MetricType } from '../../types/metrics';
  44. import { SecurityStandard } from '../../types/security';
  45. import { Dict, Issue, Paging, RawQuery } from '../../types/types';
  46. import { RestUser } from '../../types/users';
  47. const OWASP_ASVS_4_0 = 'owaspAsvs-4.0';
  48. export interface Query {
  49. assigned: boolean;
  50. assignees: string[];
  51. author: string[];
  52. cleanCodeAttributeCategories: CleanCodeAttributeCategory[];
  53. codeVariants: string[];
  54. createdAfter: Date | undefined;
  55. createdAt: string;
  56. createdBefore: Date | undefined;
  57. createdInLast: string;
  58. cwe: string[];
  59. directories: string[];
  60. files: string[];
  61. impactSeverities: SoftwareImpactSeverity[];
  62. impactSoftwareQualities: SoftwareQuality[];
  63. issues: string[];
  64. languages: string[];
  65. owaspTop10: string[];
  66. 'owaspTop10-2021': string[];
  67. 'pciDss-3.2': string[];
  68. 'pciDss-4.0': string[];
  69. [OWASP_ASVS_4_0]: string[];
  70. owaspAsvsLevel: string;
  71. projects: string[];
  72. resolutions: string[];
  73. resolved: boolean;
  74. rules: string[];
  75. scopes: string[];
  76. severities: string[];
  77. inNewCodePeriod: boolean;
  78. sonarsourceSecurity: string[];
  79. sort: string;
  80. statuses: string[];
  81. tags: string[];
  82. types: string[];
  83. }
  84. export const STANDARDS = 'standards';
  85. // allow sorting by CREATION_DATE only
  86. const parseAsSort = (sort: string) => (sort === 'CREATION_DATE' ? 'CREATION_DATE' : '');
  87. const ISSUES_DEFAULT = 'sonarqube.issues.default';
  88. export function parseQuery(query: RawQuery): Query {
  89. return {
  90. assigned: parseAsBoolean(query.assigned),
  91. assignees: parseAsArray(query.assignees, parseAsString),
  92. author: isArray(query.author) ? query.author : [query.author].filter(isDefined),
  93. cleanCodeAttributeCategories: parseAsArray<CleanCodeAttributeCategory>(
  94. query.cleanCodeAttributeCategories,
  95. parseAsString
  96. ),
  97. createdAfter: parseAsDate(query.createdAfter),
  98. createdAt: parseAsString(query.createdAt),
  99. createdBefore: parseAsDate(query.createdBefore),
  100. createdInLast: parseAsString(query.createdInLast),
  101. cwe: parseAsArray(query.cwe, parseAsString),
  102. directories: parseAsArray(query.directories, parseAsString),
  103. files: parseAsArray(query.files, parseAsString),
  104. impactSeverities: parseImpactSeverityQuery(query.impactSeverities, query.severities),
  105. impactSoftwareQualities: parseAsArray<SoftwareQuality>(
  106. query.impactSoftwareQualities,
  107. parseAsString
  108. ),
  109. inNewCodePeriod: parseAsBoolean(query.inNewCodePeriod, false),
  110. issues: parseAsArray(query.issues, parseAsString),
  111. languages: parseAsArray(query.languages, parseAsString),
  112. owaspTop10: parseAsArray(query.owaspTop10, parseAsString),
  113. 'owaspTop10-2021': parseAsArray(query['owaspTop10-2021'], parseAsString),
  114. 'pciDss-3.2': parseAsArray(query['pciDss-3.2'], parseAsString),
  115. 'pciDss-4.0': parseAsArray(query['pciDss-4.0'], parseAsString),
  116. [OWASP_ASVS_4_0]: parseAsArray(query[OWASP_ASVS_4_0], parseAsString),
  117. owaspAsvsLevel: parseAsString(query['owaspAsvsLevel']),
  118. projects: parseAsArray(query.projects, parseAsString),
  119. resolutions: parseAsArray(query.resolutions, parseAsString),
  120. resolved: parseAsBoolean(query.resolved),
  121. rules: parseAsArray(query.rules, parseAsString),
  122. scopes: parseAsArray(query.scopes, parseAsString),
  123. severities: [],
  124. sonarsourceSecurity: parseAsArray(query.sonarsourceSecurity, parseAsString),
  125. sort: parseAsSort(query.s),
  126. statuses: parseAsArray(query.statuses, parseAsString),
  127. tags: parseAsArray(query.tags, parseAsString),
  128. types: parseAsArray(query.types, parseAsString),
  129. codeVariants: parseAsArray(query.codeVariants, parseAsString),
  130. };
  131. }
  132. function parseImpactSeverityQuery(
  133. newSeverities: string,
  134. oldSeverities?: string
  135. ): SoftwareImpactSeverity[] {
  136. const OLD_TO_NEW_MAPPER = {
  137. BLOCKER: SoftwareImpactSeverity.High,
  138. CRITICAL: SoftwareImpactSeverity.High,
  139. MAJOR: SoftwareImpactSeverity.Medium,
  140. MINOR: SoftwareImpactSeverity.Low,
  141. INFO: SoftwareImpactSeverity.Low,
  142. };
  143. // Merging new and old severities includes mapping for old to new
  144. return compact(
  145. uniq([
  146. ...parseAsArray<SoftwareImpactSeverity>(newSeverities, parseAsString),
  147. ...parseAsArray(oldSeverities, parseAsString).map(
  148. (oldSeverity: string) => OLD_TO_NEW_MAPPER[oldSeverity as keyof typeof OLD_TO_NEW_MAPPER]
  149. ),
  150. ])
  151. );
  152. }
  153. export function getOpen(query: RawQuery): string | undefined {
  154. return query.open;
  155. }
  156. export function getOpenIssue(props: { location: { query: RawQuery } }, issues: Issue[]) {
  157. const open = getOpen(props.location.query);
  158. return open ? issues.find((issue) => issue.key === open) : undefined;
  159. }
  160. export const areMyIssuesSelected = (query: RawQuery) => query.myIssues === 'true';
  161. export function serializeQuery(query: Query): RawQuery {
  162. const filter = {
  163. assigned: query.assigned ? undefined : 'false',
  164. assignees: serializeStringArray(query.assignees),
  165. author: query.author,
  166. cleanCodeAttributeCategories: serializeStringArray(query.cleanCodeAttributeCategories),
  167. createdAfter: serializeDateShort(query.createdAfter),
  168. createdAt: serializeString(query.createdAt),
  169. createdBefore: serializeDateShort(query.createdBefore),
  170. createdInLast: serializeString(query.createdInLast),
  171. cwe: serializeStringArray(query.cwe),
  172. directories: serializeStringArray(query.directories),
  173. files: serializeStringArray(query.files),
  174. issues: serializeStringArray(query.issues),
  175. languages: serializeStringArray(query.languages),
  176. owaspTop10: serializeStringArray(query.owaspTop10),
  177. 'owaspTop10-2021': serializeStringArray(query['owaspTop10-2021']),
  178. 'pciDss-3.2': serializeStringArray(query['pciDss-3.2']),
  179. 'pciDss-4.0': serializeStringArray(query['pciDss-4.0']),
  180. [OWASP_ASVS_4_0]: serializeStringArray(query[OWASP_ASVS_4_0]),
  181. owaspAsvsLevel: serializeString(query['owaspAsvsLevel']),
  182. projects: serializeStringArray(query.projects),
  183. resolutions: serializeStringArray(query.resolutions),
  184. resolved: query.resolved ? undefined : 'false',
  185. rules: serializeStringArray(query.rules),
  186. s: serializeString(query.sort),
  187. scopes: serializeStringArray(query.scopes),
  188. severities: undefined,
  189. impactSeverities: serializeStringArray(query.impactSeverities),
  190. impactSoftwareQualities: serializeStringArray(query.impactSoftwareQualities),
  191. inNewCodePeriod: query.inNewCodePeriod ? 'true' : undefined,
  192. sonarsourceSecurity: serializeStringArray(query.sonarsourceSecurity),
  193. statuses: serializeStringArray(query.statuses),
  194. tags: serializeStringArray(query.tags),
  195. types: serializeStringArray(query.types),
  196. codeVariants: serializeStringArray(query.codeVariants),
  197. };
  198. return cleanQuery(filter);
  199. }
  200. export const areQueriesEqual = (a: RawQuery, b: RawQuery) =>
  201. queriesEqual(parseQuery(a), parseQuery(b));
  202. export function parseFacets(facets?: RawFacet[]): Dict<Facet> {
  203. if (!facets) {
  204. return {};
  205. }
  206. const result: Dict<Facet> = {};
  207. facets.forEach((facet) => {
  208. const values: Facet = {};
  209. facet.values.forEach((value) => {
  210. values[value.val] = value.count;
  211. });
  212. result[facet.property] = values;
  213. });
  214. return result;
  215. }
  216. export function formatFacetStat(stat: number | undefined) {
  217. return stat && formatMeasure(stat, MetricType.ShortInteger);
  218. }
  219. export const searchAssignees = (
  220. query: string,
  221. page = 1
  222. ): Promise<{ paging: Paging; results: RestUser[] }> => {
  223. return getUsers<RestUser>({ pageIndex: page, q: query }).then(({ page, users }) => ({
  224. paging: page,
  225. results: users,
  226. }));
  227. };
  228. const LOCALSTORAGE_MY = 'my';
  229. const LOCALSTORAGE_ALL = 'all';
  230. export const isMySet = () => {
  231. return get(ISSUES_DEFAULT) === LOCALSTORAGE_MY;
  232. };
  233. export const saveMyIssues = (myIssues: boolean) =>
  234. save(ISSUES_DEFAULT, myIssues ? LOCALSTORAGE_MY : LOCALSTORAGE_ALL);
  235. export function getLocations(
  236. {
  237. flows,
  238. secondaryLocations,
  239. flowsWithType,
  240. }: Pick<Issue, 'flows' | 'secondaryLocations' | 'flowsWithType'>,
  241. selectedFlowIndex: number | undefined
  242. ) {
  243. if (secondaryLocations.length > 0) {
  244. return secondaryLocations;
  245. } else if (selectedFlowIndex !== undefined) {
  246. return flows[selectedFlowIndex] || flowsWithType[selectedFlowIndex]?.locations || [];
  247. }
  248. return [];
  249. }
  250. export function getSelectedLocation(
  251. issue: Pick<Issue, 'flows' | 'secondaryLocations' | 'flowsWithType'>,
  252. selectedFlowIndex: number | undefined,
  253. selectedLocationIndex: number | undefined
  254. ) {
  255. const locations = getLocations(issue, selectedFlowIndex);
  256. if (
  257. selectedLocationIndex !== undefined &&
  258. selectedLocationIndex >= 0 &&
  259. locations.length >= selectedLocationIndex
  260. ) {
  261. return locations[selectedLocationIndex];
  262. }
  263. return undefined;
  264. }
  265. export function allLocationsEmpty(
  266. issue: Pick<Issue, 'flows' | 'secondaryLocations' | 'flowsWithType'>,
  267. selectedFlowIndex: number | undefined
  268. ) {
  269. return getLocations(issue, selectedFlowIndex).every((location) => !location.msg);
  270. }
  271. export function shouldOpenStandardsFacet(
  272. openFacets: Dict<boolean>,
  273. query: Partial<Query>
  274. ): boolean {
  275. return (
  276. openFacets[STANDARDS] ||
  277. isFilteredBySecurityIssueTypes(query) ||
  278. isOneStandardChildFacetOpen(openFacets, query)
  279. );
  280. }
  281. export function shouldOpenStandardsChildFacet(
  282. openFacets: Dict<boolean>,
  283. query: Partial<Query>,
  284. standardType:
  285. | SecurityStandard.CWE
  286. | SecurityStandard.OWASP_TOP10
  287. | SecurityStandard.OWASP_TOP10_2021
  288. | SecurityStandard.SONARSOURCE
  289. ): boolean {
  290. const filter = query[standardType];
  291. return (
  292. openFacets[STANDARDS] !== false &&
  293. (openFacets[standardType] ||
  294. (standardType !== SecurityStandard.CWE && filter !== undefined && filter.length > 0))
  295. );
  296. }
  297. export function shouldOpenSonarSourceSecurityFacet(
  298. openFacets: Dict<boolean>,
  299. query: Partial<Query>
  300. ): boolean {
  301. // Open it by default if the parent is open, and no other standard is open.
  302. return (
  303. shouldOpenStandardsChildFacet(openFacets, query, SecurityStandard.SONARSOURCE) ||
  304. (shouldOpenStandardsFacet(openFacets, query) && !isOneStandardChildFacetOpen(openFacets, query))
  305. );
  306. }
  307. function isFilteredBySecurityIssueTypes(query: Partial<Query>): boolean {
  308. return query.types !== undefined && query.types.includes('VULNERABILITY');
  309. }
  310. function isOneStandardChildFacetOpen(openFacets: Dict<boolean>, query: Partial<Query>): boolean {
  311. return [SecurityStandard.OWASP_TOP10, SecurityStandard.CWE, SecurityStandard.SONARSOURCE].some(
  312. (
  313. standardType:
  314. | SecurityStandard.CWE
  315. | SecurityStandard.OWASP_TOP10
  316. | SecurityStandard.OWASP_TOP10_2021
  317. | SecurityStandard.SONARSOURCE
  318. ) => shouldOpenStandardsChildFacet(openFacets, query, standardType)
  319. );
  320. }