diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-05-11 15:16:07 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-05-13 10:10:16 +0200 |
commit | 9c96c2af00cc05d3bb850264b3c33d4f66e89217 (patch) | |
tree | cc393757a319e61c0c6655215468d0b6b102f0fe /server | |
parent | 27aa656e1f6031e54f004c9f91d7919f3cb79722 (diff) | |
download | sonarqube-9c96c2af00cc05d3bb850264b3c33d4f66e89217.tar.gz sonarqube-9c96c2af00cc05d3bb850264b3c33d4f66e89217.zip |
SONAR-7274 Return authorized projects in api/qualitygates/search
Diffstat (limited to 'server')
3 files changed, 360 insertions, 39 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QgateProjectFinder.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QgateProjectFinder.java index 08787b786d1..fdd4b53a135 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QgateProjectFinder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QgateProjectFinder.java @@ -19,22 +19,79 @@ */ package org.sonar.server.qualitygate; +import com.google.common.base.Function; +import com.google.common.base.Predicate; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nonnull; import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.Paging; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; import org.sonar.db.qualitygate.ProjectQgateAssociation; import org.sonar.db.qualitygate.ProjectQgateAssociationDao; import org.sonar.db.qualitygate.ProjectQgateAssociationDto; import org.sonar.db.qualitygate.ProjectQgateAssociationQuery; import org.sonar.db.qualitygate.QualityGateDao; -import org.sonar.db.qualitygate.QualityGateDto; -import org.sonar.server.exceptions.NotFoundException; - -import java.util.List; +import org.sonar.server.user.UserSession; -import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.FluentIterable.from; +import static org.sonar.api.utils.Paging.forPageIndex; +import static org.sonar.server.ws.WsUtils.checkFound; @ServerSide public class QgateProjectFinder { + private final DbClient dbClient; + private final QualityGateDao qualitygateDao; + private final ProjectQgateAssociationDao associationDao; + private final UserSession userSession; + + public QgateProjectFinder(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + this.qualitygateDao = dbClient.qualityGateDao(); + this.associationDao = dbClient.projectQgateAssociationDao(); + } + + public Association find(ProjectQgateAssociationQuery query) { + DbSession dbSession = dbClient.openSession(false); + try { + getQualityGateId(dbSession, query.gateId()); + List<ProjectQgateAssociationDto> projects = associationDao.selectProjects(dbSession, query); + List<ProjectQgateAssociationDto> authorizedProjects = keepAuthorizedProjects(dbSession, projects); + + Paging paging = forPageIndex(query.pageIndex()) + .withPageSize(query.pageSize()) + .andTotal(authorizedProjects.size()); + return new Association(toProjectAssociations(getPaginatedProjects(authorizedProjects, paging)), paging.hasNextPage()); + } finally { + dbClient.closeSession(dbSession); + } + } + + private Long getQualityGateId(DbSession dbSession, String gateId) { + return checkFound(qualitygateDao.selectById(dbSession, Long.valueOf(gateId)), "Quality gate '" + gateId + "' does not exists.").getId(); + } + + private static List<ProjectQgateAssociationDto> getPaginatedProjects(List<ProjectQgateAssociationDto> projects, Paging paging) { + return from(projects) + .skip(paging.offset()) + .limit(paging.pageSize()) + .toList(); + } + + private static List<ProjectQgateAssociation> toProjectAssociations(List<ProjectQgateAssociationDto> dtos) { + return from(dtos).transform(ToProjectAssociation.INSTANCE).toList(); + } + + private List<ProjectQgateAssociationDto> keepAuthorizedProjects(DbSession dbSession, List<ProjectQgateAssociationDto> projects) { + List<Long> projectIds = from(projects).transform(ToProjectId.INSTANCE).toList(); + Collection<Long> authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(dbSession, projectIds, userSession.getUserId(), UserRole.USER); + return from(projects).filter(new MatchProjectId(authorizedProjectIds)).toList(); + } + public static class Association { private List<ProjectQgateAssociation> projects; private boolean hasMoreResults; @@ -53,45 +110,35 @@ public class QgateProjectFinder { } } - private final QualityGateDao qualitygateDao; - private final ProjectQgateAssociationDao associationDao; + private enum ToProjectId implements Function<ProjectQgateAssociationDto, Long> { + INSTANCE; - public QgateProjectFinder(QualityGateDao qualitygateDao, ProjectQgateAssociationDao associationDao) { - this.qualitygateDao = qualitygateDao; - this.associationDao = associationDao; + @Override + public Long apply(@Nonnull ProjectQgateAssociationDto input) { + return input.getId(); + } } - public Association find(ProjectQgateAssociationQuery query) { - Long gateId = validateId(query.gateId()); - int pageSize = query.pageSize(); - int pageIndex = query.pageIndex(); - - int offset = (pageIndex - 1) * pageSize; - // Add one to page size in order to be able to know if there's more results or not - int limit = pageSize + 1; - List<ProjectQgateAssociationDto> dtos = associationDao.selectProjects(query, gateId, offset, limit); - boolean hasMoreResults = false; - if (dtos.size() == limit) { - hasMoreResults = true; - // Removed last entry as it's only need to know if there more results or not - dtos.remove(dtos.size() - 1); + private static class MatchProjectId implements Predicate<ProjectQgateAssociationDto> { + private final Collection<Long> projectIds; + + private MatchProjectId(Collection<Long> projectIds) { + this.projectIds = projectIds; } - return new Association(toProjectAssociations(dtos), hasMoreResults); - } - private Long validateId(String gateId) { - QualityGateDto qualityGateDto = qualitygateDao.selectById(Long.valueOf(gateId)); - if (qualityGateDto == null) { - throw new NotFoundException("Quality gate '" + gateId + "' does not exists."); + @Override + public boolean apply(@Nonnull ProjectQgateAssociationDto input) { + return projectIds.contains(input.getId()); } - return qualityGateDto.getId(); } - private List<ProjectQgateAssociation> toProjectAssociations(List<ProjectQgateAssociationDto> dtos) { - List<ProjectQgateAssociation> groups = newArrayList(); - for (ProjectQgateAssociationDto groupMembershipDto : dtos) { - groups.add(groupMembershipDto.toQgateAssociation()); + private enum ToProjectAssociation implements Function<ProjectQgateAssociationDto, ProjectQgateAssociation> { + INSTANCE; + + @Override + public ProjectQgateAssociation apply(@Nonnull ProjectQgateAssociationDto input) { + return input.toQgateAssociation(); } - return groups; } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java index faba83f326e..a859ed87d08 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/SearchAction.java @@ -19,12 +19,11 @@ */ package org.sonar.server.qualitygate.ws; -import org.sonar.api.server.ws.WebService.Param; - import com.google.common.io.Resources; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.Param; import org.sonar.api.utils.text.JsonWriter; import org.sonar.db.qualitygate.ProjectQgateAssociation; import org.sonar.db.qualitygate.ProjectQgateAssociationQuery; @@ -41,7 +40,8 @@ public class SearchAction implements QGateWsAction { @Override public void define(WebService.NewController controller) { WebService.NewAction action = controller.createAction("search") - .setDescription("Search for projects associated (or not) to a quality gate") + .setDescription("Search for projects associated (or not) to a quality gate.<br/>" + + "Only authorized projects for current user will be returned.") .setSince("4.3") .setResponseExample(Resources.getResource(this.getClass(), "example-search.json")) .setHandler(this); @@ -79,6 +79,7 @@ public class SearchAction implements QGateWsAction { JsonWriter writer = response.newJsonWriter(); writer.beginObject().prop("more", associations.hasMoreResults()); writer.name("results").beginArray(); + for (ProjectQgateAssociation project : associations.projects()) { writer.beginObject().prop("id", project.id()).prop("name", project.name()).prop(Param.SELECTED, project.isMember()).endObject(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QgateProjectFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QgateProjectFinderTest.java new file mode 100644 index 00000000000..04db472f3bb --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QgateProjectFinderTest.java @@ -0,0 +1,273 @@ +/* + * 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; + +import com.google.common.base.Function; +import java.util.List; +import java.util.Map; +import javax.annotation.Nonnull; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +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.ProjectQgateAssociation; +import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.db.user.GroupRoleDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserRoleDto; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.qualitygate.QgateProjectFinder.Association; +import org.sonar.server.tester.UserSessionRule; + +import static com.google.common.collect.FluentIterable.from; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.IN; +import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.OUT; +import static org.sonar.db.qualitygate.ProjectQgateAssociationQuery.builder; +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.qualitygate.QualityGates.SONAR_QUALITYGATE_PROPERTY; + +public class QgateProjectFinderTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + DbClient dbClient = dbTester.getDbClient(); + + DbSession dbSession = dbTester.getSession(); + + ComponentDbTester componentDbTester = new ComponentDbTester(dbTester); + + UserDto userDto; + + QualityGateDto qGate; + + QgateProjectFinder underTest = new QgateProjectFinder(dbClient, userSession); + + @Before + public void setUp() throws Exception { + userDto = newUserDto(); + dbClient.userDao().insert(dbSession, userDto); + + qGate = new QualityGateDto().setName("Default Quality Gate"); + dbClient.qualityGateDao().insert(qGate, dbSession); + + dbTester.commit(); + } + + @Test + public void return_empty_association() throws Exception { + Association result = underTest.find( + builder() + .gateId(Long.toString(qGate.getId())) + .build()); + + assertThat(result.projects()).isEmpty(); + } + + @Test + public void return_all_projects() throws Exception { + ComponentDto associatedProject = insertProjectAuthorizedToAnyone(newProjectDto()); + ComponentDto unassociatedProject = insertProjectAuthorizedToAnyone(newProjectDto()); + associateProjectToQualitGate(associatedProject.getId()); + + Association result = underTest.find( + builder() + .gateId(Long.toString(qGate.getId())) + .build()); + + Map<Long, ProjectQgateAssociation> projectsById = projectsById(result.projects()); + assertThat(projectsById).hasSize(2); + + verifyProject(projectsById.get(associatedProject.getId()), true, associatedProject.name()); + verifyProject(projectsById.get(unassociatedProject.getId()), false, unassociatedProject.name()); + } + + @Test + public void return_only_associated_project() throws Exception { + ComponentDto associatedProject = insertProjectAuthorizedToAnyone(newProjectDto()); + insertProjectAuthorizedToAnyone(newProjectDto()); + associateProjectToQualitGate(associatedProject.getId()); + + Association result = underTest.find( + builder() + .membership(IN) + .gateId(Long.toString(qGate.getId())) + .build()); + + Map<Long, ProjectQgateAssociation> projectsById = projectsById(result.projects()); + assertThat(projectsById).hasSize(1); + verifyProject(projectsById.get(associatedProject.getId()), true, associatedProject.name()); + } + + @Test + public void return_only_unassociated_project() throws Exception { + ComponentDto associatedProject = insertProjectAuthorizedToAnyone(newProjectDto()); + ComponentDto unassociatedProject = insertProjectAuthorizedToAnyone(newProjectDto()); + associateProjectToQualitGate(associatedProject.getId()); + + Association result = underTest.find( + builder() + .membership(OUT) + .gateId(Long.toString(qGate.getId())) + .build()); + + Map<Long, ProjectQgateAssociation> projectsById = projectsById(result.projects()); + assertThat(projectsById).hasSize(1); + verifyProject(projectsById.get(unassociatedProject.getId()), false, unassociatedProject.name()); + } + + @Test + public void return_only_authorized_projects() throws Exception { + userSession.login(userDto.getLogin()).setUserId(userDto.getId().intValue()); + ComponentDto project1 = componentDbTester.insertComponent(newProjectDto()); + componentDbTester.insertComponent(newProjectDto()); + + // User can only see project 1 + dbClient.roleDao().insertUserRole(dbSession, new UserRoleDto().setUserId(userDto.getId()).setResourceId(project1.getId()).setRole(UserRole.USER)); + dbTester.commit(); + + Association result = underTest.find( + builder() + .gateId(Long.toString(qGate.getId())) + .build()); + + verifyProjects(result, project1.getId()); + } + + @Test + public void test_paging() throws Exception { + ComponentDto project1 = insertProjectAuthorizedToAnyone(newProjectDto().setName("Project 1")); + ComponentDto project2 = insertProjectAuthorizedToAnyone(newProjectDto().setName("Project 2")); + ComponentDto project3 = insertProjectAuthorizedToAnyone(newProjectDto().setName("Project 3")); + associateProjectToQualitGate(project1.getId()); + + // Return partial result on first page + verifyPaging(underTest.find( + builder().gateId(Long.toString(qGate.getId())) + .pageIndex(1) + .pageSize(1) + .build()), + true, project1.getId()); + + // Return partial result on second page + verifyPaging(underTest.find( + builder().gateId(Long.toString(qGate.getId())) + .pageIndex(2) + .pageSize(1) + .build()), + true, project2.getId()); + + // Return partial result on first page + verifyPaging(underTest.find( + builder().gateId(Long.toString(qGate.getId())) + .pageIndex(1) + .pageSize(2) + .build()), + true, project1.getId(), project2.getId()); + + // Return all result on first page + verifyPaging(underTest.find( + builder().gateId(Long.toString(qGate.getId())) + .pageIndex(1) + .pageSize(3) + .build()), + false, project1.getId(), project2.getId(), project3.getId()); + + // Return no result as page index is off limit + verifyPaging(underTest.find( + builder().gateId(Long.toString(qGate.getId())) + .pageIndex(3) + .pageSize(3) + .build()), + false); + } + + @Test + public void fail_on_unknown_quality_gate() throws Exception { + expectedException.expect(NotFoundException.class); + underTest.find(builder().gateId("123").build()); + } + + private void verifyProject(ProjectQgateAssociation project, boolean expectedMembership, String expectedName) { + assertThat(project.isMember()).isEqualTo(expectedMembership); + assertThat(project.name()).isEqualTo(expectedName); + } + + private void verifyProjects(Association association, Long... expectedProjectIds) { + assertThat(association.projects()).extracting("id").containsOnly(expectedProjectIds); + } + + private void verifyPaging(Association association, boolean expectedHasMoreResults, Long... expectedProjectIds) { + assertThat(association.hasMoreResults()).isEqualTo(expectedHasMoreResults); + assertThat(association.projects()).extracting("id").containsOnly(expectedProjectIds); + } + + private void associateProjectToQualitGate(long projectId) { + dbClient.propertiesDao().insertProperty( + new PropertyDto().setKey(SONAR_QUALITYGATE_PROPERTY) + .setResourceId(projectId) + .setValue(Long.toString(qGate.getId()))); + dbTester.commit(); + } + + private ComponentDto insertProjectAuthorizedToAnyone(ComponentDto project) { + componentDbTester.insertComponent(project); + dbClient.roleDao().insertGroupRole(dbSession, new GroupRoleDto().setGroupId(null).setResourceId(project.getId()).setRole(UserRole.USER)); + dbSession.commit(); + return project; + } + + private ComponentDto insertProjectAuthorizedToUser(ComponentDto project, UserDto userDto) { + componentDbTester.insertComponent(project); + dbClient.roleDao().insertGroupRole(dbSession, new GroupRoleDto().setGroupId(null).setResourceId(project.getId()).setRole(UserRole.USER)); + dbSession.commit(); + return project; + } + + private static Map<Long, ProjectQgateAssociation> projectsById(List<ProjectQgateAssociation> projects) { + return from(projects).uniqueIndex(ProjectToId.INSTANCE); + } + + private enum ProjectToId implements Function<ProjectQgateAssociation, Long> { + INSTANCE; + + @Override + public Long apply(@Nonnull ProjectQgateAssociation input) { + return input.id(); + } + } +} |