]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22914 Create and update APIs for CVEs
authorantoine.vinot <antoine.vinot@sonarsource.com>
Wed, 4 Sep 2024 12:31:58 +0000 (14:31 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 12 Sep 2024 20:02:54 +0000 (20:02 +0000)
server/sonar-db-dao/src/it/java/org/sonar/db/issue/IssueDaoIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDto.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/issue/IssueDbTester.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionDependenciesIT.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsResponseFormatter.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java
sonar-ws/src/main/protobuf/ws-hotspots.proto
sonar-ws/src/main/protobuf/ws-issues.proto

index 6d6a2e9bd2018065e6e329436b0274a14a75db13..13195fb952eaf036e2ff9394f6443b1e1ccd9605 100644 (file)
@@ -53,6 +53,8 @@ import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.dependency.CveDto;
+import org.sonar.db.dependency.IssuesDependencyDto;
 import org.sonar.db.protobuf.DbIssues;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
@@ -218,6 +220,21 @@ class IssueDaoIT {
         tuple(LOW, SECURITY));
   }
 
+  @Test
+  void selectByKeys_shouldFetchCveIds() {
+    prepareTables();
+    var cveDto1 = new CveDto("cve_uuid_1", "CVE-123", "Some CVE description", 1.0, 2.0, 3.0, 4L, 5L, 6L, 7L);
+    db.getDbClient().cveDao().insert(db.getSession(), cveDto1);
+    var cveDto2 = new CveDto("cve_uuid_2", "CVE-456", "Some CVE description", 1.0, 2.0, 3.0, 4L, 5L, 6L, 7L);
+    db.getDbClient().cveDao().insert(db.getSession(), cveDto2);
+    db.issues().insertIssuesDependency(new IssuesDependencyDto(ISSUE_KEY1, cveDto1.uuid()));
+    db.issues().insertIssuesDependency(new IssuesDependencyDto(ISSUE_KEY2, cveDto2.uuid()));
+
+    List<IssueDto> issues = underTest.selectByKeys(db.getSession(), asList("I1", "I2", "I3"));
+
+    assertThat(issues).extracting(IssueDto::getCveId).containsExactlyInAnyOrder(cveDto1.id(), cveDto2.id());
+  }
+
   @Test
   void scrollIndexationIssues_shouldReturnDto() throws SQLException {
     ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
index eae7cd669b1ab83ebcdedcdf801ecf06701844da..c60fdb3075d6a46b787ef8ea67d217e5625d0561 100644 (file)
@@ -119,6 +119,9 @@ public final class IssueDto implements Serializable {
   private CleanCodeAttribute cleanCodeAttribute;
   private CleanCodeAttribute ruleCleanCodeAttribute;
 
+  //issues dependency fields, one-one relationship
+  private String cveId;
+
   public IssueDto() {
     // nothing to do
   }
@@ -873,6 +876,14 @@ public final class IssueDto implements Serializable {
     return this;
   }
 
+  public String getCveId() {
+    return cveId;
+  }
+
+  public void setCveId(String cveId) {
+    this.cveId = cveId;
+  }
+
   @Override
   public String toString() {
     return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
index 422e50a7922fdb1a2439da6ff1377000e835d2ed..eba1d964f016179d1b5080282b3ae8b81c98fbed 100644 (file)
   <select id="selectByKeys" parameterType="map" resultMap="issueResultMap">
     select
     <include refid="issueColumns"/>,
-    u.login as assigneeLogin
+    u.login as assigneeLogin,
+    cve.id as cveId
     from issues i
     inner join rules r on r.uuid=i.rule_uuid
     inner join components p on p.uuid=i.component_uuid
     left join new_code_reference_issues n on i.kee = n.issue_key
     left outer join issues_impacts ii on i.kee = ii.issue_key
     left outer join rules_default_impacts rdi on r.uuid = rdi.rule_uuid
+    left join issues_dependency dep on i.kee = dep.issue_uuid
+    left join cves cve on dep.cve_uuid = cve.uuid
     where i.kee in
     <foreach collection="list" open="(" close=")" item="key" separator=",">
       #{key,jdbcType=VARCHAR}
index de399502cc9477a83849a107403f04f4dcf388fe..051dd9b0e373dff2a3c2d65bc2abf8ed56e3d679 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.db.DbTester;
 import org.sonar.db.component.BranchDto;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ProjectData;
+import org.sonar.db.dependency.IssuesDependencyDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.user.UserDto;
 
@@ -255,4 +256,9 @@ public class IssueDbTester {
     db.getDbClient().issueDao().insertAsNewCodeOnReferenceBranch(db.getSession(), IssueTesting.newCodeReferenceIssue(issue));
     db.commit();
   }
+
+  public void insertIssuesDependency(IssuesDependencyDto issuesDependencyDto) {
+    db.getDbClient().issuesDependencyDao().insert(db.getSession(), issuesDependencyDto);
+    db.commit();
+  }
 }
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionDependenciesIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/SearchActionDependenciesIT.java
new file mode 100644 (file)
index 0000000..87b282c
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.hotspot.ws;
+
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ProjectData;
+import org.sonar.db.dependency.CveDto;
+import org.sonar.db.dependency.IssuesDependencyDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.component.ComponentFinder;
+import org.sonar.server.component.TestComponentFinder;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.issue.TextRangeResponseFormatter;
+import org.sonar.server.issue.index.AsyncIssueIndexing;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueIndexSyncProgressChecker;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.issue.index.IssueIteratorFactory;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Hotspots.SearchWsResponse;
+import org.sonarqube.ws.Hotspots.SearchWsResponse.Hotspot;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+class SearchActionDependenciesIT {
+
+  @RegisterExtension
+  private final UserSessionRule userSession = UserSessionRule.standalone();
+  @RegisterExtension
+  private final DbTester db = DbTester.create();
+  @RegisterExtension
+  private final EsTester es = EsTester.create();
+
+  private final TestSystem2 system2 = new TestSystem2();
+  private final DbClient dbClient = db.getDbClient();
+  private final IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
+  private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), mock(AsyncIssueIndexing.class));
+  private final PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer);
+  private final HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(new TextRangeResponseFormatter());
+  private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = mock(IssueIndexSyncProgressChecker.class);
+  private final ComponentFinder componentFinder = TestComponentFinder.from(db);
+  private final SearchAction underTest = new SearchAction(dbClient, userSession, issueIndex,
+    issueIndexSyncProgressChecker, responseFormatter, system2, componentFinder);
+  private final WsActionTester ws = new WsActionTester(underTest);
+
+  private RuleDto rule;
+  private ProjectData projectData;
+  private ComponentDto project;
+  private ComponentDto projectFile;
+
+  @BeforeEach
+  void setup() {
+    rule = db.rules().insertHotspotRule();
+    projectData = db.components().insertPublicProject();
+    project = projectData.getMainBranchComponent();
+    projectFile = db.components().insertComponent(newFileDto(project));
+  }
+
+  @Test
+  void search_whenAttachedToCve_shouldReturnsCveId() {
+    insertHotspotWithCve("1");
+    insertHotspotWithCve("2");
+    allowAnyoneOnProjects(projectData.getProjectDto());
+    issueIndexer.indexAllIssues();
+
+    SearchWsResponse searchWsResponse = ws.newRequest().setParam("project", project.getKey()).executeProtobuf(SearchWsResponse.class);
+
+    assertThat(searchWsResponse.getHotspotsList())
+      .extracting(Hotspot::getKey, Hotspot::getCveId).containsExactlyInAnyOrder(tuple("hotspot_key_1", "CVE-1"), tuple("hotspot_key_2", "CVE-2"));
+  }
+
+  private void insertHotspotWithCve(String suffix) {
+    IssueDto issueDto = db.issues().insertHotspot(rule, project, projectFile, issue -> issue.setKee("hotspot_key_"+suffix));
+    var cveDto = new CveDto("cve_uuid_"+suffix, "CVE-"+suffix, "Some CVE description "+suffix, 1.0, 2.0, 3.0, 4L, 5L, 6L, 7L);
+    db.getDbClient().cveDao().insert(db.getSession(), cveDto);
+    db.issues().insertIssuesDependency(new IssuesDependencyDto(issueDto.getKee(), cveDto.uuid()));
+  }
+
+  private void allowAnyoneOnProjects(ProjectDto... projects) {
+    userSession.registerProjects(projects);
+    Arrays.stream(projects).forEach(permissionIndexer::allowOnlyAnyone);
+  }
+
+}
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java
new file mode 100644 (file)
index 0000000..f7bf2db
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.ws;
+
+import java.time.Clock;
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.utils.Durations;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ProjectData;
+import org.sonar.db.dependency.CveDto;
+import org.sonar.db.dependency.IssuesDependencyDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.server.common.avatar.AvatarResolverImpl;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TextRangeResponseFormatter;
+import org.sonar.server.issue.TransitionService;
+import org.sonar.server.issue.index.AsyncIssueIndexing;
+import org.sonar.server.issue.index.IssueIndex;
+import org.sonar.server.issue.index.IssueIndexSyncProgressChecker;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.issue.index.IssueIteratorFactory;
+import org.sonar.server.issue.index.IssueQueryFactory;
+import org.sonar.server.issue.workflow.FunctionExecutor;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+import org.sonar.server.permission.index.PermissionIndexerTester;
+import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Issues.SearchWsResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+class SearchActionDependenciesIT {
+
+  @RegisterExtension
+  private final UserSessionRule userSession = UserSessionRule.standalone();
+  @RegisterExtension
+  private final DbTester db = DbTester.create();
+  @RegisterExtension
+  private final EsTester es = EsTester.create();
+
+  private final DbClient dbClient = db.getDbClient();
+  private final IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession));
+  private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), mock(AsyncIssueIndexing.class));
+  private final IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession);
+  private final IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
+  private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
+  private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
+  private final Languages languages = new Languages();
+  private final UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl());
+  private final SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new TextRangeResponseFormatter(), userFormatter);
+  private final PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer);
+
+  private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = new IssueIndexSyncProgressChecker(db.getDbClient());
+
+  private final WsActionTester ws = new WsActionTester(
+    new SearchAction(userSession, issueIndex, issueQueryFactory, issueIndexSyncProgressChecker, searchResponseLoader, searchResponseFormat,
+      System2.INSTANCE, dbClient));
+
+  private RuleDto rule;
+  private ProjectData projectData;
+  private ComponentDto project;
+  private ComponentDto projectFile;
+
+  @BeforeEach
+  void setup() {
+    rule = db.rules().insertIssueRule();
+    projectData = db.components().insertPublicProject();
+    project = projectData.getMainBranchComponent();
+    projectFile = db.components().insertComponent(newFileDto(project));
+  }
+
+  @Test
+  void search_whenAttachedToCve_shouldReturnsCveId() {
+    insertIssueWithCve("1");
+    insertIssueWithCve("2");
+    allowAnyoneOnProjects(projectData.getProjectDto());
+    issueIndexer.indexAllIssues();
+
+    SearchWsResponse searchWsResponse = ws.newRequest().executeProtobuf(SearchWsResponse.class);
+
+    assertThat(searchWsResponse.getIssuesList())
+      .extracting(Issue::getKey, Issue::getCveId).containsExactlyInAnyOrder(tuple("issue_key_1", "CVE-1"), tuple("issue_key_2", "CVE-2"));
+  }
+
+  private void insertIssueWithCve(String suffix) {
+    IssueDto issueDto = db.issues().insertIssue(rule, project, projectFile, issue -> issue.setKee("issue_key_"+suffix));
+    var cveDto = new CveDto("cve_uuid_"+suffix, "CVE-"+suffix, "Some CVE description "+suffix, 1.0, 2.0, 3.0, 4L, 5L, 6L, 7L);
+    db.getDbClient().cveDao().insert(db.getSession(), cveDto);
+    db.issues().insertIssuesDependency(new IssuesDependencyDto(issueDto.getKee(), cveDto.uuid()));
+  }
+
+  private void allowAnyoneOnProjects(ProjectDto... projects) {
+    userSession.registerProjects(projects);
+    Arrays.stream(projects).forEach(permissionIndexer::allowOnlyAnyone);
+  }
+
+}
index 90ab338bb21cc2b263a059582156251fbefd3451..16afe8303c8057963db7782963fe517b6f7d1d03 100644 (file)
@@ -113,6 +113,7 @@ public class HotspotWsResponseFormatter {
       builder.setCreationDate(formatDateTime(hotspot.getIssueCreationDate()));
       builder.setUpdateDate(formatDateTime(hotspot.getIssueUpdateDate()));
       completeHotspotLocations(hotspot, builder, searchResponseData);
+      ofNullable(hotspot.getCveId()).ifPresent(builder::setCveId);
       hotspotsList.add(builder.build());
     }
     return hotspotsList;
