From e8732e24d023811bb542c0726dfeb1715849ba01 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Fri, 5 Aug 2016 18:46:03 +0200 Subject: [PATCH] SONAR-7943 WS api/qualitygates/deselect handles deselect by project uuid and project key --- .../server/qualitygate/QualityGates.java | 3 +- .../server/qualitygate/ws/DeselectAction.java | 55 +++- .../server/qualitygate/ws/SelectAction.java | 3 +- .../server/qualitygate/QualityGatesTest.java | 9 +- .../server/qualitygate/ws/AppActionTest.java | 30 +- .../qualitygate/ws/DeselectActionTest.java | 262 ++++++++++++++++++ .../qualitygate/ws/QualityGatesWsTest.java | 14 +- 7 files changed, 330 insertions(+), 46 deletions(-) create mode 100644 server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java index 786f38ed803..08bcbc4897a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGates.java @@ -397,7 +397,8 @@ public class QualityGates { private void checkPermission(Long projectId, DbSession session) { ComponentDto project = componentDao.selectOrFailById(session, projectId); - if (!userSession.hasPermission(GlobalPermissions.QUALITY_GATE_ADMIN) && !userSession.hasComponentPermission(UserRole.ADMIN, project.key())) { + if (!userSession.hasPermission(GlobalPermissions.QUALITY_GATE_ADMIN) + && !userSession.hasComponentUuidPermission(UserRole.ADMIN, project.uuid())) { throw new ForbiddenException("Insufficient privileges"); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java index 0579676dfcb..29c947d5afa 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/DeselectAction.java @@ -19,20 +19,33 @@ */ package org.sonar.server.qualitygate.ws; +import com.google.common.base.Optional; +import org.elasticsearch.common.Nullable; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; import org.sonar.server.qualitygate.QualityGates; import org.sonarqube.ws.client.qualitygate.QualityGatesWsParameters; +import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonarqube.ws.client.qualitygate.QualityGatesWsParameters.PARAM_PROJECT_ID; +import static org.sonarqube.ws.client.qualitygate.QualityGatesWsParameters.PARAM_PROJECT_KEY; public class DeselectAction implements QualityGatesWsAction { private final QualityGates qualityGates; + private final DbClient dbClient; + private final ComponentFinder componentFinder; - public DeselectAction(QualityGates qualityGates) { + public DeselectAction(QualityGates qualityGates, DbClient dbClient, ComponentFinder componentFinder) { this.qualityGates = qualityGates; + this.dbClient = dbClient; + this.componentFinder = componentFinder; } @Override @@ -44,20 +57,48 @@ public class DeselectAction implements QualityGatesWsAction { .setHandler(this); action.createParam(QualityGatesWsParameters.PARAM_GATE_ID) - .setDescription("Quality Gate ID") + .setDescription("Quality Gate id") .setRequired(true) - .setExampleValue("1"); + .setExampleValue("23"); action.createParam(PARAM_PROJECT_ID) - .setDescription("Project ID") - .setRequired(true) - .setExampleValue("12"); + .setDescription("Project id. Project id as an numeric value is deprecated since 6.1") + .setExampleValue(Uuids.UUID_EXAMPLE_01); + + action.createParam(PARAM_PROJECT_KEY) + .setDescription("Project key") + .setExampleValue(KEY_PROJECT_EXAMPLE_001) + .setSince("6.1"); } @Override public void handle(Request request, Response response) { - qualityGates.dissociateProject(QualityGatesWs.parseId(request, QualityGatesWsParameters.PARAM_GATE_ID), QualityGatesWs.parseId(request, PARAM_PROJECT_ID)); + ComponentDto project = getProject(request.param(PARAM_PROJECT_ID), request.param(PARAM_PROJECT_KEY)); + qualityGates.dissociateProject(QualityGatesWs.parseId(request, QualityGatesWsParameters.PARAM_GATE_ID), project.getId()); response.noContent(); } + private ComponentDto getProject(@Nullable String projectId, @Nullable String projectKey) { + DbSession dbSession = dbClient.openSession(false); + try { + return selectProjectById(dbSession, projectId) + .or(() -> componentFinder.getByUuidOrKey(dbSession, projectId, projectKey, ComponentFinder.ParamNames.PROJECT_ID_AND_KEY)); + } finally { + dbClient.closeSession(dbSession); + } + } + + private Optional selectProjectById(DbSession dbSession, @Nullable String projectId) { + if (projectId == null) { + return Optional.absent(); + } + + try { + long dbId = Long.parseLong(projectId); + return dbClient.componentDao().selectById(dbSession, dbId); + } catch (NumberFormatException e) { + return Optional.absent(); + } + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java index e60d1b73d61..0dcea16e9c1 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SelectAction.java @@ -75,8 +75,7 @@ public class SelectAction implements QualityGatesWsAction { action.createParam(PARAM_PROJECT_ID) .setDescription("Project id. Project id as an numeric value is deprecated since 6.1") - .setExampleValue(Uuids.UUID_EXAMPLE_01) - .setDeprecatedSince("6.1"); + .setExampleValue(Uuids.UUID_EXAMPLE_01); action.createParam(PARAM_PROJECT_KEY) .setDescription("Project key") diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGatesTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGatesTest.java index ec9af1871e2..9d8e25947c1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGatesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGatesTest.java @@ -41,10 +41,10 @@ import org.sonar.api.measures.Metric.ValueType; import org.sonar.api.measures.MetricFinder; import org.sonar.api.web.UserRole; import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDao; -import org.sonar.db.component.ComponentDto; import org.sonar.db.property.PropertiesDao; import org.sonar.db.property.PropertyDto; import org.sonar.db.qualitygate.QualityGateConditionDao; @@ -70,6 +70,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.db.component.ComponentTesting.newProjectDto; @RunWith(MockitoJUnitRunner.class) public class QualityGatesTest { @@ -94,9 +95,10 @@ public class QualityGatesTest { QualityGates underTest; static final String PROJECT_KEY = "SonarQube"; + static final String PROJECT_UUID = Uuids.UUID_EXAMPLE_01; UserSession authorizedProfileAdminUserSession = new MockUserSession("gaudol").setName("Olivier").setGlobalPermissions(GlobalPermissions.QUALITY_GATE_ADMIN); - UserSession authorizedProjectAdminUserSession = new MockUserSession("gaudol").setName("Olivier").addProjectPermissions(UserRole.ADMIN, PROJECT_KEY); + UserSession authorizedProjectAdminUserSession = new MockUserSession("gaudol").setName("Olivier").addProjectUuidPermissions(UserRole.ADMIN, PROJECT_UUID); UserSession unauthorizedUserSession = new MockUserSession("polop").setName("Polop"); UserSession unauthenticatedUserSession = new AnonymousMockUserSession(); @@ -110,9 +112,10 @@ public class QualityGatesTest { when(dbClient.propertiesDao()).thenReturn(propertiesDao); when(dbClient.componentDao()).thenReturn(componentDao); - when(componentDao.selectOrFailById(eq(dbSession), anyLong())).thenReturn(new ComponentDto().setId(1L).setKey(PROJECT_KEY)); + when(componentDao.selectOrFailById(eq(dbSession), anyLong())).thenReturn(newProjectDto(PROJECT_UUID).setId(1L).setKey(PROJECT_KEY)); underTest = new QualityGates(dbClient, metricFinder, userSessionRule, settings); + userSessionRule.set(authorizedProfileAdminUserSession); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/AppActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/AppActionTest.java index 619fee9c638..7e942c4be87 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/AppActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/AppActionTest.java @@ -20,13 +20,13 @@ package org.sonar.server.qualitygate.ws; import com.google.common.collect.ImmutableList; +import java.util.Collection; +import java.util.Locale; +import java.util.Map; import org.json.simple.JSONValue; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; -import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.sonar.api.i18n.I18n; import org.sonar.api.measures.Metric; @@ -39,40 +39,30 @@ import org.sonar.server.qualitygate.QualityGates; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.WsTester; -import java.util.Collection; -import java.util.Locale; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) public class AppActionTest { - @Mock - private QualityGates qGates; - - @Mock - private Periods periods; - - @Mock - private I18n i18n; + private QualityGates qGates = mock(QualityGates.class); + private Periods periods = mock(Periods.class); + private I18n i18n = mock(I18n.class); - WsTester tester; + WsTester ws; @Before public void setUp() { SelectAction selectAction = new SelectAction(mock(DbClient.class), mock(UserSessionRule.class), mock(ComponentFinder.class)); - tester = new WsTester(new QualityGatesWs( + ws = new WsTester(new QualityGatesWs( new ListAction(qGates), new ShowAction(qGates), new SearchAction(mock(QgateProjectFinder.class)), new CreateAction(qGates), new CopyAction(qGates), new DestroyAction(qGates), new RenameAction(qGates), new SetAsDefaultAction(qGates), new UnsetDefaultAction(qGates), new CreateConditionAction(qGates), new UpdateConditionAction(qGates), new DeleteConditionAction(qGates), - selectAction, new DeselectAction(qGates), new AppAction(qGates))); + selectAction, new DeselectAction(qGates, mock(DbClient.class), mock(ComponentFinder.class)), new AppAction(qGates))); } @Test @@ -94,7 +84,7 @@ public class AppActionTest { when(metric.isHidden()).thenReturn(false); when(qGates.gateMetrics()).thenReturn(ImmutableList.of(metric)); - String json = tester.newGetRequest("api/qualitygates", "app").execute().outputAsString(); + String json = ws.newGetRequest("api/qualitygates", "app").execute().outputAsString(); Map responseJson = (Map) JSONValue.parse(json); assertThat((Boolean) responseJson.get("edit")).isFalse(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java new file mode 100644 index 00000000000..91b3a0eb071 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java @@ -0,0 +1,262 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.qualitygate.ws; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.Settings; +import org.sonar.api.measures.MetricFinder; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.property.PropertyDto; +import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.qualitygate.QualityGates; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.sonar.core.permission.GlobalPermissions.QUALITY_GATE_ADMIN; +import static org.sonar.core.permission.GlobalPermissions.QUALITY_PROFILE_ADMIN; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.server.qualitygate.QualityGates.SONAR_QUALITYGATE_PROPERTY; + +public class DeselectActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + DbClient dbClient = db.getDbClient(); + DbSession dbSession = db.getSession(); + ComponentDbTester componentDb = new ComponentDbTester(db); + + QualityGates qualityGates = new QualityGates(dbClient, mock(MetricFinder.class), userSession, mock(Settings.class)); + + WsActionTester ws; + + DeselectAction underTest; + + @Before + public void setUp() { + ComponentFinder componentFinder = new ComponentFinder(dbClient); + underTest = new DeselectAction(qualityGates, dbClient, componentFinder); + ws = new WsActionTester(underTest); + + userSession.login("login").setGlobalPermissions(QUALITY_GATE_ADMIN); + } + + @Test + public void deselect_by_id() throws Exception { + ComponentDto project = insertProject(); + ComponentDto anotherProject = componentDb.insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + associateProjectToQualityGate(project.getId(), gateId); + associateProjectToQualityGate(anotherProject.getId(), gateId); + + callById(gateId, project.getId()); + + assertDeselected(project.getId()); + assertSelected(gateId, anotherProject.getId()); + } + + @Test + public void deselect_by_uuid() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + associateProjectToQualityGate(project.getId(), gateId); + + callByUuid(gateId, project.uuid()); + + assertDeselected(project.getId()); + } + + @Test + public void deselect_by_key() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + associateProjectToQualityGate(project.getId(), gateId); + + callByKey(gateId, project.getKey()); + + assertDeselected(project.getId()); + } + + @Test + public void project_admin() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + associateProjectToQualityGate(project.getId(), gateId); + + userSession.login("login").addProjectUuidPermissions(UserRole.ADMIN, project.uuid()); + + callByKey(gateId, project.getKey()); + + assertDeselected(project.getId()); + } + + @Test + public void system_admin() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + associateProjectToQualityGate(project.getId(), gateId); + + userSession.login("login").setGlobalPermissions(SYSTEM_ADMIN); + + callByKey(gateId, project.getKey()); + + assertDeselected(project.getId()); + } + + @Test + public void fail_when_no_quality_gate() throws Exception { + ComponentDto project = insertProject(); + + expectedException.expect(NotFoundException.class); + + callByKey("1", project.getKey()); + } + + @Test + public void fail_when_no_project_id() throws Exception { + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + + expectedException.expect(NotFoundException.class); + + callById(gateId, 1L); + } + + @Test + public void fail_when_no_project_key() throws Exception { + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + + expectedException.expect(NotFoundException.class); + + callByKey(gateId, "unknown"); + } + + @Test + public void fail_when_anonymous() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + userSession.anonymous(); + + expectedException.expect(ForbiddenException.class); + callByKey(gateId, project.getKey()); + } + + @Test + public void fail_when_not_project_admin() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + + userSession.login("login").addProjectUuidPermissions(UserRole.ISSUE_ADMIN, project.uuid()); + + expectedException.expect(ForbiddenException.class); + + callByKey(gateId, project.getKey()); + } + + @Test + public void fail_when_not_quality_gates_admin() throws Exception { + ComponentDto project = insertProject(); + QualityGateDto gate = insertQualityGate(); + String gateId = String.valueOf(gate.getId()); + + userSession.login("login").setGlobalPermissions(QUALITY_PROFILE_ADMIN); + + expectedException.expect(ForbiddenException.class); + + callByKey(gateId, project.getKey()); + } + + private ComponentDto insertProject() { + return componentDb.insertComponent(newProjectDto()); + } + + private QualityGateDto insertQualityGate() { + QualityGateDto gate = new QualityGateDto().setName("Custom"); + dbClient.qualityGateDao().insert(dbSession, gate); + dbSession.commit(); + return gate; + } + + private void callByKey(String gateId, String projectKey) { + ws.newRequest() + .setParam("gateId", String.valueOf(gateId)) + .setParam("projectKey", projectKey) + .execute(); + } + + private void callById(String gateId, Long projectId) { + ws.newRequest() + .setParam("gateId", String.valueOf(gateId)) + .setParam("projectId", String.valueOf(projectId)) + .execute(); + } + + private void callByUuid(String gateId, String projectUuid) { + ws.newRequest() + .setParam("gateId", String.valueOf(gateId)) + .setParam("projectId", projectUuid) + .execute(); + } + + private void associateProjectToQualityGate(long projectId, String gateId) { + dbClient.propertiesDao().insertProperty(dbSession, new PropertyDto() + .setResourceId(projectId) + .setValue(gateId) + .setKey(SONAR_QUALITYGATE_PROPERTY)); + db.commit(); + } + + private void assertDeselected(long projectId) { + assertThat(dbClient.propertiesDao().selectProjectProperty(projectId, SONAR_QUALITYGATE_PROPERTY)).isNull(); + } + + private void assertSelected(String qGateId, long projectId) { + assertThat(dbClient.propertiesDao().selectProjectProperty(projectId, SONAR_QUALITYGATE_PROPERTY).getValue()).isEqualTo(qGateId); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java index fce093f1e56..58916a1ab78 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java @@ -73,7 +73,7 @@ public class QualityGatesWsTest { new CreateAction(qGates), new CopyAction(qGates), new DestroyAction(qGates), new RenameAction(qGates), new SetAsDefaultAction(qGates), new UnsetDefaultAction(qGates), new CreateConditionAction(qGates), new UpdateConditionAction(qGates), new DeleteConditionAction(qGates), - selectAction, new DeselectAction(qGates), new AppAction(qGates))); + selectAction, new DeselectAction(qGates, mock(DbClient.class), mock(ComponentFinder.class)), new AppAction(qGates))); } @Test @@ -429,16 +429,4 @@ public class QualityGatesWsTest { ProjectQgateAssociationQuery query = queryCaptor.getValue(); assertThat(query.membership()).isEqualTo(ProjectQgateAssociationQuery.IN); } - - @Test - public void deselect_nominal() throws Exception { - long gateId = 42L; - long projectId = 666L; - tester.newPostRequest("api/qualitygates", "deselect") - .setParam("gateId", Long.toString(gateId)) - .setParam("projectId", Long.toString(projectId)) - .execute() - .assertNoContent(); - verify(qGates).dissociateProject(gateId, projectId); - } } -- 2.39.5