]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23064 Return 403 when trying to select/deselect a Quality Gate for AI-flagged...
authorMatteo Mara <matteo.mara@sonarsource.com>
Thu, 19 Sep 2024 12:59:45 +0000 (14:59 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 25 Sep 2024 20:02:53 +0000 (20:02 +0000)
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/DeselectActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/SelectActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java

index 5a31ff436c5127f4d6ec70e5c763b8f104a8c65a..c4fd31374e0aa4e80bbee1d59399ec9127f5afe3 100644 (file)
@@ -184,6 +184,11 @@ public class ComponentDbTester {
     return insertComponentAndBranchAndProject(ComponentTesting.newPrivateProjectDto(), true, branchPopulator, componentPopulator, projectPopulator);
   }
 
+  public ProjectData insertProjectWithAiCode() {
+    return insertComponentAndBranchAndProject(ComponentTesting.newPrivateProjectDto(), true, defaults(), defaults(), p -> p.setAiCodeAssurance(true));
+  }
+
+
   public final ComponentDto insertPublicPortfolio() {
     return insertComponentAndPortfolio(ComponentTesting.newPortfolio().setPrivate(false), false, defaults(), defaults());
   }
index 620569c575707739b1022942db1706cc481928e7..5f33072d2a796bd824e585e0a7ab18b38c5615e9 100644 (file)
@@ -20,8 +20,9 @@
 package org.sonar.server.qualitygate.ws;
 
 import java.util.Optional;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.web.UserRole;
@@ -31,33 +32,47 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ProjectData;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.TestComponentFinder;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.web.UserRole.ADMIN;
 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
+import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_GATE_NAME;
 
-public class DeselectActionIT {
+class DeselectActionIT {
 
-  @Rule
+  @RegisterExtension
   public UserSessionRule userSession = UserSessionRule.standalone();
-  @Rule
+  @RegisterExtension
   public DbTester db = DbTester.create();
 
   private final DbClient dbClient = db.getDbClient();
   private final ComponentFinder componentFinder = TestComponentFinder.from(db);
-  private final DeselectAction underTest = new DeselectAction(dbClient, new QualityGatesWsSupport(db.getDbClient(), userSession, componentFinder));
+  private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier = mock(AiCodeAssuranceVerifier.class);
+  private final DeselectAction underTest = new DeselectAction(dbClient, new QualityGatesWsSupport(db.getDbClient(), userSession,
+    componentFinder), aiCodeAssuranceVerifier);
   private final WsActionTester ws = new WsActionTester(underTest);
 
+  @BeforeEach
+  void setUp() {
+    when(aiCodeAssuranceVerifier.isAiCodeAssured(any())).thenReturn(false);
+  }
+
   @Test
-  public void deselect_by_key() {
+  void deselect_by_key() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
@@ -71,7 +86,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void project_admin() {
+  void project_admin() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
     associateProjectToQualityGate(project, qualityGate);
@@ -85,7 +100,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void other_project_should_not_be_updated() {
+  void other_project_should_not_be_updated() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
@@ -103,7 +118,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void default_is_used() {
+  void default_is_used() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
@@ -117,7 +132,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void fail_when_no_project_key() {
+  void fail_when_no_project_key() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
 
     assertThatThrownBy(() -> ws.newRequest()
@@ -127,7 +142,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void fail_when_anonymous() {
+  void fail_when_anonymous() {
     ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
     userSession.anonymous();
 
@@ -138,7 +153,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void fail_when_not_project_admin() {
+  void fail_when_not_project_admin() {
     ProjectData project = db.components().insertPrivateProject();
     userSession.logIn().addProjectPermission(UserRole.ISSUE_ADMIN, project.getProjectDto());
 
@@ -149,7 +164,7 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void fail_when_not_quality_gates_admin() {
+  void fail_when_not_quality_gates_admin() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
 
@@ -161,15 +176,16 @@ public class DeselectActionIT {
   }
 
   @Test
-  public void definition() {
+  void definition() {
     WebService.Action def = ws.getDef();
 
     assertThat(def.description()).isNotEmpty();
     assertThat(def.isPost()).isTrue();
     assertThat(def.since()).isEqualTo("4.3");
-    assertThat(def.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactly(
+    assertThat(def.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactlyInAnyOrder(
       tuple("6.6", "The parameter 'gateId' was removed"),
-      tuple("8.3", "The parameter 'projectId' was removed"));
+      tuple("8.3", "The parameter 'projectId' was removed"),
+      tuple("10.7", "It is not possible anymore to change the Quality Gate of a project flagged as containing AI code."));
 
     assertThat(def.params())
       .extracting(WebService.Param::key, WebService.Param::isRequired)
@@ -177,6 +193,23 @@ public class DeselectActionIT {
         tuple("projectKey", true));
   }
 
+  @Test
+  void whenAiCodeAssuranceIsSet_failIfEditionIsDeveloperOrHigher() {
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+    ProjectDto projectDto = db.components().insertProjectWithAiCode().getProjectDto();
+    when(aiCodeAssuranceVerifier.isAiCodeAssured(projectDto)).thenReturn(true);
+
+    userSession.logIn().addProjectPermission(ADMIN, projectDto);
+
+    TestRequest request = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, qualityGate.getName())
+      .setParam("projectKey", projectDto.getKey());
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(ForbiddenException.class)
+      .hasMessage("Quality gate cannot be changed for project with AI Code Assurance enabled.");
+  }
+
   private void associateProjectToQualityGate(ProjectDto project, QualityGateDto qualityGate) {
     db.qualityGates().associateProjectToQualityGate(project, qualityGate);
     db.commit();
@@ -196,4 +229,5 @@ public class DeselectActionIT {
       .isNotEmpty()
       .hasValue(qualityGate.getUuid());
   }
+
 }
index 9b193891199e527ef18fb58f8fdb395ea89edc1f..1a2a0843b85106dbddb1f9406763cbcff566294c 100644 (file)
 package org.sonar.server.qualitygate.ws;
 
 import java.util.Optional;
-import org.junit.Rule;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
 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.project.ProjectDto;
 import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.TestComponentFinder;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.sonar.api.web.UserRole.ADMIN;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_GATE_NAME;
 
-public class SelectActionIT {
+class SelectActionIT {
 
-  @Rule
+  @RegisterExtension
   public UserSessionRule userSession = UserSessionRule.standalone();
-  @Rule
+  @RegisterExtension
   public DbTester db = DbTester.create();
 
   private final DbClient dbClient = db.getDbClient();
   private final ComponentFinder componentFinder = TestComponentFinder.from(db);
+  private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier = mock(AiCodeAssuranceVerifier.class);
   private final SelectAction underTest = new SelectAction(dbClient,
-    new QualityGatesWsSupport(db.getDbClient(), userSession, componentFinder));
+    new QualityGatesWsSupport(db.getDbClient(), userSession, componentFinder), aiCodeAssuranceVerifier);
   private final WsActionTester ws = new WsActionTester(underTest);
 
+  @BeforeEach
+  void setUp() {
+    when(aiCodeAssuranceVerifier.isAiCodeAssured(any())).thenReturn(false);
+  }
+
   @Test
-  public void select_by_key() {
+  void select_by_key() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
@@ -70,7 +82,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void change_quality_gate_for_project() {
+  void change_quality_gate_for_project() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto initialQualityGate = db.qualityGates().insertQualityGate();
     QualityGateDto secondQualityGate = db.qualityGates().insertQualityGate();
@@ -90,7 +102,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void select_same_quality_gate_for_project_twice() {
+  void select_same_quality_gate_for_project_twice() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto initialQualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
@@ -109,7 +121,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void project_admin() {
+  void project_admin() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
     userSession.logIn().addProjectPermission(ADMIN, project);
@@ -123,7 +135,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void gate_administrator_can_associate_a_gate_to_a_project() {
+  void gate_administrator_can_associate_a_gate_to_a_project() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectDto project = db.components().insertPrivateProject().getProjectDto();
@@ -137,7 +149,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void fail_when_no_quality_gate() {
+  void fail_when_no_quality_gate() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
 
@@ -149,7 +161,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void fail_when_no_project_key() {
+  void fail_when_no_project_key() {
     userSession.addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -161,7 +173,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void fail_when_anonymous() {
+  void fail_when_anonymous() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
     userSession.anonymous();
@@ -174,7 +186,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void fail_when_not_project_admin() {
+  void fail_when_not_project_admin() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ProjectData project = db.components().insertPrivateProject();
     userSession.logIn().addProjectPermission(ISSUE_ADMIN, project.getProjectDto());
@@ -187,7 +199,7 @@ public class SelectActionIT {
   }
 
   @Test
-  public void fail_when_not_quality_gates_admin() {
+  void fail_when_not_quality_gates_admin() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
     userSession.logIn();
@@ -199,6 +211,23 @@ public class SelectActionIT {
       .isInstanceOf(ForbiddenException.class);
   }
 
+  @Test
+  void whenAiCodeAssuranceIsSet_failIfEditionIsDeveloperOrHigher() {
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+    ProjectDto projectDto = db.components().insertProjectWithAiCode().getProjectDto();
+    when(aiCodeAssuranceVerifier.isAiCodeAssured(projectDto)).thenReturn(true);
+
+    userSession.logIn().addProjectPermission(ADMIN, projectDto);
+
+    TestRequest request = ws.newRequest()
+      .setParam(PARAM_GATE_NAME, qualityGate.getName())
+      .setParam("projectKey", projectDto.getKey());
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(ForbiddenException.class)
+      .hasMessage("Quality gate cannot be changed for project with AI Code Assurance enabled.");
+  }
+
   private void assertSelected(QualityGateDto qualityGate, ProjectDto project) {
     Optional<String> qGateUuid = db.qualityGates().selectQGateUuidByProjectUuid(project.getUuid());
     assertThat(qGateUuid)
index 937bf359c921ad61375d46a7c8dac2ea481371ef..a9c008f3f171869abf78cba5b1a60cec6ee2f454 100644 (file)
@@ -26,6 +26,8 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.project.ProjectDto;
+import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier;
+import org.sonar.server.exceptions.ForbiddenException;
 
 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_PROJECT_KEY;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
@@ -34,10 +36,12 @@ public class DeselectAction implements QualityGatesWsAction {
 
   private final DbClient dbClient;
   private final QualityGatesWsSupport wsSupport;
+  private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier;
 
-  public DeselectAction(DbClient dbClient, QualityGatesWsSupport wsSupport) {
+  public DeselectAction(DbClient dbClient, QualityGatesWsSupport wsSupport, AiCodeAssuranceVerifier aiCodeAssuranceVerifier) {
     this.wsSupport = wsSupport;
     this.dbClient = dbClient;
+    this.aiCodeAssuranceVerifier = aiCodeAssuranceVerifier;
   }
 
   @Override
@@ -52,8 +56,10 @@ public class DeselectAction implements QualityGatesWsAction {
       .setPost(true)
       .setSince("4.3")
       .setHandler(this)
-      .setChangelog(new Change("6.6", "The parameter 'gateId' was removed"),
-        new Change("8.3", "The parameter 'projectId' was removed"));
+      .setChangelog(
+        new Change("10.7", "It is not possible anymore to change the Quality Gate of a project flagged as containing AI code."),
+        new Change("8.3", "The parameter 'projectId' was removed"),
+        new Change("6.6", "The parameter 'gateId' was removed"));
 
     action.createParam(PARAM_PROJECT_KEY)
       .setRequired(true)
@@ -72,8 +78,15 @@ public class DeselectAction implements QualityGatesWsAction {
   }
 
   private void dissociateProject(DbSession dbSession, ProjectDto project) {
-    wsSupport.checkCanAdminProject(project);
+    checkProjectQGCanChange(project);
     dbClient.projectQgateAssociationDao().deleteByProjectUuid(dbSession, project.getUuid());
     dbSession.commit();
   }
+
+  private void checkProjectQGCanChange(ProjectDto project) {
+    wsSupport.checkCanAdminProject(project);
+    if (aiCodeAssuranceVerifier.isAiCodeAssured(project)) {
+      throw new ForbiddenException("Quality gate cannot be changed for project with AI Code Assurance enabled.");
+    }
+  }
 }
index f92d4e37344d57521cc4b1b6ad7ad5698e32f422..b6390f3a7bc8e967a54884e17fa31822d9524d43 100644 (file)
@@ -27,6 +27,8 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.ai.code.assurance.AiCodeAssuranceVerifier;
+import org.sonar.server.exceptions.ForbiddenException;
 
 import static org.sonar.server.qualitygate.ws.CreateAction.NAME_MAXIMUM_LENGTH;
 import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.ACTION_SELECT;
@@ -37,10 +39,12 @@ import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 public class SelectAction implements QualityGatesWsAction {
   private final DbClient dbClient;
   private final QualityGatesWsSupport wsSupport;
+  private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier;
 
-  public SelectAction(DbClient dbClient, QualityGatesWsSupport wsSupport) {
+  public SelectAction(DbClient dbClient, QualityGatesWsSupport wsSupport, AiCodeAssuranceVerifier aiCodeAssuranceVerifier) {
     this.dbClient = dbClient;
     this.wsSupport = wsSupport;
+    this.aiCodeAssuranceVerifier = aiCodeAssuranceVerifier;
   }
 
   @Override
@@ -56,6 +60,7 @@ public class SelectAction implements QualityGatesWsAction {
       .setSince("4.3")
       .setHandler(this)
       .setChangelog(
+        new Change("10.7", "It is not possible anymore to change the Quality Gate of a project flagged as containing AI code."),
         new Change("10.0", "Parameter 'gateId' is removed. Use 'gateName' instead."),
         new Change("8.4", "Parameter 'gateName' added"),
         new Change("8.4", "Parameter 'gateId' is deprecated. Format changes from integer to string. Use 'gateName' instead."),
@@ -85,7 +90,7 @@ public class SelectAction implements QualityGatesWsAction {
       QualityGateDto qualityGate;
       qualityGate = wsSupport.getByName(dbSession, gateName);
       ProjectDto project = wsSupport.getProject(dbSession, projectKey);
-      wsSupport.checkCanAdminProject(project);
+      checkProjectQGCanChange(project);
 
       QualityGateDto currentQualityGate = dbClient.qualityGateDao().selectByProjectUuid(dbSession, project.getUuid());
       if (currentQualityGate == null) {
@@ -101,4 +106,11 @@ public class SelectAction implements QualityGatesWsAction {
     }
     response.noContent();
   }
+
+  private void checkProjectQGCanChange(ProjectDto project) {
+    wsSupport.checkCanAdminProject(project);
+    if (aiCodeAssuranceVerifier.isAiCodeAssured(project)) {
+      throw new ForbiddenException("Quality gate cannot be changed for project with AI Code Assurance enabled.");
+    }
+  }
 }