index 318ff72b1f6f181c6e6b3fb82118c136225e0023..e309eed6fae5a2e9c018a84416a10f22bd14c6e6 100644 (file)
@@ -230,6 +230,8 @@ public class SearchResponseFormat {
 
     issueBuilder.setScope(UNIT_TEST_FILE.equals(component.qualifier()) ? IssueScope.TEST.name() : IssueScope.MAIN.name());
     issueBuilder.setPrioritizedRule(dto.isPrioritizedRule());
+
+    Optional.ofNullable(dto.getCveId()).ifPresent(issueBuilder::setCveId);
   }
 
   private static void addAdditionalFieldsToIssueBuilder(Collection<SearchAdditionalField> fields, SearchResponseData data, IssueDto dto, Issue.Builder issueBuilder) {
index 94455620be727c3f6bc34ddd76f871963ecda1f7..47706fd3e39f4565e6534dd15d0619ae50bd79a0 100644 (file)
@@ -126,7 +126,8 @@ public class SearchResponseLoader {
       .toList();
 
     return issueKeys.stream()
-      .map(new KeyToIssueFunction(unorderedIssues)).filter(Objects::nonNull)
+      .map(new KeyToIssueFunction(unorderedIssues))
+      .filter(Objects::nonNull)
       .toList();
   }
 
index ef4721c44ddade0da1e30475333e43c7f8dd6ac6..4b2779f41c33d6ebc79e233b0ef1c5c28cf683be 100644 (file)
@@ -50,6 +50,7 @@ message SearchWsResponse {
     repeated sonarqube.ws.commons.Flow flows = 15;
     optional string ruleKey = 16;
     repeated sonarqube.ws.commons.MessageFormatting messageFormattings = 17;
+    optional string cveId = 18;
   }
 }
 
index dcc65a70ee45e0082df98df56f45e270a6590982..647d461b2b8ad908e92f9b87926211f0b060d7c9 100644 (file)
@@ -168,6 +168,7 @@ message Issue {
   repeated sonarqube.ws.commons.Impact impacts = 42;
   optional string issueStatus = 43;
   optional bool prioritizedRule = 44;
+  optional string cveId = 45;
 }
 
 message Transitions {