From def87ea26a53c40af92b225d1c2e03d1a28016da Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Thu, 19 Oct 2017 17:50:42 +0200 Subject: [PATCH] SONAR-9262 Prevent using files/dirs/modules facets when no proj/org --- .../sonar/server/issue/ws/SearchAction.java | 11 ++ .../issue/ws/SearchActionComponentsTest.java | 137 +++++++++++++++++- .../server/issue/ws/SearchActionTest.java | 4 + 3 files changed, 147 insertions(+), 5 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 7e99634b893..e9fac5edb66 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -19,6 +19,7 @@ */ package org.sonar.server.issue.ws; +import com.google.common.base.Joiner; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import java.util.Arrays; @@ -51,6 +52,7 @@ import org.sonar.server.user.UserSession; import org.sonarqube.ws.Issues.SearchWsResponse; import org.sonarqube.ws.client.issue.SearchWsRequest; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Sets.newHashSet; import static java.lang.String.format; @@ -107,6 +109,8 @@ public class SearchAction implements IssuesWsAction { private static final String INTERNAL_PARAMETER_DISCLAIMER = "This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. "; private static final Set IGNORED_FACETS = newHashSet(PARAM_PLANNED, DEPRECATED_PARAM_ACTION_PLANS, PARAM_REPORTERS); + private static final Set FACETS_REQUIRING_PROJECT_OR_ORGANIZATION = newHashSet(PARAM_FILE_UUIDS, PARAM_DIRECTORIES, PARAM_MODULE_UUIDS); + private static final Joiner COMA_JOINER = Joiner.on(","); private final UserSession userSession; private final IssueIndex issueIndex; @@ -333,6 +337,13 @@ public class SearchAction implements IssuesWsAction { // This is a constraint from webapp UX. completeFacets(facets, request, wsRequest); collectFacets(collector, facets); + + Set facetsRequiringProjectOrOrganizationParameter = facets.getNames().stream() + .filter(FACETS_REQUIRING_PROJECT_OR_ORGANIZATION::contains) + .collect(MoreCollectors.toSet()); + checkArgument(facetsRequiringProjectOrOrganizationParameter.isEmpty() || + (!query.projectUuids().isEmpty()) || query.organizationUuid() != null, "Facet(s) '%s' require to also filter by project or organization", + COMA_JOINER.join(facetsRequiringProjectOrOrganizationParameter)); } SearchResponseData data = searchResponseLoader.load(collector, facets); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java index b00028310b7..ca0539ed339 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Date; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.config.internal.MapSettings; import org.sonar.api.resources.Languages; import org.sonar.api.resources.Qualifiers; @@ -82,6 +83,8 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_ public class SearchActionComponentsTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); @Rule public UserSessionRule userSession = UserSessionRule.standalone(); @Rule @@ -322,8 +325,9 @@ public class SearchActionComponentsTest { } @Test - public void display_file_facet() throws Exception { - ComponentDto project = db.components().insertPublicProject(p -> p.setDbKey("PK1")); + public void display_file_facet_with_project() throws Exception { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPublicProject(organization, p -> p.setDbKey("PK1")); ComponentDto file1 = db.components().insertComponent(newFileDto(project, null, "F1").setDbKey("FK1")); ComponentDto file2 = db.components().insertComponent(newFileDto(project, null, "F2").setDbKey("FK2")); ComponentDto file3 = db.components().insertComponent(newFileDto(project, null, "F3").setDbKey("FK3")); @@ -334,13 +338,52 @@ public class SearchActionComponentsTest { indexIssues(); ws.newRequest() - .setParam(IssuesWsParameters.PARAM_COMPONENT_UUIDS, project.uuid()) + .setParam(IssuesWsParameters.PARAM_COMPONENT_KEYS, project.getKey()) + .setParam(IssuesWsParameters.PARAM_FILE_UUIDS, file1.uuid() + "," + file3.uuid()) + .setParam(WebService.Param.FACETS, "fileUuids") + .execute() + .assertJson(this.getClass(), "display_file_facet.json"); + } + + @Test + public void display_file_facet_with_organization() throws Exception { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPublicProject(organization, p -> p.setDbKey("PK1")); + ComponentDto file1 = db.components().insertComponent(newFileDto(project, null, "F1").setDbKey("FK1")); + ComponentDto file2 = db.components().insertComponent(newFileDto(project, null, "F2").setDbKey("FK2")); + ComponentDto file3 = db.components().insertComponent(newFileDto(project, null, "F3").setDbKey("FK3")); + RuleDefinitionDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.of("xoo", "x1"))); + db.issues().insert(rule, project, file1, i -> i.setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")); + db.issues().insert(rule, project, file2, i -> i.setKee("2bd4eac2-b650-4037-80bc-7b1182fd47d4")); + allowAnyoneOnProjects(project); + indexIssues(); + + ws.newRequest() + .setParam(IssuesWsParameters.PARAM_ORGANIZATION, organization.getKey()) .setParam(IssuesWsParameters.PARAM_FILE_UUIDS, file1.uuid() + "," + file3.uuid()) .setParam(WebService.Param.FACETS, "fileUuids") .execute() .assertJson(this.getClass(), "display_file_facet.json"); } + @Test + public void fail_to_display_file_facet_when_no_organization_or_project_is_set() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto file = db.components().insertComponent(newFileDto(project)); + RuleDefinitionDto rule = db.rules().insert(); + db.issues().insert(rule, project, file); + allowAnyoneOnProjects(project); + indexIssues(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Facet(s) 'fileUuids' require to also filter by project or organization"); + + ws.newRequest() + .setParam(IssuesWsParameters.PARAM_FILE_UUIDS, file.uuid()) + .setParam(WebService.Param.FACETS, "fileUuids") + .execute(); + } + @Test public void search_by_directory_path() throws Exception { ComponentDto project = db.components().insertPublicProject(p -> p.setDbKey("PK1")); @@ -420,7 +463,7 @@ public class SearchActionComponentsTest { } @Test - public void display_module_facet() throws Exception { + public void display_module_facet_using_project() throws Exception { ComponentDto project = db.components().insertPublicProject(p -> p.setDbKey("PK1")); ComponentDto module = db.components().insertComponent(newModuleDto("M1", project).setDbKey("MK1")); ComponentDto subModule1 = db.components().insertComponent(newModuleDto("SUBM1", module).setDbKey("SUBMK1")); @@ -435,6 +478,32 @@ public class SearchActionComponentsTest { indexIssues(); ws.newRequest() + .setParam(IssuesWsParameters.PARAM_PROJECTS, project.getKey()) + .setParam(IssuesWsParameters.PARAM_COMPONENT_UUIDS, module.uuid()) + .setParam(IssuesWsParameters.PARAM_MODULE_UUIDS, subModule1.uuid() + "," + subModule3.uuid()) + .setParam(WebService.Param.FACETS, "moduleUuids") + .execute() + .assertJson(this.getClass(), "display_module_facet.json"); + } + + @Test + public void display_module_facet_using_organization() throws Exception { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPublicProject(organization, p -> p.setDbKey("PK1")); + ComponentDto module = db.components().insertComponent(newModuleDto("M1", project).setDbKey("MK1")); + ComponentDto subModule1 = db.components().insertComponent(newModuleDto("SUBM1", module).setDbKey("SUBMK1")); + ComponentDto subModule2 = db.components().insertComponent(newModuleDto("SUBM2", module).setDbKey("SUBMK2")); + ComponentDto subModule3 = db.components().insertComponent(newModuleDto("SUBM3", module).setDbKey("SUBMK3")); + ComponentDto file1 = db.components().insertComponent(newFileDto(subModule1, null, "F1").setDbKey("FK1")); + ComponentDto file2 = db.components().insertComponent(newFileDto(subModule2, null, "F2").setDbKey("FK2")); + RuleDefinitionDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.of("xoo", "x1"))); + IssueDto issue1 = db.issues().insert(rule, project, file1, i -> i.setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")); + IssueDto issue2 = db.issues().insert(rule, project, file2, i -> i.setKee("2bd4eac2-b650-4037-80bc-7b1182fd47d4")); + allowAnyoneOnProjects(project); + indexIssues(); + + ws.newRequest() + .setParam(IssuesWsParameters.PARAM_ORGANIZATION, organization.getKey()) .setParam(IssuesWsParameters.PARAM_COMPONENT_UUIDS, module.uuid()) .setParam(IssuesWsParameters.PARAM_MODULE_UUIDS, subModule1.uuid() + "," + subModule3.uuid()) .setParam(WebService.Param.FACETS, "moduleUuids") @@ -443,7 +512,26 @@ public class SearchActionComponentsTest { } @Test - public void display_directory_facet() throws Exception { + public void fail_to_display_module_facet_when_no_organization_or_project_is_set() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto module = db.components().insertComponent(newModuleDto(project)); + ComponentDto file = db.components().insertComponent(newFileDto(module, null)); + RuleDefinitionDto rule = db.rules().insert(); + db.issues().insert(rule, project, file); + allowAnyoneOnProjects(project); + indexIssues(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Facet(s) 'moduleUuids' require to also filter by project or organization"); + + ws.newRequest() + .setParam(IssuesWsParameters.PARAM_COMPONENT_UUIDS, module.uuid()) + .setParam(WebService.Param.FACETS, "moduleUuids") + .execute(); + } + + @Test + public void display_directory_facet_using_project() throws Exception { ComponentDto project = db.components().insertPublicProject(p -> p.setDbKey("PK1")); ComponentDto directory = db.components().insertComponent(newDirectory(project, "D1", "src/main/java/dir")); ComponentDto file = db.components().insertComponent(newFileDto(project, directory, "F1").setDbKey("FK1").setPath(directory.path() + "/MyComponent.java")); @@ -455,11 +543,50 @@ public class SearchActionComponentsTest { userSession.logIn("john"); ws.newRequest() .setParam("resolved", "false") + .setParam(IssuesWsParameters.PARAM_COMPONENT_KEYS, project.getKey()) .setParam(WebService.Param.FACETS, "directories") .execute() .assertJson(this.getClass(), "display_directory_facet.json"); } + @Test + public void display_directory_facet_using_organization() throws Exception { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPublicProject(organization, p -> p.setDbKey("PK1")); + ComponentDto directory = db.components().insertComponent(newDirectory(project, "D1", "src/main/java/dir")); + ComponentDto file = db.components().insertComponent(newFileDto(project, directory, "F1").setDbKey("FK1").setPath(directory.path() + "/MyComponent.java")); + RuleDefinitionDto rule = db.rules().insert(r -> r.setRuleKey(RuleKey.of("xoo", "x1"))); + IssueDto issue = db.issues().insert(rule, project, file, i -> i.setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2")); + allowAnyoneOnProjects(project); + indexIssues(); + + userSession.logIn("john"); + ws.newRequest() + .setParam("resolved", "false") + .setParam(IssuesWsParameters.PARAM_ORGANIZATION, organization.getKey()) + .setParam(WebService.Param.FACETS, "directories") + .execute() + .assertJson(this.getClass(), "display_directory_facet.json"); + } + + @Test + public void fail_to_display_directory_facet_when_no_organization_or_project_is_set() { + ComponentDto project = db.components().insertPublicProject(); + ComponentDto directory = db.components().insertComponent(newDirectory(project, "src")); + ComponentDto file = db.components().insertComponent(newFileDto(project, directory)); + RuleDefinitionDto rule = db.rules().insert(); + db.issues().insert(rule, project, file); + allowAnyoneOnProjects(project); + indexIssues(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Facet(s) 'directories' require to also filter by project or organization"); + + ws.newRequest() + .setParam(WebService.Param.FACETS, "directories") + .execute(); + } + @Test public void search_by_view_uuid() throws Exception { ComponentDto project = db.components().insertPublicProject(p -> p.setDbKey("PK1")); diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index 29bacc9551b..14bea4cc667 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -75,6 +75,7 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.DEPRECATED_FACET_ import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ADDITIONAL_FIELDS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENTS; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PAGE_INDEX; @@ -381,6 +382,7 @@ public class SearchActionTest { userSessionRule.logIn("john"); ws.newRequest() .setParam("resolved", "false") + .setParam(PARAM_COMPONENT_KEYS, project.getKey()) .setParam(WebService.Param.FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans,types") .execute() .assertJson(this.getClass(), "display_facets.json"); @@ -405,6 +407,7 @@ public class SearchActionTest { userSessionRule.logIn("john"); ws.newRequest() .setParam("resolved", "false") + .setParam(PARAM_COMPONENT_KEYS, project.getKey()) .setParam(WebService.Param.FACETS, "statuses,severities,resolutions,projectUuids,rules,fileUuids,assignees,languages,actionPlans") .setParam("facetMode", FACET_MODE_EFFORT) .execute() @@ -429,6 +432,7 @@ public class SearchActionTest { userSessionRule.logIn("john"); ws.newRequest() + .setParam(PARAM_COMPONENT_KEYS, project.getKey()) .setParam("resolved", "false") .setParam("severities", "MAJOR,MINOR") .setParam("languages", "xoo,polop,palap") -- 2.39.5