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.

IssueIndex.java 76KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445
  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. package org.sonar.server.issue.index;
  21. import com.google.common.base.Preconditions;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Collection;
  25. import java.util.Collections;
  26. import java.util.Date;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Objects;
  30. import java.util.Optional;
  31. import java.util.OptionalInt;
  32. import java.util.OptionalLong;
  33. import java.util.Set;
  34. import java.util.function.Consumer;
  35. import java.util.function.Function;
  36. import java.util.stream.Collectors;
  37. import java.util.stream.IntStream;
  38. import java.util.stream.Stream;
  39. import javax.annotation.CheckForNull;
  40. import javax.annotation.Nullable;
  41. import org.apache.commons.lang.StringUtils;
  42. import org.apache.lucene.search.join.ScoreMode;
  43. import org.elasticsearch.action.search.SearchRequest;
  44. import org.elasticsearch.action.search.SearchResponse;
  45. import org.elasticsearch.index.query.BoolQueryBuilder;
  46. import org.elasticsearch.index.query.QueryBuilder;
  47. import org.elasticsearch.index.query.QueryBuilders;
  48. import org.elasticsearch.indices.TermsLookup;
  49. import org.elasticsearch.search.aggregations.AggregationBuilder;
  50. import org.elasticsearch.search.aggregations.AggregationBuilders;
  51. import org.elasticsearch.search.aggregations.BucketOrder;
  52. import org.elasticsearch.search.aggregations.HasAggregations;
  53. import org.elasticsearch.search.aggregations.bucket.filter.FilterAggregationBuilder;
  54. import org.elasticsearch.search.aggregations.bucket.filter.FiltersAggregator;
  55. import org.elasticsearch.search.aggregations.bucket.filter.ParsedFilter;
  56. import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
  57. import org.elasticsearch.search.aggregations.bucket.histogram.LongBounds;
  58. import org.elasticsearch.search.aggregations.bucket.terms.IncludeExclude;
  59. import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
  60. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  61. import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
  62. import org.elasticsearch.search.aggregations.metrics.Min;
  63. import org.elasticsearch.search.aggregations.metrics.ParsedMax;
  64. import org.elasticsearch.search.aggregations.metrics.ParsedValueCount;
  65. import org.elasticsearch.search.aggregations.metrics.SumAggregationBuilder;
  66. import org.elasticsearch.search.builder.SearchSourceBuilder;
  67. import org.elasticsearch.search.sort.FieldSortBuilder;
  68. import org.joda.time.Duration;
  69. import org.sonar.api.issue.Issue;
  70. import org.sonar.api.issue.impact.SoftwareQuality;
  71. import org.sonar.api.rule.Severity;
  72. import org.sonar.api.rules.CleanCodeAttributeCategory;
  73. import org.sonar.api.rules.RuleType;
  74. import org.sonar.api.server.rule.RulesDefinition;
  75. import org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version;
  76. import org.sonar.api.server.rule.RulesDefinition.PciDssVersion;
  77. import org.sonar.api.utils.DateUtils;
  78. import org.sonar.api.utils.System2;
  79. import org.sonar.core.issue.status.IssueStatus;
  80. import org.sonar.server.es.EsClient;
  81. import org.sonar.server.es.EsUtils;
  82. import org.sonar.server.es.SearchOptions;
  83. import org.sonar.server.es.Sorting;
  84. import org.sonar.server.es.searchrequest.RequestFiltersComputer;
  85. import org.sonar.server.es.searchrequest.RequestFiltersComputer.AllFilters;
  86. import org.sonar.server.es.searchrequest.SimpleFieldTopAggregationDefinition;
  87. import org.sonar.server.es.searchrequest.SubAggregationHelper;
  88. import org.sonar.server.es.searchrequest.TopAggregationDefinition;
  89. import org.sonar.server.es.searchrequest.TopAggregationDefinition.FilterScope;
  90. import org.sonar.server.es.searchrequest.TopAggregationDefinition.SimpleFieldFilterScope;
  91. import org.sonar.server.es.searchrequest.TopAggregationHelper;
  92. import org.sonar.server.issue.index.IssueQuery.PeriodStart;
  93. import org.sonar.server.permission.index.AuthorizationDoc;
  94. import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
  95. import org.sonar.server.security.SecurityStandards;
  96. import org.sonar.server.security.SecurityStandards.PciDss;
  97. import org.sonar.server.security.SecurityStandards.SQCategory;
  98. import org.sonar.server.user.UserSession;
  99. import org.sonar.server.view.index.ViewIndexDefinition;
  100. import org.springframework.util.CollectionUtils;
  101. import static com.google.common.base.Preconditions.checkState;
  102. import static java.lang.String.format;
  103. import static java.util.Collections.singletonList;
  104. import static java.util.stream.Collectors.toCollection;
  105. import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
  106. import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
  107. import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
  108. import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
  109. import static org.elasticsearch.index.query.QueryBuilders.prefixQuery;
  110. import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
  111. import static org.elasticsearch.index.query.QueryBuilders.termQuery;
  112. import static org.elasticsearch.index.query.QueryBuilders.termsQuery;
  113. import static org.elasticsearch.search.aggregations.AggregationBuilders.filters;
  114. import static org.elasticsearch.search.aggregations.AggregationBuilders.reverseNested;
  115. import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
  116. import static org.sonar.api.rules.RuleType.VULNERABILITY;
  117. import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
  118. import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
  119. import static org.sonar.server.es.searchrequest.TopAggregationDefinition.NON_STICKY;
  120. import static org.sonar.server.es.searchrequest.TopAggregationDefinition.STICKY;
  121. import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_EXTRA_FILTER;
  122. import static org.sonar.server.es.searchrequest.TopAggregationHelper.NO_OTHER_SUBAGGREGATION;
  123. import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNED_TO_ME;
  124. import static org.sonar.server.issue.index.IssueIndex.Facet.ASSIGNEES;
  125. import static org.sonar.server.issue.index.IssueIndex.Facet.AUTHOR;
  126. import static org.sonar.server.issue.index.IssueIndex.Facet.CLEAN_CODE_ATTRIBUTE_CATEGORY;
  127. import static org.sonar.server.issue.index.IssueIndex.Facet.CODE_VARIANTS;
  128. import static org.sonar.server.issue.index.IssueIndex.Facet.CREATED_AT;
  129. import static org.sonar.server.issue.index.IssueIndex.Facet.CWE;
  130. import static org.sonar.server.issue.index.IssueIndex.Facet.DIRECTORIES;
  131. import static org.sonar.server.issue.index.IssueIndex.Facet.FILES;
  132. import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SEVERITY;
  133. import static org.sonar.server.issue.index.IssueIndex.Facet.IMPACT_SOFTWARE_QUALITY;
  134. import static org.sonar.server.issue.index.IssueIndex.Facet.LANGUAGES;
  135. import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_ASVS_40;
  136. import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10;
  137. import static org.sonar.server.issue.index.IssueIndex.Facet.OWASP_TOP_10_2021;
  138. import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_32;
  139. import static org.sonar.server.issue.index.IssueIndex.Facet.PCI_DSS_40;
  140. import static org.sonar.server.issue.index.IssueIndex.Facet.PROJECT_UUIDS;
  141. import static org.sonar.server.issue.index.IssueIndex.Facet.RESOLUTIONS;
  142. import static org.sonar.server.issue.index.IssueIndex.Facet.RULES;
  143. import static org.sonar.server.issue.index.IssueIndex.Facet.SANS_TOP_25;
  144. import static org.sonar.server.issue.index.IssueIndex.Facet.SCOPES;
  145. import static org.sonar.server.issue.index.IssueIndex.Facet.SEVERITIES;
  146. import static org.sonar.server.issue.index.IssueIndex.Facet.ISSUE_STATUSES;
  147. import static org.sonar.server.issue.index.IssueIndex.Facet.SONARSOURCE_SECURITY;
  148. import static org.sonar.server.issue.index.IssueIndex.Facet.STATUSES;
  149. import static org.sonar.server.issue.index.IssueIndex.Facet.TAGS;
  150. import static org.sonar.server.issue.index.IssueIndex.Facet.TYPES;
  151. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID;
  152. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN;
  153. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID;
  154. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY;
  155. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CODE_VARIANTS;
  156. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID;
  157. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_CWE;
  158. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH;
  159. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_EFFORT;
  160. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FILE_PATH;
  161. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CLOSED_AT;
  162. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_CREATED_AT;
  163. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_FUNC_UPDATED_AT;
  164. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACTS;
  165. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SEVERITY;
  166. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY;
  167. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH;
  168. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_KEY;
  169. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LANGUAGE;
  170. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_LINE;
  171. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_CODE_REFERENCE;
  172. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_ASVS_40;
  173. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10;
  174. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_OWASP_TOP_10_2021;
  175. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_32;
  176. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PCI_DSS_40;
  177. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID;
  178. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RESOLUTION;
  179. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_RULE_UUID;
  180. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SANS_TOP_25;
  181. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SCOPE;
  182. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY;
  183. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SEVERITY_VALUE;
  184. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_NEW_STATUS;
  185. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_SQ_SECURITY_CATEGORY;
  186. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_STATUS;
  187. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TAGS;
  188. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_TYPE;
  189. import static org.sonar.server.issue.index.IssueIndexDefinition.FIELD_ISSUE_VULNERABILITY_PROBABILITY;
  190. import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
  191. import static org.sonar.server.security.SecurityReviewRating.computePercent;
  192. import static org.sonar.server.security.SecurityReviewRating.computeRating;
  193. import static org.sonar.server.security.SecurityStandards.CWES_BY_CWE_TOP_25;
  194. import static org.sonar.server.security.SecurityStandards.OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL;
  195. import static org.sonar.server.view.index.ViewIndexDefinition.TYPE_VIEW;
  196. import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
  197. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES;
  198. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_AUTHOR;
  199. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES;
  200. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CODE_VARIANTS;
  201. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AT;
  202. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CWE;
  203. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_DIRECTORIES;
  204. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_FILES;
  205. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SEVERITIES;
  206. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_IMPACT_SOFTWARE_QUALITIES;
  207. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_LANGUAGES;
  208. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_ASVS_40;
  209. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10;
  210. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_OWASP_TOP_10_2021;
  211. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_32;
  212. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PCI_DSS_40;
  213. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RESOLUTIONS;
  214. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES;
  215. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SANS_TOP_25;
  216. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SCOPES;
  217. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SEVERITIES;
  218. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE_STATUSES;
  219. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SONARSOURCE_SECURITY;
  220. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_STATUSES;
  221. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
  222. import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TYPES;
  223. /**
  224. * The unique entry-point to interact with Elasticsearch index "issues".
  225. * All the requests are listed here.
  226. */
  227. public class IssueIndex {
  228. public static final String FACET_PROJECTS = "projects";
  229. public static final String FACET_ASSIGNED_TO_ME = "assigned_to_me";
  230. private static final int DEFAULT_FACET_SIZE = 15;
  231. private static final int MAX_FACET_SIZE = 100;
  232. private static final String AGG_VULNERABILITIES = "vulnerabilities";
  233. private static final String AGG_SEVERITIES = "severities";
  234. private static final String AGG_TO_REVIEW_SECURITY_HOTSPOTS = "toReviewSecurityHotspots";
  235. private static final String AGG_IN_REVIEW_SECURITY_HOTSPOTS = "inReviewSecurityHotspots";
  236. private static final String AGG_REVIEWED_SECURITY_HOTSPOTS = "reviewedSecurityHotspots";
  237. private static final String AGG_DISTRIBUTION = "distribution";
  238. private static final BoolQueryBuilder NON_RESOLVED_VULNERABILITIES_FILTER = boolQuery()
  239. .filter(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name()))
  240. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
  241. private static final BoolQueryBuilder IN_REVIEW_HOTSPOTS_FILTER = boolQuery()
  242. .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
  243. .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_IN_REVIEW))
  244. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
  245. private static final BoolQueryBuilder TO_REVIEW_HOTSPOTS_FILTER = boolQuery()
  246. .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
  247. .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_TO_REVIEW))
  248. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION));
  249. private static final BoolQueryBuilder REVIEWED_HOTSPOTS_FILTER = boolQuery()
  250. .filter(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name()))
  251. .filter(termQuery(FIELD_ISSUE_STATUS, Issue.STATUS_REVIEWED))
  252. .filter(termQuery(FIELD_ISSUE_RESOLUTION, Issue.RESOLUTION_FIXED));
  253. private static final Object[] NO_SELECTED_VALUES = {0};
  254. private static final SimpleFieldTopAggregationDefinition EFFORT_TOP_AGGREGATION = new SimpleFieldTopAggregationDefinition(FIELD_ISSUE_EFFORT, NON_STICKY);
  255. public enum Facet {
  256. SEVERITIES(PARAM_SEVERITIES, FIELD_ISSUE_SEVERITY, STICKY, Severity.ALL.size()),
  257. IMPACT_SOFTWARE_QUALITY(PARAM_IMPACT_SOFTWARE_QUALITIES, FIELD_ISSUE_IMPACTS, STICKY),
  258. IMPACT_SEVERITY(PARAM_IMPACT_SEVERITIES, FIELD_ISSUE_IMPACTS, STICKY),
  259. CLEAN_CODE_ATTRIBUTE_CATEGORY(PARAM_CLEAN_CODE_ATTRIBUTE_CATEGORIES, FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, STICKY, CleanCodeAttributeCategory.values().length),
  260. STATUSES(PARAM_STATUSES, FIELD_ISSUE_STATUS, STICKY, Issue.STATUSES.size()),
  261. // Resolutions facet returns one more element than the number of resolutions to take into account unresolved issues
  262. RESOLUTIONS(PARAM_RESOLUTIONS, FIELD_ISSUE_RESOLUTION, STICKY, Issue.RESOLUTIONS.size() + 1),
  263. ISSUE_STATUSES(PARAM_ISSUE_STATUSES, FIELD_ISSUE_NEW_STATUS, STICKY, IssueStatus.values().length),
  264. TYPES(PARAM_TYPES, FIELD_ISSUE_TYPE, STICKY, RuleType.values().length),
  265. SCOPES(PARAM_SCOPES, FIELD_ISSUE_SCOPE, STICKY, MAX_FACET_SIZE),
  266. LANGUAGES(PARAM_LANGUAGES, FIELD_ISSUE_LANGUAGE, STICKY, MAX_FACET_SIZE),
  267. RULES(PARAM_RULES, FIELD_ISSUE_RULE_UUID, STICKY, MAX_FACET_SIZE),
  268. TAGS(PARAM_TAGS, FIELD_ISSUE_TAGS, STICKY, MAX_FACET_SIZE),
  269. AUTHOR(PARAM_AUTHOR, FIELD_ISSUE_AUTHOR_LOGIN, STICKY, MAX_FACET_SIZE),
  270. PROJECT_UUIDS(FACET_PROJECTS, FIELD_ISSUE_PROJECT_UUID, STICKY, MAX_FACET_SIZE),
  271. FILES(PARAM_FILES, FIELD_ISSUE_FILE_PATH, STICKY, MAX_FACET_SIZE),
  272. DIRECTORIES(PARAM_DIRECTORIES, FIELD_ISSUE_DIRECTORY_PATH, STICKY, MAX_FACET_SIZE),
  273. ASSIGNEES(PARAM_ASSIGNEES, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, MAX_FACET_SIZE),
  274. ASSIGNED_TO_ME(FACET_ASSIGNED_TO_ME, FIELD_ISSUE_ASSIGNEE_UUID, STICKY, 1),
  275. PCI_DSS_32(PARAM_PCI_DSS_32, FIELD_ISSUE_PCI_DSS_32, STICKY, DEFAULT_FACET_SIZE),
  276. PCI_DSS_40(PARAM_PCI_DSS_40, FIELD_ISSUE_PCI_DSS_40, STICKY, DEFAULT_FACET_SIZE),
  277. OWASP_ASVS_40(PARAM_OWASP_ASVS_40, FIELD_ISSUE_OWASP_ASVS_40, STICKY, DEFAULT_FACET_SIZE),
  278. OWASP_TOP_10(PARAM_OWASP_TOP_10, FIELD_ISSUE_OWASP_TOP_10, STICKY, DEFAULT_FACET_SIZE),
  279. OWASP_TOP_10_2021(PARAM_OWASP_TOP_10_2021, FIELD_ISSUE_OWASP_TOP_10_2021, STICKY, DEFAULT_FACET_SIZE),
  280. SANS_TOP_25(PARAM_SANS_TOP_25, FIELD_ISSUE_SANS_TOP_25, STICKY, DEFAULT_FACET_SIZE),
  281. CWE(PARAM_CWE, FIELD_ISSUE_CWE, STICKY, DEFAULT_FACET_SIZE),
  282. CREATED_AT(PARAM_CREATED_AT, FIELD_ISSUE_FUNC_CREATED_AT, NON_STICKY),
  283. SONARSOURCE_SECURITY(PARAM_SONARSOURCE_SECURITY, FIELD_ISSUE_SQ_SECURITY_CATEGORY, STICKY, DEFAULT_FACET_SIZE),
  284. CODE_VARIANTS(PARAM_CODE_VARIANTS, FIELD_ISSUE_CODE_VARIANTS, STICKY, MAX_FACET_SIZE);
  285. private final String name;
  286. private final TopAggregationDefinition<FilterScope> topAggregation;
  287. private final Integer numberOfTerms;
  288. Facet(String name, String fieldName, boolean sticky, int numberOfTerms) {
  289. this.name = name;
  290. this.topAggregation = new SimpleFieldTopAggregationDefinition(fieldName, sticky);
  291. this.numberOfTerms = numberOfTerms;
  292. }
  293. Facet(String name, String fieldName, boolean sticky) {
  294. this.name = name;
  295. this.topAggregation = new SimpleFieldTopAggregationDefinition(fieldName, sticky);
  296. this.numberOfTerms = null;
  297. }
  298. public String getName() {
  299. return name;
  300. }
  301. public String getFieldName() {
  302. return topAggregation.getFilterScope().getFieldName();
  303. }
  304. public FilterScope getFilterScope() {
  305. return topAggregation.getFilterScope();
  306. }
  307. public TopAggregationDefinition<FilterScope> getTopAggregationDef() {
  308. return topAggregation;
  309. }
  310. public int getNumberOfTerms() {
  311. checkState(numberOfTerms != null, "numberOfTerms should have been provided in constructor");
  312. return numberOfTerms;
  313. }
  314. }
  315. private static final Map<String, Facet> FACETS_BY_NAME = Arrays.stream(Facet.values())
  316. .collect(Collectors.toMap(Facet::getName, Function.identity()));
  317. private static final String SUBSTRING_MATCH_REGEXP = ".*%s.*";
  318. // TODO to be documented
  319. // TODO move to Facets ?
  320. private static final String FACET_SUFFIX_MISSING = "_missing";
  321. private static final String IS_ASSIGNED_FILTER = "__isAssigned";
  322. private static final SumAggregationBuilder EFFORT_AGGREGATION = AggregationBuilders.sum(FACET_MODE_EFFORT).field(FIELD_ISSUE_EFFORT);
  323. private static final BucketOrder EFFORT_AGGREGATION_ORDER = BucketOrder.aggregation(FACET_MODE_EFFORT, false);
  324. private static final Duration TWENTY_DAYS = Duration.standardDays(20L);
  325. private static final Duration TWENTY_WEEKS = Duration.standardDays(20L * 7L);
  326. private static final Duration TWENTY_MONTHS = Duration.standardDays(20L * 30L);
  327. private static final String AGG_COUNT = "count";
  328. private final Sorting sorting;
  329. private final EsClient client;
  330. private final System2 system;
  331. private final UserSession userSession;
  332. private final WebAuthorizationTypeSupport authorizationTypeSupport;
  333. public IssueIndex(EsClient client, System2 system, UserSession userSession, WebAuthorizationTypeSupport authorizationTypeSupport) {
  334. this.client = client;
  335. this.system = system;
  336. this.userSession = userSession;
  337. this.authorizationTypeSupport = authorizationTypeSupport;
  338. this.sorting = new Sorting();
  339. this.sorting.add(IssueQuery.SORT_BY_STATUS, FIELD_ISSUE_STATUS);
  340. this.sorting.add(IssueQuery.SORT_BY_STATUS, FIELD_ISSUE_KEY);
  341. this.sorting.add(IssueQuery.SORT_BY_SEVERITY, FIELD_ISSUE_SEVERITY_VALUE);
  342. this.sorting.add(IssueQuery.SORT_BY_SEVERITY, FIELD_ISSUE_KEY);
  343. this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, FIELD_ISSUE_FUNC_CREATED_AT);
  344. this.sorting.add(IssueQuery.SORT_BY_CREATION_DATE, FIELD_ISSUE_KEY);
  345. this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, FIELD_ISSUE_FUNC_UPDATED_AT);
  346. this.sorting.add(IssueQuery.SORT_BY_UPDATE_DATE, FIELD_ISSUE_KEY);
  347. this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, FIELD_ISSUE_FUNC_CLOSED_AT);
  348. this.sorting.add(IssueQuery.SORT_BY_CLOSE_DATE, FIELD_ISSUE_KEY);
  349. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_PROJECT_UUID);
  350. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_FILE_PATH);
  351. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_LINE);
  352. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_SEVERITY_VALUE).reverse();
  353. this.sorting.add(IssueQuery.SORT_BY_FILE_LINE, FIELD_ISSUE_KEY);
  354. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_VULNERABILITY_PROBABILITY).reverse();
  355. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_SQ_SECURITY_CATEGORY);
  356. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_RULE_UUID);
  357. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_PROJECT_UUID);
  358. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_FILE_PATH);
  359. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_LINE);
  360. this.sorting.add(IssueQuery.SORT_HOTSPOTS, FIELD_ISSUE_KEY);
  361. // by default order by created date, project, file, line and issue key (in order to be deterministic when same ms)
  362. this.sorting.addDefault(FIELD_ISSUE_FUNC_CREATED_AT).reverse();
  363. this.sorting.addDefault(FIELD_ISSUE_PROJECT_UUID);
  364. this.sorting.addDefault(FIELD_ISSUE_FILE_PATH);
  365. this.sorting.addDefault(FIELD_ISSUE_LINE);
  366. this.sorting.addDefault(FIELD_ISSUE_KEY);
  367. }
  368. public SearchResponse search(IssueQuery query, SearchOptions options) {
  369. SearchRequest requestBuilder = EsClient.prepareSearch(TYPE_ISSUE.getMainType());
  370. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  371. requestBuilder.source(sourceBuilder);
  372. configureSorting(query, sourceBuilder);
  373. configurePagination(options, sourceBuilder);
  374. configureRouting(query, options, requestBuilder);
  375. AllFilters allFilters = createAllFilters(query);
  376. RequestFiltersComputer filterComputer = newFilterComputer(options, allFilters);
  377. configureTopAggregations(query, options, sourceBuilder, allFilters, filterComputer);
  378. configureQuery(sourceBuilder, filterComputer);
  379. configureTopFilters(sourceBuilder, filterComputer);
  380. sourceBuilder.fetchSource(false)
  381. .trackTotalHits(true);
  382. return client.search(requestBuilder);
  383. }
  384. private void configureTopAggregations(IssueQuery query, SearchOptions options, SearchSourceBuilder esRequest, AllFilters allFilters,
  385. RequestFiltersComputer filterComputer) {
  386. TopAggregationHelper aggregationHelper = newAggregationHelper(filterComputer, query);
  387. configureTopAggregations(aggregationHelper, query, options, allFilters, esRequest);
  388. }
  389. private static void configureQuery(SearchSourceBuilder esRequest, RequestFiltersComputer filterComputer) {
  390. QueryBuilder esQuery = filterComputer.getQueryFilters()
  391. .map(t -> (QueryBuilder) boolQuery().must(matchAllQuery()).filter(t))
  392. .orElse(matchAllQuery());
  393. esRequest.query(esQuery);
  394. }
  395. private static void configureTopFilters(SearchSourceBuilder esRequest, RequestFiltersComputer filterComputer) {
  396. filterComputer.getPostFilters().ifPresent(esRequest::postFilter);
  397. }
  398. /**
  399. * Optimization - do not send ES request to all shards when scope is restricted
  400. * to a set of projects. Because project UUID is used for routing, the request
  401. * can be sent to only the shards containing the specified projects.
  402. * Note that sticky facets may involve all projects, so this optimization must be
  403. * disabled when facets are enabled.
  404. */
  405. private static void configureRouting(IssueQuery query, SearchOptions options, SearchRequest searchRequest) {
  406. Collection<String> uuids = query.projectUuids();
  407. if (!uuids.isEmpty() && options.getFacets().isEmpty()) {
  408. searchRequest.routing(uuids.stream().map(AuthorizationDoc::idOf).toArray(String[]::new));
  409. }
  410. }
  411. private static void configurePagination(SearchOptions options, SearchSourceBuilder esSearch) {
  412. esSearch.from(options.getOffset()).size(options.getLimit());
  413. }
  414. private AllFilters createAllFilters(IssueQuery query) {
  415. AllFilters filters = RequestFiltersComputer.newAllFilters();
  416. filters.addFilter("__indexType", new SimpleFieldFilterScope(FIELD_INDEX_TYPE), termQuery(FIELD_INDEX_TYPE, TYPE_ISSUE.getName()));
  417. filters.addFilter("__authorization", new SimpleFieldFilterScope("parent"), createAuthorizationFilter());
  418. // Issue is assigned Filter
  419. if (Boolean.TRUE.equals(query.assigned())) {
  420. filters.addFilter(IS_ASSIGNED_FILTER, Facet.ASSIGNEES.getFilterScope(), existsQuery(FIELD_ISSUE_ASSIGNEE_UUID));
  421. } else if (Boolean.FALSE.equals(query.assigned())) {
  422. filters.addFilter(IS_ASSIGNED_FILTER, ASSIGNEES.getFilterScope(), boolQuery().mustNot(existsQuery(FIELD_ISSUE_ASSIGNEE_UUID)));
  423. }
  424. // Issue is Resolved Filter
  425. if (Boolean.TRUE.equals(query.resolved())) {
  426. filters.addFilter("__isResolved", Facet.RESOLUTIONS.getFilterScope(), existsQuery(FIELD_ISSUE_RESOLUTION));
  427. } else if (Boolean.FALSE.equals(query.resolved())) {
  428. filters.addFilter("__isResolved", Facet.RESOLUTIONS.getFilterScope(), boolQuery().mustNot(existsQuery(FIELD_ISSUE_RESOLUTION)));
  429. }
  430. // Field Filters
  431. filters.addFilter(FIELD_ISSUE_KEY, new SimpleFieldFilterScope(FIELD_ISSUE_KEY), createTermsFilter(FIELD_ISSUE_KEY, query.issueKeys()));
  432. filters.addFilter(FIELD_ISSUE_ASSIGNEE_UUID, ASSIGNEES.getFilterScope(), createTermsFilter(FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
  433. filters.addFilter(FIELD_ISSUE_SCOPE, SCOPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_SCOPE, query.scopes()));
  434. filters.addFilter(FIELD_ISSUE_LANGUAGE, LANGUAGES.getFilterScope(), createTermsFilter(FIELD_ISSUE_LANGUAGE, query.languages()));
  435. filters.addFilter(FIELD_ISSUE_TAGS, TAGS.getFilterScope(), createTermsFilter(FIELD_ISSUE_TAGS, query.tags()));
  436. filters.addFilter(FIELD_ISSUE_TYPE, TYPES.getFilterScope(), createTermsFilter(FIELD_ISSUE_TYPE, query.types()));
  437. filters.addFilter(
  438. FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY,
  439. CLEAN_CODE_ATTRIBUTE_CATEGORY.getFilterScope(),
  440. createTermsFilter(FIELD_ISSUE_CLEAN_CODE_ATTRIBUTE_CATEGORY, query.cleanCodeAttributesCategories()));
  441. filters.addFilter(
  442. FIELD_ISSUE_RESOLUTION, RESOLUTIONS.getFilterScope(),
  443. createTermsFilter(FIELD_ISSUE_RESOLUTION, query.resolutions()));
  444. filters.addFilter(
  445. FIELD_ISSUE_AUTHOR_LOGIN, AUTHOR.getFilterScope(),
  446. createTermsFilter(FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
  447. filters.addFilter(
  448. FIELD_ISSUE_RULE_UUID, RULES.getFilterScope(), createTermsFilter(
  449. FIELD_ISSUE_RULE_UUID,
  450. query.ruleUuids()));
  451. filters.addFilter(FIELD_ISSUE_STATUS, STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_STATUS, query.statuses()));
  452. filters.addFilter(FIELD_ISSUE_NEW_STATUS, ISSUE_STATUSES.getFilterScope(), createTermsFilter(FIELD_ISSUE_NEW_STATUS, query.issueStatuses()));
  453. filters.addFilter(FIELD_ISSUE_CODE_VARIANTS, CODE_VARIANTS.getFilterScope(), createTermsFilter(FIELD_ISSUE_CODE_VARIANTS, query.codeVariants()));
  454. // security category
  455. addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_32, PCI_DSS_32, query.pciDss32(), filters);
  456. addSecurityCategoryPrefixFilter(FIELD_ISSUE_PCI_DSS_40, PCI_DSS_40, query.pciDss40(), filters);
  457. addOwaspAsvsFilter(FIELD_ISSUE_OWASP_ASVS_40, OWASP_ASVS_40, query, filters);
  458. addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10, OWASP_TOP_10, query.owaspTop10(), filters);
  459. addSecurityCategoryFilter(FIELD_ISSUE_OWASP_TOP_10_2021, OWASP_TOP_10_2021, query.owaspTop10For2021(), filters);
  460. addSecurityCategoryFilter(FIELD_ISSUE_SANS_TOP_25, SANS_TOP_25, query.sansTop25(), filters);
  461. addSecurityCategoryFilter(FIELD_ISSUE_CWE, CWE, query.cwe(), filters);
  462. addSecurityCategoryFilter(FIELD_ISSUE_SQ_SECURITY_CATEGORY, SONARSOURCE_SECURITY, query.sonarsourceSecurity(), filters);
  463. addSeverityFilter(query, filters);
  464. addImpactFilters(query, filters);
  465. addComponentRelatedFilters(query, filters);
  466. addDatesFilter(filters, query);
  467. addCreatedAfterByProjectsFilter(filters, query);
  468. addNewCodeReferenceFilter(filters, query);
  469. addNewCodeReferenceFilterByProjectsFilter(filters, query);
  470. return filters;
  471. }
  472. private static void addOwaspAsvsFilter(String fieldName, Facet facet, IssueQuery query, AllFilters allFilters) {
  473. if (!CollectionUtils.isEmpty(query.owaspAsvs40())) {
  474. Set<String> requirements = calculateRequirementsForOwaspAsvs40Params(query);
  475. QueryBuilder securityCategoryFilter = termsQuery(fieldName, requirements);
  476. allFilters.addFilter(
  477. fieldName,
  478. facet.getFilterScope(),
  479. boolQuery()
  480. .must(securityCategoryFilter)
  481. .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name())));
  482. }
  483. }
  484. private static Set<String> calculateRequirementsForOwaspAsvs40Params(IssueQuery query) {
  485. int level = query.getOwaspAsvsLevel().orElse(3);
  486. List<String> levelRequirements = OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(level);
  487. return query.owaspAsvs40().stream()
  488. .flatMap(value -> {
  489. // it's a specific category required
  490. if (value.contains(".")) {
  491. return Stream.of(value).filter(levelRequirements::contains);
  492. } else {
  493. return SecurityStandards.getRequirementsForCategoryAndLevel(value, level).stream();
  494. }
  495. }).collect(Collectors.toSet());
  496. }
  497. private static void addSecurityCategoryFilter(String fieldName, Facet facet, Collection<String> values, AllFilters allFilters) {
  498. QueryBuilder securityCategoryFilter = createTermsFilter(fieldName, values);
  499. if (securityCategoryFilter != null) {
  500. allFilters.addFilter(
  501. fieldName,
  502. facet.getFilterScope(),
  503. boolQuery()
  504. .must(securityCategoryFilter)
  505. .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name())));
  506. }
  507. }
  508. /**
  509. * <p>Builds the Elasticsearch boolean query to filter the PCI DSS categories.</p>
  510. *
  511. * <p>The PCI DSS security report handles all the subcategories as one level. This means that subcategory 1.1 doesn't include the issues from 1.1.1.
  512. * Taking this into account, the search filter follows the same logic and uses prefix matching for top-level categories and exact matching for subcategories</p>
  513. *
  514. * <p>Example</p>
  515. * <p>List of PCI DSS categories in issues: {1.5.8, 1.5.9, 1.6.7}
  516. * <ul>
  517. * <li>Search: {1}, returns {1.5.8, 1.5.9, 1.6.7}</li>
  518. * <li>Search: {1.5.8}, returns {1.5.8}</li>
  519. * <li>Search: {1.5}, returns {}</li>
  520. * </ul>
  521. * </p>
  522. *
  523. * @param fieldName The PCI DSS version, e.g. pciDss-3.2
  524. * @param facet The facet used for the filter
  525. * @param values The PCI DSS categories to search for
  526. * @param allFilters Object that holds all the filters for the Elastic search call
  527. */
  528. private static void addSecurityCategoryPrefixFilter(String fieldName, Facet facet, Collection<String> values, AllFilters allFilters) {
  529. if (values.isEmpty()) {
  530. return;
  531. }
  532. BoolQueryBuilder boolQueryBuilder = boolQuery()
  533. // ensures that at least one "should" query is matched. Without it, "should" queries are optional, when a "must" is also present.
  534. .minimumShouldMatch(1)
  535. // the field type must be vulnerability or security hotspot
  536. .must(termsQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name(), SECURITY_HOTSPOT.name()));
  537. // for top level categories a prefix query is added, while for subcategories a term query is used for exact matching
  538. values.stream().map(v -> choosePrefixQuery(fieldName, v)).forEach(boolQueryBuilder::should);
  539. allFilters.addFilter(
  540. fieldName,
  541. facet.getFilterScope(),
  542. boolQueryBuilder);
  543. }
  544. private static QueryBuilder choosePrefixQuery(String fieldName, String value) {
  545. return value.contains(".") ? createTermFilter(fieldName, value) : createPrefixFilter(fieldName, value + ".");
  546. }
  547. private static void addSeverityFilter(IssueQuery query, AllFilters allFilters) {
  548. QueryBuilder severityFieldFilter = createTermsFilter(FIELD_ISSUE_SEVERITY, query.severities());
  549. if (severityFieldFilter != null) {
  550. allFilters.addFilter(
  551. FIELD_ISSUE_SEVERITY,
  552. SEVERITIES.getFilterScope(),
  553. boolQuery()
  554. .must(severityFieldFilter)
  555. // Ignore severity of Security HotSpots
  556. .mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())));
  557. }
  558. }
  559. private static void addImpactFilters(IssueQuery query, AllFilters allFilters) {
  560. if (query.impactSoftwareQualities().isEmpty() && query.impactSeverities().isEmpty()) {
  561. return;
  562. }
  563. if (!query.impactSoftwareQualities().isEmpty() && query.impactSeverities().isEmpty()) {
  564. allFilters.addFilter(
  565. FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY,
  566. IMPACT_SOFTWARE_QUALITY.getFilterScope(),
  567. nestedQuery(
  568. FIELD_ISSUE_IMPACTS,
  569. termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()),
  570. ScoreMode.Avg));
  571. return;
  572. }
  573. if (!query.impactSeverities().isEmpty() && query.impactSoftwareQualities().isEmpty()) {
  574. allFilters.addFilter(
  575. FIELD_ISSUE_IMPACT_SEVERITY,
  576. IMPACT_SEVERITY.getFilterScope(),
  577. nestedQuery(
  578. FIELD_ISSUE_IMPACTS,
  579. termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()),
  580. ScoreMode.Avg));
  581. return;
  582. }
  583. BoolQueryBuilder impactsFilter = boolQuery()
  584. .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()))
  585. .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()));
  586. allFilters.addFilter(FIELD_ISSUE_IMPACTS, new SimpleFieldFilterScope(FIELD_ISSUE_IMPACTS),
  587. nestedQuery(FIELD_ISSUE_IMPACTS, impactsFilter, ScoreMode.Avg));
  588. }
  589. private static void addComponentRelatedFilters(IssueQuery query, AllFilters filters) {
  590. addCommonComponentRelatedFilters(query, filters);
  591. if (query.viewUuids().isEmpty()) {
  592. addBranchComponentRelatedFilters(query, filters);
  593. } else {
  594. addViewRelatedFilters(query, filters);
  595. }
  596. }
  597. private static void addCommonComponentRelatedFilters(IssueQuery query, AllFilters filters) {
  598. filters.addFilter(FIELD_ISSUE_COMPONENT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_COMPONENT_UUID),
  599. createTermsFilter(FIELD_ISSUE_COMPONENT_UUID, query.componentUuids()));
  600. if (!Boolean.TRUE.equals(query.onComponentOnly())) {
  601. filters.addFilter(
  602. FIELD_ISSUE_PROJECT_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_PROJECT_UUID),
  603. createTermsFilter(FIELD_ISSUE_PROJECT_UUID, query.projectUuids()));
  604. filters.addFilter(
  605. FIELD_ISSUE_DIRECTORY_PATH, new SimpleFieldFilterScope(FIELD_ISSUE_DIRECTORY_PATH),
  606. createTermsFilter(FIELD_ISSUE_DIRECTORY_PATH, query.directories()));
  607. filters.addFilter(
  608. FIELD_ISSUE_FILE_PATH, new SimpleFieldFilterScope(FIELD_ISSUE_FILE_PATH),
  609. createTermsFilter(FIELD_ISSUE_FILE_PATH, query.files()));
  610. }
  611. }
  612. private static void addBranchComponentRelatedFilters(IssueQuery query, AllFilters allFilters) {
  613. if (Boolean.TRUE.equals(query.onComponentOnly())) {
  614. return;
  615. }
  616. if (query.isMainBranch() != null) {
  617. allFilters.addFilter(
  618. "__is_main_branch", new SimpleFieldFilterScope(FIELD_ISSUE_IS_MAIN_BRANCH),
  619. createTermFilter(FIELD_ISSUE_IS_MAIN_BRANCH, query.isMainBranch().toString()));
  620. }
  621. allFilters.addFilter(
  622. FIELD_ISSUE_BRANCH_UUID, new SimpleFieldFilterScope(FIELD_ISSUE_BRANCH_UUID),
  623. createTermFilter(FIELD_ISSUE_BRANCH_UUID, query.branchUuid()));
  624. }
  625. private static void addViewRelatedFilters(IssueQuery query, AllFilters allFilters) {
  626. if (Boolean.TRUE.equals(query.onComponentOnly())) {
  627. return;
  628. }
  629. Collection<String> viewUuids = query.viewUuids();
  630. String branchUuid = query.branchUuid();
  631. boolean onApplicationBranch = branchUuid != null && !viewUuids.isEmpty();
  632. if (onApplicationBranch) {
  633. allFilters.addFilter("__view", new SimpleFieldFilterScope("view"), createViewFilter(singletonList(query.branchUuid())));
  634. } else {
  635. allFilters.addFilter("__view", new SimpleFieldFilterScope("view"), createViewFilter(viewUuids));
  636. }
  637. }
  638. @CheckForNull
  639. private static QueryBuilder createViewFilter(Collection<String> viewUuids) {
  640. if (viewUuids.isEmpty()) {
  641. return null;
  642. }
  643. BoolQueryBuilder viewsFilter = boolQuery();
  644. for (String viewUuid : viewUuids) {
  645. viewsFilter.should(QueryBuilders.termsLookupQuery(FIELD_ISSUE_BRANCH_UUID,
  646. new TermsLookup(
  647. TYPE_VIEW.getIndex().getName(),
  648. viewUuid,
  649. ViewIndexDefinition.FIELD_PROJECTS)));
  650. }
  651. return viewsFilter;
  652. }
  653. private static RequestFiltersComputer newFilterComputer(SearchOptions options, AllFilters allFilters) {
  654. Collection<String> facetNames = options.getFacets();
  655. Set<TopAggregationDefinition<?>> facets = Stream.concat(
  656. Stream.of(EFFORT_TOP_AGGREGATION),
  657. facetNames.stream()
  658. .map(FACETS_BY_NAME::get)
  659. .filter(Objects::nonNull)
  660. .map(Facet::getTopAggregationDef))
  661. .collect(Collectors.toSet());
  662. return new RequestFiltersComputer(allFilters, facets);
  663. }
  664. private static TopAggregationHelper newAggregationHelper(RequestFiltersComputer filterComputer, IssueQuery query) {
  665. if (hasQueryEffortFacet(query)) {
  666. return new TopAggregationHelper(filterComputer, new SubAggregationHelper(EFFORT_AGGREGATION, EFFORT_AGGREGATION_ORDER));
  667. }
  668. return new TopAggregationHelper(filterComputer, new SubAggregationHelper());
  669. }
  670. private static AggregationBuilder addEffortAggregationIfNeeded(IssueQuery query, AggregationBuilder aggregation) {
  671. if (hasQueryEffortFacet(query)) {
  672. aggregation.subAggregation(EFFORT_AGGREGATION);
  673. }
  674. return aggregation;
  675. }
  676. private static boolean hasQueryEffortFacet(IssueQuery query) {
  677. return FACET_MODE_EFFORT.equals(query.facetMode());
  678. }
  679. @CheckForNull
  680. private static QueryBuilder createTermsFilter(String field, Collection<?> values) {
  681. return values.isEmpty() ? null : termsQuery(field, values);
  682. }
  683. @CheckForNull
  684. private static QueryBuilder createTermFilter(String field, @Nullable String value) {
  685. return value == null ? null : termQuery(field, value);
  686. }
  687. private static QueryBuilder createPrefixFilter(String field, String value) {
  688. return prefixQuery(field, value);
  689. }
  690. private void configureSorting(IssueQuery query, SearchSourceBuilder esRequest) {
  691. createSortBuilders(query).forEach(esRequest::sort);
  692. }
  693. private List<FieldSortBuilder> createSortBuilders(IssueQuery query) {
  694. String sortField = query.sort();
  695. if (sortField != null) {
  696. boolean asc = Boolean.TRUE.equals(query.asc());
  697. return sorting.fill(sortField, asc);
  698. }
  699. return sorting.fillDefault();
  700. }
  701. private QueryBuilder createAuthorizationFilter() {
  702. return authorizationTypeSupport.createQueryFilter();
  703. }
  704. private void addDatesFilter(AllFilters filters, IssueQuery query) {
  705. PeriodStart createdAfter = query.createdAfter();
  706. Date createdBefore = query.createdBefore();
  707. validateCreationDateBounds(createdBefore, createdAfter != null ? createdAfter.date() : null);
  708. if (createdAfter != null) {
  709. filters.addFilter(
  710. "__createdAfter", CREATED_AT.getFilterScope(),
  711. QueryBuilders
  712. .rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT)
  713. .from(createdAfter.date().getTime(), createdAfter.inclusive()));
  714. }
  715. if (createdBefore != null) {
  716. filters.addFilter(
  717. "__createdBefore", CREATED_AT.getFilterScope(),
  718. QueryBuilders
  719. .rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT)
  720. .lt(createdBefore.getTime()));
  721. }
  722. Date createdAt = query.createdAt();
  723. if (createdAt != null) {
  724. filters.addFilter(
  725. "__createdAt", CREATED_AT.getFilterScope(),
  726. termQuery(FIELD_ISSUE_FUNC_CREATED_AT, createdAt.getTime()));
  727. }
  728. }
  729. private static void addNewCodeReferenceFilter(AllFilters filters, IssueQuery query) {
  730. Boolean newCodeOnReference = query.newCodeOnReference();
  731. if (newCodeOnReference != null) {
  732. filters.addFilter(
  733. FIELD_ISSUE_NEW_CODE_REFERENCE, new SimpleFieldFilterScope(FIELD_ISSUE_NEW_CODE_REFERENCE),
  734. termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true));
  735. }
  736. }
  737. private static void addNewCodeReferenceFilterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
  738. Collection<String> newCodeOnReferenceByProjectUuids = query.newCodeOnReferenceByProjectUuids();
  739. BoolQueryBuilder boolQueryBuilder = boolQuery();
  740. if (!newCodeOnReferenceByProjectUuids.isEmpty()) {
  741. newCodeOnReferenceByProjectUuids.forEach(projectOrProjectBranchUuid -> boolQueryBuilder.should(boolQuery()
  742. .filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
  743. .filter(termQuery(FIELD_ISSUE_NEW_CODE_REFERENCE, true))));
  744. allFilters.addFilter("__is_new_code_reference_by_project_uuids",
  745. new SimpleFieldFilterScope("newCodeReferenceByProjectUuids"), boolQueryBuilder);
  746. }
  747. }
  748. private static void addCreatedAfterByProjectsFilter(AllFilters allFilters, IssueQuery query) {
  749. Map<String, PeriodStart> createdAfterByProjectUuids = query.createdAfterByProjectUuids();
  750. BoolQueryBuilder boolQueryBuilder = boolQuery();
  751. createdAfterByProjectUuids.forEach((projectOrProjectBranchUuid, createdAfterDate) -> boolQueryBuilder.should(boolQuery()
  752. .filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectOrProjectBranchUuid))
  753. .filter(rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT).from(createdAfterDate.date().getTime(), createdAfterDate.inclusive()))));
  754. allFilters.addFilter("__created_after_by_project_uuids", new SimpleFieldFilterScope("createdAfterByProjectUuids"), boolQueryBuilder);
  755. }
  756. private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) {
  757. Preconditions.checkArgument(createdAfter == null || createdAfter.compareTo(new Date(system.now())) <= 0,
  758. "Start bound cannot be in the future");
  759. Preconditions.checkArgument(createdAfter == null || createdBefore == null || createdAfter.before(createdBefore),
  760. "Start bound cannot be larger or equal to end bound");
  761. }
  762. private void configureTopAggregations(TopAggregationHelper aggregationHelper, IssueQuery query, SearchOptions options,
  763. AllFilters queryFilters, SearchSourceBuilder esRequest) {
  764. addFacetIfNeeded(options, aggregationHelper, esRequest, STATUSES, NO_SELECTED_VALUES);
  765. addFacetIfNeeded(options, aggregationHelper, esRequest, ISSUE_STATUSES, query.issueStatuses().toArray());
  766. addFacetIfNeeded(options, aggregationHelper, esRequest, PROJECT_UUIDS, query.projectUuids().toArray());
  767. addFacetIfNeeded(options, aggregationHelper, esRequest, DIRECTORIES, query.directories().toArray());
  768. addFacetIfNeeded(options, aggregationHelper, esRequest, FILES, query.files().toArray());
  769. addFacetIfNeeded(options, aggregationHelper, esRequest, SCOPES, query.scopes().toArray());
  770. addFacetIfNeeded(options, aggregationHelper, esRequest, LANGUAGES, query.languages().toArray());
  771. addFacetIfNeeded(options, aggregationHelper, esRequest, RULES, query.ruleUuids().toArray());
  772. addFacetIfNeeded(options, aggregationHelper, esRequest, AUTHOR, query.authors().toArray());
  773. addFacetIfNeeded(options, aggregationHelper, esRequest, TAGS, query.tags().toArray());
  774. addFacetIfNeeded(options, aggregationHelper, esRequest, TYPES, query.types().toArray());
  775. addFacetIfNeeded(options, aggregationHelper, esRequest, CODE_VARIANTS, query.codeVariants().toArray());
  776. addFacetIfNeeded(options, aggregationHelper, esRequest, CLEAN_CODE_ATTRIBUTE_CATEGORY, query.cleanCodeAttributesCategories().toArray());
  777. addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_32, PCI_DSS_32, options, aggregationHelper, esRequest, query.pciDss32().toArray());
  778. addSecurityCategoryFacetIfNeeded(PARAM_PCI_DSS_40, PCI_DSS_40, options, aggregationHelper, esRequest, query.pciDss40().toArray());
  779. addSecurityCategoryFacetIfNeeded(PARAM_OWASP_ASVS_40, OWASP_ASVS_40, options, aggregationHelper, esRequest, query.owaspAsvs40().toArray());
  780. addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10, OWASP_TOP_10, options, aggregationHelper, esRequest, query.owaspTop10().toArray());
  781. addSecurityCategoryFacetIfNeeded(PARAM_OWASP_TOP_10_2021, OWASP_TOP_10_2021, options, aggregationHelper, esRequest, query.owaspTop10For2021().toArray());
  782. addSecurityCategoryFacetIfNeeded(PARAM_SANS_TOP_25, SANS_TOP_25, options, aggregationHelper, esRequest, query.sansTop25().toArray());
  783. addSecurityCategoryFacetIfNeeded(PARAM_CWE, CWE, options, aggregationHelper, esRequest, query.cwe().toArray());
  784. addSecurityCategoryFacetIfNeeded(PARAM_SONARSOURCE_SECURITY, SONARSOURCE_SECURITY, options, aggregationHelper, esRequest, query.sonarsourceSecurity().toArray());
  785. addSeverityFacetIfNeeded(options, aggregationHelper, esRequest);
  786. addImpactSoftwareQualityFacetIfNeeded(options, query, aggregationHelper, esRequest);
  787. addImpactSeverityFacetIfNeeded(options, query, aggregationHelper, esRequest);
  788. addResolutionFacetIfNeeded(options, query, aggregationHelper, esRequest);
  789. addAssigneesFacetIfNeeded(options, query, aggregationHelper, esRequest);
  790. addCreatedAtFacetIfNeeded(options, query, aggregationHelper, queryFilters, esRequest);
  791. addAssignedToMeFacetIfNeeded(options, aggregationHelper, esRequest);
  792. addEffortTopAggregation(aggregationHelper, esRequest);
  793. }
  794. private static void addFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper,
  795. SearchSourceBuilder esRequest, Facet facet, Object[] selectedValues) {
  796. if (!options.getFacets().contains(facet.getName())) {
  797. return;
  798. }
  799. FilterAggregationBuilder topAggregation = aggregationHelper.buildTermTopAggregation(
  800. facet.getName(), facet.getTopAggregationDef(), facet.getNumberOfTerms(),
  801. NO_EXTRA_FILTER,
  802. t -> aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(facet.getName(), facet.getTopAggregationDef(), selectedValues)
  803. .ifPresent(t::subAggregation));
  804. esRequest.aggregation(topAggregation);
  805. }
  806. private static void addSecurityCategoryFacetIfNeeded(String param, Facet facet, SearchOptions options, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest,
  807. Object[] selectedValues) {
  808. if (!options.getFacets().contains(param)) {
  809. return;
  810. }
  811. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  812. facet.getName(), facet.getTopAggregationDef(), facet.getNumberOfTerms(),
  813. filter -> filter.must(termQuery(FIELD_ISSUE_TYPE, VULNERABILITY.name())),
  814. t -> aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(facet.getName(), facet.getTopAggregationDef(), selectedValues)
  815. .ifPresent(t::subAggregation));
  816. esRequest.aggregation(aggregation);
  817. }
  818. private static void addSeverityFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  819. if (!options.getFacets().contains(PARAM_SEVERITIES)) {
  820. return;
  821. }
  822. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  823. SEVERITIES.getName(), SEVERITIES.getTopAggregationDef(), SEVERITIES.getNumberOfTerms(),
  824. // Ignore severity of Security HotSpots
  825. filter -> filter.mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())),
  826. NO_OTHER_SUBAGGREGATION);
  827. esRequest.aggregation(aggregation);
  828. }
  829. private static void addImpactSoftwareQualityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper,
  830. SearchSourceBuilder esRequest) {
  831. if (!options.getFacets().contains(PARAM_IMPACT_SOFTWARE_QUALITIES)) {
  832. return;
  833. }
  834. Function<SoftwareQuality, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
  835. .filter(termQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, softwareQuality.name()));
  836. FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(SoftwareQuality.values())
  837. .map(softwareQuality -> new FiltersAggregator.KeyedFilter(softwareQuality.name(),
  838. query.impactSeverities().isEmpty() ? mainQuery.apply(softwareQuality)
  839. : mainQuery.apply(softwareQuality)
  840. .filter(termsQuery(FIELD_ISSUE_IMPACT_SEVERITY, query.impactSeverities()))))
  841. .toArray(FiltersAggregator.KeyedFilter[]::new);
  842. AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
  843. IMPACT_SOFTWARE_QUALITY.getName(), IMPACT_SOFTWARE_QUALITY.getTopAggregationDef(),
  844. NO_EXTRA_FILTER,
  845. t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SOFTWARE_QUALITY.getName(), FIELD_ISSUE_IMPACTS)
  846. .subAggregation(filters(IMPACT_SOFTWARE_QUALITY.getName(), keyedFilters))));
  847. esRequest.aggregation(aggregation);
  848. }
  849. private static void addImpactSeverityFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  850. if (!options.getFacets().contains(PARAM_IMPACT_SEVERITIES)) {
  851. return;
  852. }
  853. Function<org.sonar.api.issue.impact.Severity, BoolQueryBuilder> mainQuery = softwareQuality -> boolQuery()
  854. .filter(termQuery(FIELD_ISSUE_IMPACT_SEVERITY, softwareQuality.name()));
  855. FiltersAggregator.KeyedFilter[] keyedFilters = Arrays.stream(org.sonar.api.issue.impact.Severity.values())
  856. .map(severity -> new FiltersAggregator.KeyedFilter(severity.name(),
  857. query.impactSoftwareQualities().isEmpty() ? mainQuery.apply(severity)
  858. : mainQuery.apply(severity)
  859. .filter(termsQuery(FIELD_ISSUE_IMPACT_SOFTWARE_QUALITY, query.impactSoftwareQualities()))))
  860. .toArray(FiltersAggregator.KeyedFilter[]::new);
  861. AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
  862. IMPACT_SEVERITY.getName(), IMPACT_SEVERITY.getTopAggregationDef(),
  863. NO_EXTRA_FILTER,
  864. t -> t.subAggregation(AggregationBuilders.nested("nested_" + IMPACT_SEVERITY.getName(), FIELD_ISSUE_IMPACTS)
  865. .subAggregation(filters(IMPACT_SEVERITY.getName(), keyedFilters)
  866. // we want to count the number of issues for each severity, so we need to reverse the nested aggregation
  867. .subAggregation(reverseNested("reverse_nested_" + IMPACT_SOFTWARE_QUALITY.getName())))));
  868. esRequest.aggregation(aggregation);
  869. }
  870. private static void addResolutionFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  871. if (!options.getFacets().contains(PARAM_RESOLUTIONS)) {
  872. return;
  873. }
  874. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  875. RESOLUTIONS.getName(), RESOLUTIONS.getTopAggregationDef(), RESOLUTIONS.getNumberOfTerms(),
  876. NO_EXTRA_FILTER,
  877. t ->
  878. // add aggregation of type "missing" to return count of unresolved issues in the facet
  879. t.subAggregation(
  880. addEffortAggregationIfNeeded(query, AggregationBuilders
  881. .missing(RESOLUTIONS.getName() + FACET_SUFFIX_MISSING)
  882. .field(RESOLUTIONS.getFieldName()))));
  883. esRequest.aggregation(aggregation);
  884. }
  885. private static void addAssigneesFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  886. if (!options.getFacets().contains(PARAM_ASSIGNEES)) {
  887. return;
  888. }
  889. Consumer<FilterAggregationBuilder> assigneeAggregations = t -> {
  890. // optional second aggregation to return the issue count for selected assignees (if any)
  891. Object[] assignees = query.assignees().toArray();
  892. aggregationHelper.getSubAggregationHelper().buildSelectedItemsAggregation(ASSIGNEES.getName(), ASSIGNEES.getTopAggregationDef(), assignees)
  893. .ifPresent(t::subAggregation);
  894. // third aggregation to always return the count of unassigned in the assignee facet
  895. t.subAggregation(addEffortAggregationIfNeeded(query, AggregationBuilders
  896. .missing(ASSIGNEES.getName() + FACET_SUFFIX_MISSING)
  897. .field(ASSIGNEES.getFieldName())));
  898. };
  899. AggregationBuilder aggregation = aggregationHelper.buildTermTopAggregation(
  900. ASSIGNEES.getName(), ASSIGNEES.getTopAggregationDef(), ASSIGNEES.getNumberOfTerms(),
  901. NO_EXTRA_FILTER, assigneeAggregations);
  902. esRequest.aggregation(aggregation);
  903. }
  904. private void addCreatedAtFacetIfNeeded(SearchOptions options, IssueQuery query, TopAggregationHelper aggregationHelper, AllFilters allFilters,
  905. SearchSourceBuilder esRequest) {
  906. if (options.getFacets().contains(PARAM_CREATED_AT)) {
  907. getCreatedAtFacet(query, aggregationHelper, allFilters).ifPresent(esRequest::aggregation);
  908. }
  909. }
  910. private Optional<AggregationBuilder> getCreatedAtFacet(IssueQuery query, TopAggregationHelper aggregationHelper, AllFilters allFilters) {
  911. long startTime;
  912. boolean startInclusive;
  913. PeriodStart createdAfter = query.createdAfter();
  914. if (createdAfter == null) {
  915. OptionalLong minDate = getMinCreatedAt(allFilters);
  916. if (!minDate.isPresent()) {
  917. return Optional.empty();
  918. }
  919. startTime = minDate.getAsLong();
  920. startInclusive = true;
  921. } else {
  922. startTime = createdAfter.date().getTime();
  923. startInclusive = createdAfter.inclusive();
  924. }
  925. Date createdBefore = query.createdBefore();
  926. long endTime = createdBefore == null ? system.now() : createdBefore.getTime();
  927. Duration timeSpan = new Duration(startTime, endTime);
  928. DateHistogramInterval bucketSize = computeDateHistogramBucketSize(timeSpan);
  929. FilterAggregationBuilder topAggregation = aggregationHelper.buildTopAggregation(
  930. CREATED_AT.getName(),
  931. CREATED_AT.getTopAggregationDef(),
  932. NO_EXTRA_FILTER,
  933. t -> {
  934. AggregationBuilder dateHistogram = AggregationBuilders.dateHistogram(CREATED_AT.getName())
  935. .field(CREATED_AT.getFieldName())
  936. .calendarInterval(bucketSize)
  937. .minDocCount(0L)
  938. .format(DateUtils.DATETIME_FORMAT)
  939. .timeZone(Optional.ofNullable(query.timeZone()).orElse(system.getDefaultTimeZone().toZoneId()))
  940. // ES dateHistogram bounds are inclusive while createdBefore parameter is exclusive
  941. .extendedBounds(new LongBounds(startInclusive ? startTime : (startTime + 1), endTime - 1L));
  942. addEffortAggregationIfNeeded(query, dateHistogram);
  943. t.subAggregation(dateHistogram);
  944. });
  945. return Optional.of(topAggregation);
  946. }
  947. private static DateHistogramInterval computeDateHistogramBucketSize(Duration timeSpan) {
  948. if (timeSpan.isShorterThan(TWENTY_DAYS)) {
  949. return DateHistogramInterval.DAY;
  950. }
  951. if (timeSpan.isShorterThan(TWENTY_WEEKS)) {
  952. return DateHistogramInterval.WEEK;
  953. }
  954. if (timeSpan.isShorterThan(TWENTY_MONTHS)) {
  955. return DateHistogramInterval.MONTH;
  956. }
  957. return DateHistogramInterval.YEAR;
  958. }
  959. private OptionalLong getMinCreatedAt(AllFilters filters) {
  960. String facetNameAndField = CREATED_AT.getFieldName();
  961. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
  962. .size(0);
  963. BoolQueryBuilder esFilter = boolQuery();
  964. filters.stream().filter(Objects::nonNull).forEach(esFilter::must);
  965. if (esFilter.hasClauses()) {
  966. sourceBuilder.query(QueryBuilders.boolQuery().filter(esFilter));
  967. }
  968. sourceBuilder.aggregation(AggregationBuilders.min(facetNameAndField).field(facetNameAndField));
  969. SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType())
  970. .source(sourceBuilder);
  971. Min minValue = client.search(request).getAggregations().get(facetNameAndField);
  972. double actualValue = minValue.getValue();
  973. if (Double.isInfinite(actualValue)) {
  974. return OptionalLong.empty();
  975. }
  976. return OptionalLong.of((long) actualValue);
  977. }
  978. private void addAssignedToMeFacetIfNeeded(SearchOptions options, TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  979. String uuid = userSession.getUuid();
  980. if (options.getFacets().contains(ASSIGNED_TO_ME.getName()) && !StringUtils.isEmpty(uuid)) {
  981. AggregationBuilder aggregation = aggregationHelper.buildTopAggregation(
  982. ASSIGNED_TO_ME.getName(),
  983. ASSIGNED_TO_ME.getTopAggregationDef(),
  984. NO_EXTRA_FILTER,
  985. t ->
  986. // add sub-aggregation to return issue count for current user
  987. aggregationHelper.getSubAggregationHelper()
  988. .buildSelectedItemsAggregation(ASSIGNED_TO_ME.getName(), ASSIGNED_TO_ME.getTopAggregationDef(), new String[] {uuid})
  989. .ifPresent(t::subAggregation));
  990. esRequest.aggregation(aggregation);
  991. }
  992. }
  993. private static void addEffortTopAggregation(TopAggregationHelper aggregationHelper, SearchSourceBuilder esRequest) {
  994. AggregationBuilder topAggregation = aggregationHelper.buildTopAggregation(
  995. FACET_MODE_EFFORT,
  996. EFFORT_TOP_AGGREGATION,
  997. NO_EXTRA_FILTER,
  998. t -> t.subAggregation(EFFORT_AGGREGATION));
  999. esRequest.aggregation(topAggregation);
  1000. }
  1001. public List<String> searchTags(IssueQuery query, @Nullable String textQuery, int size) {
  1002. Terms terms = listTermsMatching(FIELD_ISSUE_TAGS, query, textQuery, BucketOrder.key(true), size);
  1003. return EsUtils.termsKeys(terms);
  1004. }
  1005. public Map<String, Long> countTags(IssueQuery query, int maxNumberOfTags) {
  1006. Terms terms = listTermsMatching(FIELD_ISSUE_TAGS, query, null, BucketOrder.count(false), maxNumberOfTags);
  1007. return EsUtils.termsToMap(terms);
  1008. }
  1009. public List<String> searchAuthors(IssueQuery query, @Nullable String textQuery, int maxNumberOfAuthors) {
  1010. Terms terms = listTermsMatching(FIELD_ISSUE_AUTHOR_LOGIN, query, textQuery, BucketOrder.key(true), maxNumberOfAuthors);
  1011. return EsUtils.termsKeys(terms);
  1012. }
  1013. private Terms listTermsMatching(String fieldName, IssueQuery query, @Nullable String textQuery, BucketOrder termsOrder, int size) {
  1014. SearchRequest requestBuilder = EsClient.prepareSearch(TYPE_ISSUE.getMainType());
  1015. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
  1016. // Avoids returning search hits
  1017. .size(0);
  1018. requestBuilder.source(sourceBuilder);
  1019. sourceBuilder.query(boolQuery().must(QueryBuilders.matchAllQuery()).filter(createBoolFilter(query)));
  1020. TermsAggregationBuilder aggreg = AggregationBuilders.terms("_ref")
  1021. .field(fieldName)
  1022. .size(size)
  1023. .order(termsOrder)
  1024. .minDocCount(1L);
  1025. if (textQuery != null) {
  1026. aggreg.includeExclude(new IncludeExclude(format(SUBSTRING_MATCH_REGEXP, escapeSpecialRegexChars(textQuery)), null));
  1027. }
  1028. sourceBuilder.aggregation(aggreg);
  1029. SearchResponse searchResponse = client.search(requestBuilder);
  1030. return searchResponse.getAggregations().get("_ref");
  1031. }
  1032. private BoolQueryBuilder createBoolFilter(IssueQuery query) {
  1033. BoolQueryBuilder boolQuery = boolQuery();
  1034. createAllFilters(query).stream()
  1035. .filter(Objects::nonNull)
  1036. .forEach(boolQuery::must);
  1037. return boolQuery;
  1038. }
  1039. public List<ProjectStatistics> searchProjectStatistics(List<String> projectUuids, List<Long> froms, @Nullable String assigneeUuid) {
  1040. checkState(projectUuids.size() == froms.size(),
  1041. "Expected same size for projectUuids (had size %s) and froms (had size %s)", projectUuids.size(), froms.size());
  1042. if (projectUuids.isEmpty()) {
  1043. return Collections.emptyList();
  1044. }
  1045. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
  1046. .query(
  1047. boolQuery()
  1048. .mustNot(existsQuery(FIELD_ISSUE_RESOLUTION))
  1049. .filter(termQuery(FIELD_ISSUE_ASSIGNEE_UUID, assigneeUuid))
  1050. .mustNot(termQuery(FIELD_ISSUE_TYPE, SECURITY_HOTSPOT.name())))
  1051. .size(0);
  1052. IntStream.range(0, projectUuids.size()).forEach(i -> {
  1053. String projectUuid = projectUuids.get(i);
  1054. long from = froms.get(i);
  1055. sourceBuilder
  1056. .aggregation(AggregationBuilders
  1057. .filter(projectUuid, boolQuery()
  1058. .filter(termQuery(FIELD_ISSUE_PROJECT_UUID, projectUuid))
  1059. .filter(rangeQuery(FIELD_ISSUE_FUNC_CREATED_AT).gte(from)))
  1060. .subAggregation(
  1061. AggregationBuilders.terms("branchUuid").field(FIELD_ISSUE_BRANCH_UUID)
  1062. .subAggregation(
  1063. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY))
  1064. .subAggregation(
  1065. AggregationBuilders.max("maxFuncCreatedAt").field(FIELD_ISSUE_FUNC_CREATED_AT))));
  1066. });
  1067. SearchRequest requestBuilder = EsClient.prepareSearch(TYPE_ISSUE.getMainType());
  1068. requestBuilder.source(sourceBuilder);
  1069. SearchResponse response = client.search(requestBuilder);
  1070. return response.getAggregations().asList().stream()
  1071. .map(ParsedFilter.class::cast)
  1072. .flatMap(projectBucket -> ((ParsedStringTerms) projectBucket.getAggregations().get("branchUuid")).getBuckets().stream()
  1073. .flatMap(branchBucket -> {
  1074. long count = ((ParsedValueCount) branchBucket.getAggregations().get(AGG_COUNT)).getValue();
  1075. if (count < 1L) {
  1076. return Stream.empty();
  1077. }
  1078. long lastIssueDate = (long) ((ParsedMax) branchBucket.getAggregations().get("maxFuncCreatedAt")).getValue();
  1079. return Stream.of(new ProjectStatistics(branchBucket.getKeyAsString(), count, lastIssueDate));
  1080. }))
  1081. .toList();
  1082. }
  1083. public List<SecurityStandardCategoryStatistics> getCweTop25Reports(String projectUuid, boolean isViewOrApp) {
  1084. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1085. CWES_BY_CWE_TOP_25.keySet()
  1086. .forEach(cweYear -> request.aggregation(
  1087. newSecurityReportSubAggregations(
  1088. AggregationBuilders.filter(cweYear, boolQuery().filter(existsQuery(FIELD_ISSUE_CWE))),
  1089. true,
  1090. CWES_BY_CWE_TOP_25.get(cweYear))));
  1091. List<SecurityStandardCategoryStatistics> result = search(request, true, null);
  1092. for (SecurityStandardCategoryStatistics cweReport : result) {
  1093. Set<String> foundRules = cweReport.getChildren().stream()
  1094. .map(SecurityStandardCategoryStatistics::getCategory)
  1095. .collect(Collectors.toSet());
  1096. CWES_BY_CWE_TOP_25.get(cweReport.getCategory()).stream()
  1097. .filter(rule -> !foundRules.contains(rule))
  1098. .forEach(rule -> cweReport.getChildren().add(emptyCweStatistics(rule)));
  1099. }
  1100. return result;
  1101. }
  1102. private static SecurityStandardCategoryStatistics emptyCweStatistics(String rule) {
  1103. return new SecurityStandardCategoryStatistics(rule, 0, OptionalInt.of(1), 0, 0, 1, null, null);
  1104. }
  1105. public List<SecurityStandardCategoryStatistics> getSonarSourceReport(String projectUuid, boolean isViewOrApp, boolean includeCwe) {
  1106. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1107. Arrays.stream(SQCategory.values())
  1108. .forEach(sonarsourceCategory -> request.aggregation(
  1109. newSecurityReportSubAggregations(
  1110. AggregationBuilders.filter(sonarsourceCategory.getKey(), boolQuery().filter(termQuery(FIELD_ISSUE_SQ_SECURITY_CATEGORY, sonarsourceCategory.getKey()))),
  1111. includeCwe,
  1112. SecurityStandards.CWES_BY_SQ_CATEGORY.get(sonarsourceCategory))));
  1113. return search(request, includeCwe, null);
  1114. }
  1115. public List<SecurityStandardCategoryStatistics> getPciDssReport(String projectUuid, boolean isViewOrApp, PciDssVersion version) {
  1116. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1117. Arrays.stream(PciDss.values())
  1118. .forEach(pciDss -> request.aggregation(
  1119. newSecurityReportSubAggregations(
  1120. AggregationBuilders.filter(pciDss.category(), boolQuery().filter(prefixQuery(version.prefix(), pciDss.category() + "."))), version.prefix())));
  1121. return searchWithDistribution(request, version.label(), null);
  1122. }
  1123. public List<SecurityStandardCategoryStatistics> getOwaspAsvsReport(String projectUuid, boolean isViewOrApp, RulesDefinition.OwaspAsvsVersion version, Integer level) {
  1124. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1125. Arrays.stream(SecurityStandards.OwaspAsvs.values())
  1126. .forEach(owaspAsvs -> request.aggregation(
  1127. newSecurityReportSubAggregations(
  1128. AggregationBuilders.filter(
  1129. owaspAsvs.category(),
  1130. boolQuery().filter(termsQuery(version.prefix(), SecurityStandards.getRequirementsForCategoryAndLevel(owaspAsvs, level)))),
  1131. version.prefix())));
  1132. return searchWithDistribution(request, version.label(), level);
  1133. }
  1134. public List<SecurityStandardCategoryStatistics> getOwaspAsvsReportGroupedByLevel(String projectUuid, boolean isViewOrApp, RulesDefinition.OwaspAsvsVersion version, int level) {
  1135. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1136. request.aggregation(
  1137. newSecurityReportSubAggregations(
  1138. AggregationBuilders.filter(
  1139. "l" + level,
  1140. boolQuery().filter(termsQuery(version.prefix(), SecurityStandards.OWASP_ASVS_REQUIREMENTS_BY_LEVEL.get(version).get(level)))),
  1141. version.prefix()));
  1142. return searchWithLevelDistribution(request, version.label(), Integer.toString(level));
  1143. }
  1144. public List<SecurityStandardCategoryStatistics> getOwaspTop10Report(String projectUuid, boolean isViewOrApp, boolean includeCwe, OwaspTop10Version version) {
  1145. SearchSourceBuilder request = prepareNonClosedVulnerabilitiesAndHotspotSearch(projectUuid, isViewOrApp);
  1146. IntStream.rangeClosed(1, 10).mapToObj(i -> "a" + i)
  1147. .forEach(owaspCategory -> request.aggregation(
  1148. newSecurityReportSubAggregations(
  1149. AggregationBuilders.filter(owaspCategory, boolQuery().filter(termQuery(version.prefix(), owaspCategory))),
  1150. includeCwe,
  1151. null)));
  1152. return search(request, includeCwe, version.label());
  1153. }
  1154. private List<SecurityStandardCategoryStatistics> searchWithLevelDistribution(SearchSourceBuilder sourceBuilder, String version, @Nullable String level) {
  1155. return getSearchResponse(sourceBuilder)
  1156. .getAggregations().asList().stream()
  1157. .map(c -> processSecurityReportIssueSearchResultsWithLevelDistribution((ParsedFilter) c, version, level))
  1158. .toList();
  1159. }
  1160. private List<SecurityStandardCategoryStatistics> searchWithDistribution(SearchSourceBuilder sourceBuilder, String version, @Nullable Integer level) {
  1161. return getSearchResponse(sourceBuilder)
  1162. .getAggregations().asList().stream()
  1163. .map(c -> processSecurityReportIssueSearchResultsWithDistribution((ParsedFilter) c, version, level))
  1164. .toList();
  1165. }
  1166. private List<SecurityStandardCategoryStatistics> search(SearchSourceBuilder sourceBuilder, boolean includeDistribution, @Nullable String version) {
  1167. return getSearchResponse(sourceBuilder)
  1168. .getAggregations().asList().stream()
  1169. .map(c -> processSecurityReportIssueSearchResults((ParsedFilter) c, includeDistribution, version))
  1170. .toList();
  1171. }
  1172. private SearchResponse getSearchResponse(SearchSourceBuilder sourceBuilder) {
  1173. SearchRequest request = EsClient.prepareSearch(TYPE_ISSUE.getMainType())
  1174. .source(sourceBuilder);
  1175. return client.search(request);
  1176. }
  1177. private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithDistribution(ParsedFilter categoryFilter, String version, @Nullable Integer level) {
  1178. var list = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets();
  1179. List<SecurityStandardCategoryStatistics> children = list.stream()
  1180. .filter(categoryBucket -> StringUtils.startsWith(categoryBucket.getKeyAsString(), categoryFilter.getName() + "."))
  1181. .filter(categoryBucket -> level == null || OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(level).contains(categoryBucket.getKeyAsString()))
  1182. .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null))
  1183. .toList();
  1184. return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version);
  1185. }
  1186. private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResultsWithLevelDistribution(ParsedFilter categoryFilter, String version, String level) {
  1187. var list = ((ParsedStringTerms) categoryFilter.getAggregations().get(AGG_DISTRIBUTION)).getBuckets();
  1188. List<SecurityStandardCategoryStatistics> children = list.stream()
  1189. .filter(categoryBucket -> OWASP_ASVS_40_REQUIREMENTS_BY_LEVEL.get(Integer.parseInt(level)).contains(categoryBucket.getKeyAsString()))
  1190. .map(categoryBucket -> processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getKeyAsString(), null, null))
  1191. .toList();
  1192. return processSecurityReportCategorySearchResults(categoryFilter, categoryFilter.getName(), children, version);
  1193. }
  1194. private static SecurityStandardCategoryStatistics processSecurityReportIssueSearchResults(ParsedFilter categoryBucket, boolean includeDistribution, String version) {
  1195. List<SecurityStandardCategoryStatistics> children = new ArrayList<>();
  1196. if (includeDistribution) {
  1197. Stream<? extends Terms.Bucket> stream = ((ParsedStringTerms) categoryBucket.getAggregations().get(AGG_DISTRIBUTION)).getBuckets().stream();
  1198. children = stream.map(cweBucket -> processSecurityReportCategorySearchResults(cweBucket, cweBucket.getKeyAsString(), null, null))
  1199. .collect(toCollection(ArrayList<SecurityStandardCategoryStatistics>::new));
  1200. }
  1201. return processSecurityReportCategorySearchResults(categoryBucket, categoryBucket.getName(), children, version);
  1202. }
  1203. private static SecurityStandardCategoryStatistics processSecurityReportCategorySearchResults(HasAggregations categoryBucket, String categoryName,
  1204. @Nullable List<SecurityStandardCategoryStatistics> children, @Nullable String version) {
  1205. List<? extends Terms.Bucket> severityBuckets = ((ParsedStringTerms) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_VULNERABILITIES)).getAggregations()
  1206. .get(AGG_SEVERITIES)).getBuckets();
  1207. long vulnerabilities = severityBuckets.stream().mapToLong(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue()).sum();
  1208. // Worst severity having at least one issue
  1209. OptionalInt severityRating = severityBuckets.stream()
  1210. .filter(b -> ((ParsedValueCount) b.getAggregations().get(AGG_COUNT)).getValue() != 0)
  1211. .mapToInt(b -> Severity.ALL.indexOf(b.getKeyAsString()) + 1)
  1212. .max();
  1213. long toReviewSecurityHotspots = ((ParsedValueCount) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_TO_REVIEW_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
  1214. .getValue();
  1215. long reviewedSecurityHotspots = ((ParsedValueCount) ((ParsedFilter) categoryBucket.getAggregations().get(AGG_REVIEWED_SECURITY_HOTSPOTS)).getAggregations().get(AGG_COUNT))
  1216. .getValue();
  1217. Optional<Double> percent = computePercent(toReviewSecurityHotspots, reviewedSecurityHotspots);
  1218. Integer securityReviewRating = computeRating(percent.orElse(null)).getIndex();
  1219. return new SecurityStandardCategoryStatistics(categoryName, vulnerabilities, severityRating, toReviewSecurityHotspots,
  1220. reviewedSecurityHotspots, securityReviewRating, children, version);
  1221. }
  1222. private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, String securityStandardVersionPrefix) {
  1223. AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
  1224. final TermsAggregationBuilder distributionAggregation = AggregationBuilders.terms(AGG_DISTRIBUTION)
  1225. .field(securityStandardVersionPrefix)
  1226. // 100 should be enough to display all the requirements per category. If not, the UI will be broken anyway
  1227. .size(MAX_FACET_SIZE);
  1228. categoriesAggs.subAggregation(addSecurityReportIssueCountAggregations(distributionAggregation));
  1229. return aggregationBuilder;
  1230. }
  1231. private static AggregationBuilder newSecurityReportSubAggregations(AggregationBuilder categoriesAggs, boolean includeCwe, @Nullable Collection<String> cwesInCategory) {
  1232. AggregationBuilder aggregationBuilder = addSecurityReportIssueCountAggregations(categoriesAggs);
  1233. if (includeCwe) {
  1234. final TermsAggregationBuilder cwesAgg = AggregationBuilders.terms(AGG_DISTRIBUTION)
  1235. .field(FIELD_ISSUE_CWE)
  1236. // 100 should be enough to display all CWEs. If not, the UI will be broken anyway
  1237. .size(MAX_FACET_SIZE);
  1238. if (cwesInCategory != null) {
  1239. cwesAgg.includeExclude(new IncludeExclude(cwesInCategory.toArray(new String[0]), new String[0]));
  1240. }
  1241. categoriesAggs.subAggregation(addSecurityReportIssueCountAggregations(cwesAgg));
  1242. }
  1243. return aggregationBuilder;
  1244. }
  1245. private static AggregationBuilder addSecurityReportIssueCountAggregations(AggregationBuilder categoryAggs) {
  1246. return categoryAggs
  1247. .subAggregation(
  1248. AggregationBuilders.filter(AGG_VULNERABILITIES, NON_RESOLVED_VULNERABILITIES_FILTER)
  1249. .subAggregation(
  1250. AggregationBuilders.terms(AGG_SEVERITIES).field(FIELD_ISSUE_SEVERITY)
  1251. .subAggregation(
  1252. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY))))
  1253. .subAggregation(AggregationBuilders.filter(AGG_TO_REVIEW_SECURITY_HOTSPOTS, TO_REVIEW_HOTSPOTS_FILTER)
  1254. .subAggregation(
  1255. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)))
  1256. .subAggregation(AggregationBuilders.filter(AGG_IN_REVIEW_SECURITY_HOTSPOTS, IN_REVIEW_HOTSPOTS_FILTER)
  1257. .subAggregation(
  1258. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)))
  1259. .subAggregation(AggregationBuilders.filter(AGG_REVIEWED_SECURITY_HOTSPOTS, REVIEWED_HOTSPOTS_FILTER)
  1260. .subAggregation(
  1261. AggregationBuilders.count(AGG_COUNT).field(FIELD_ISSUE_KEY)));
  1262. }
  1263. private static SearchSourceBuilder prepareNonClosedVulnerabilitiesAndHotspotSearch(String projectUuid, boolean isViewOrApp) {
  1264. BoolQueryBuilder componentFilter = boolQuery();
  1265. if (isViewOrApp) {
  1266. componentFilter.filter(QueryBuilders.termsLookupQuery(FIELD_ISSUE_BRANCH_UUID,
  1267. new TermsLookup(
  1268. TYPE_VIEW.getIndex().getName(),
  1269. projectUuid,
  1270. ViewIndexDefinition.FIELD_PROJECTS)));
  1271. } else {
  1272. componentFilter.filter(termQuery(FIELD_ISSUE_BRANCH_UUID, projectUuid));
  1273. }
  1274. SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  1275. return sourceBuilder
  1276. .query(
  1277. componentFilter
  1278. .should(NON_RESOLVED_VULNERABILITIES_FILTER)
  1279. .should(TO_REVIEW_HOTSPOTS_FILTER)
  1280. .should(IN_REVIEW_HOTSPOTS_FILTER)
  1281. .should(REVIEWED_HOTSPOTS_FILTER)
  1282. .minimumShouldMatch(1))
  1283. .size(0);
  1284. }
  1285. }