aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBelen Pruvost <belen.pruvost@sonarsource.com>2022-04-19 17:07:14 +0200
committersonartech <sonartech@sonarsource.com>2022-04-29 20:03:18 +0000
commit429d325a10ffb2e8ba55e397d07006c4c4062ea5 (patch)
tree9c90ae34dd600c42f77602ce263cd612f7856a1a
parent20537fc4569b2b47d04ae52ee12ed8e825285884 (diff)
downloadsonarqube-429d325a10ffb2e8ba55e397d07006c4c4062ea5.tar.gz
sonarqube-429d325a10ffb2e8ba55e397d07006c4c4062ea5.zip
SONAR-16261 - Endpoint to search for Projects a user can scan
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java120
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java74
-rw-r--r--server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json12
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java140
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java179
-rw-r--r--sonar-ws/src/main/protobuf/ws-projectanalyses.proto1
-rw-r--r--sonar-ws/src/main/protobuf/ws-projects.proto10
9 files changed, 539 insertions, 1 deletions
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java
new file mode 100644
index 00000000000..971aa7a4381
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java
@@ -0,0 +1,120 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.project.ws;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.permission.GlobalPermission;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.user.UserSession;
+
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.nullsFirst;
+import static java.util.Locale.ENGLISH;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+@ServerSide
+public class ProjectFinder {
+
+ private final DbClient dbClient;
+ private final UserSession userSession;
+
+ public ProjectFinder(DbClient dbClient, UserSession userSession) {
+ this.dbClient = dbClient;
+ this.userSession = userSession;
+ }
+
+ public SearchResult search(DbSession dbSession, @Nullable String searchQuery) {
+ List<ProjectDto> allProjects = dbClient.projectDao().selectProjects(dbSession);
+
+ Set<String> projectsUserHasAccessTo = userSession.keepAuthorizedProjects(UserRole.SCAN, allProjects)
+ .stream()
+ .map(ProjectDto::getKey)
+ .collect(toSet());
+
+ applyQueryAndPermissionFilter(searchQuery, allProjects, projectsUserHasAccessTo);
+
+ List<Project> resultProjects = allProjects.stream()
+ .sorted(comparing(ProjectDto::getName, nullsFirst(String.CASE_INSENSITIVE_ORDER)))
+ .map(p -> new Project(p.getKey(), p.getName()))
+ .collect(toList());
+ return new SearchResult(resultProjects);
+ }
+
+ private void applyQueryAndPermissionFilter(@Nullable String searchQuery, final List<ProjectDto> projects,
+ Set<String> projectsUserHasAccessTo) {
+ Iterator<ProjectDto> projectIterator = projects.iterator();
+ while (projectIterator.hasNext()) {
+ ProjectDto project = projectIterator.next();
+ if (isFilteredByQuery(searchQuery, project.getName()) || !hasPermission(projectsUserHasAccessTo, project.getKey())) {
+ projectIterator.remove();
+ }
+ }
+ }
+
+ private static boolean isFilteredByQuery(@Nullable String query, String projectName) {
+ return query != null && !projectName.toLowerCase(ENGLISH).contains(query.toLowerCase(ENGLISH));
+ }
+
+ private boolean hasPermission(Set<String> projectsUserHasAccessTo, String projectKey) {
+ return userSession.hasPermission(GlobalPermission.SCAN) || projectsUserHasAccessTo.contains(projectKey);
+ }
+
+ public static class SearchResult {
+ private final List<Project> projects;
+
+ public SearchResult(List<Project> projects) {
+ this.projects = projects;
+ }
+
+ public List<Project> getProjects() {
+ return projects;
+ }
+ }
+
+ public static class Project {
+ private final String key;
+ private final String name;
+
+ public Project(String key, @Nullable String name) {
+ this.key = requireNonNull(key, "Key cant be null");
+ this.name = name;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ @CheckForNull
+ public String getName() {
+ return name;
+ }
+
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
index f452466feae..aac5be28426 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
@@ -33,6 +33,7 @@ public class ProjectsWsModule extends Module {
protected void configureModule() {
add(
ProjectDefaultVisibility.class,
+ ProjectFinder.class,
ProjectLifeCycleListenersImpl.class,
ProjectsWs.class,
CreateAction.class,
@@ -41,6 +42,7 @@ public class ProjectsWsModule extends Module {
UpdateKeyAction.class,
SearchMyProjectsAction.class,
SearchAction.class,
+ SearchMyScannableProjectsAction.class,
UpdateVisibilityAction.class,
UpdateDefaultVisibilityAction.class);
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java
new file mode 100644
index 00000000000..917da7a58f7
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.project.ws;
+
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService.NewAction;
+import org.sonar.api.server.ws.WebService.NewController;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse.Project;
+
+import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
+import static org.sonar.server.project.ws.ProjectFinder.SearchResult;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse;
+
+public class SearchMyScannableProjectsAction implements ProjectsWsAction {
+
+ private final DbClient dbClient;
+ private final ProjectFinder projectFinder;
+
+ public SearchMyScannableProjectsAction(DbClient dbClient, ProjectFinder projectFinder) {
+ this.dbClient = dbClient;
+ this.projectFinder = projectFinder;
+ }
+
+ @Override
+ public void define(NewController controller) {
+ NewAction action = controller.createAction("search_my_scannable_projects")
+ .setDescription("List projects that a user can scan.")
+ .setSince("9.5")
+ .setInternal(true)
+ .setResponseExample(getClass().getResource("search-my-scannable-projects-example.json"))
+ .setHandler(this);
+
+ action.createSearchQuery("project", "project names");
+ }
+
+ @Override
+ public void handle(Request request, Response response) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ String query = request.param(TEXT_QUERY);
+
+ SearchResult searchResult = projectFinder.search(dbSession, query);
+
+ SearchMyScannableProjectsResponse.Builder searchProjects = SearchMyScannableProjectsResponse.newBuilder();
+ searchResult.getProjects().stream()
+ .map(p -> Project.newBuilder()
+ .setKey(p.getKey())
+ .setName(p.getName()))
+ .forEach(searchProjects::addProjects);
+ writeProtobuf(searchProjects.build(), request, response);
+ }
+ }
+
+}
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json
new file mode 100644
index 00000000000..4a2dbc338c0
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json
@@ -0,0 +1,12 @@
+{
+ "projects": [
+ {
+ "key": "project-key-1",
+ "name": "Project 1"
+ },
+ {
+ "key": "project-key-2",
+ "name": "Project 2"
+ }
+ ]
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java
new file mode 100644
index 00000000000..7fa18411245
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java
@@ -0,0 +1,140 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.project.ws;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbTester;
+import org.sonar.db.permission.GlobalPermission;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.project.ws.ProjectFinder.Project;
+import org.sonar.server.tester.UserSessionRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.web.UserRole.SCAN;
+import static org.sonar.server.project.ws.ProjectFinder.SearchResult;
+
+public class ProjectFinderTest {
+ @Rule
+ public DbTester db = DbTester.create();
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ private final ProjectFinder underTest = new ProjectFinder(db.getDbClient(), userSession);
+
+ @Test
+ public void selected_projects() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto();
+ ProjectDto project2 = db.components().insertPrivateProjectDto();
+ ProjectDto project3 = db.components().insertPrivateProjectDto();
+ userSession.addProjectPermission(SCAN, project1, project2);
+ List<Project> projects = underTest.search(db.getSession(), "").getProjects();
+
+ assertThat(projects)
+ .extracting(Project::getKey, Project::getName)
+ .containsExactlyInAnyOrder(
+ tuple(project1.getKey(), project1.getName()),
+ tuple(project2.getKey(), project2.getName()));
+ }
+
+ @Test
+ public void sort_project_by_name() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois"));
+ ProjectDto project4 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre"));
+ userSession.addProjectPermission(SCAN, project1, project2, project3, project4);
+
+ assertThat(underTest.search(db.getSession(), "projet")
+ .getProjects())
+ .extracting(Project::getName)
+ .containsExactly("Projet Deux", "Projet Quatre", "Projet Trois", "Projet Un");
+ }
+
+ @Test
+ public void projects_are_filtered_by_permissions() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois"));
+ ProjectDto project4 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:five").setName("Projet Cinq"));
+
+ userSession.addProjectPermission(SCAN, project1, project2, project3, project4);
+
+ SearchResult result = underTest.search(db.getSession(), null);
+
+ assertThat(result.getProjects())
+ .extracting(Project::getKey, Project::getName)
+ .containsExactlyInAnyOrder(
+ tuple("project:one", "Projet Un"),
+ tuple("project:two", "Projet Deux"),
+ tuple("project:three", "Projet Trois"),
+ tuple("project:four", "Projet Quatre"));
+ }
+
+ @Test
+ public void projects_are_not_filtered_due_to_global_scan_permission() {
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:five").setName("Projet Cinq"));
+
+ userSession.addPermission(GlobalPermission.SCAN);
+
+ SearchResult result = underTest.search(db.getSession(), null);
+
+ assertThat(result.getProjects())
+ .extracting(Project::getKey, Project::getName)
+ .containsExactlyInAnyOrder(
+ tuple("project:one", "Projet Un"),
+ tuple("project:two", "Projet Deux"),
+ tuple("project:three", "Projet Trois"),
+ tuple("project:four", "Projet Quatre"),
+ tuple("project:five", "Projet Cinq"));
+ }
+
+ @Test
+ public void search_by_query_on_name_case_insensitive() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois"));
+ ProjectDto project4 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre"));
+
+ userSession.addProjectPermission(SCAN, project1, project2, project3, project4);
+
+ assertThat(underTest.search(db.getSession(), "projet")
+ .getProjects())
+ .extracting(Project::getKey)
+ .containsExactlyInAnyOrder("project:one", "project:two", "project:three", "project:four");
+
+ assertThat(underTest.search(db.getSession(), "un")
+ .getProjects())
+ .extracting(Project::getKey)
+ .containsExactlyInAnyOrder("project:one");
+
+ assertThat(underTest.search(db.getSession(), "TROIS")
+ .getProjects())
+ .extracting(Project::getKey)
+ .containsExactlyInAnyOrder("project:three");
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
index ec6d1c4283b..169267e7aba 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java
@@ -29,6 +29,6 @@ public class ProjectsWsModuleTest {
public void verify_count_of_added_components_on_SonarQube() {
ListContainer container = new ListContainer();
new ProjectsWsModule().configure(container);
- assertThat(container.getAddedObjects()).hasSize(11);
+ assertThat(container.getAddedObjects()).hasSize(13);
}
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java
new file mode 100644
index 00000000000..1201b6197b0
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java
@@ -0,0 +1,179 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 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.project.ws;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.db.DbTester;
+import org.sonar.db.permission.GlobalPermission;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY;
+import static org.sonar.api.web.UserRole.SCAN;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse;
+import static org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse.Project;
+
+public class SearchMyScannableProjectsActionTest {
+
+ @Rule
+ public UserSessionRule userSession = UserSessionRule.standalone();
+
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private final WsActionTester ws = new WsActionTester(
+ new SearchMyScannableProjectsAction(db.getDbClient(), new ProjectFinder(db.getDbClient(), userSession)));
+
+ @Test
+ public void projects_filtered_by_query() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three"));
+ ProjectDto project4 = db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four"));
+ userSession.addProjectPermission(SCAN, project1, project2, project3, project4);
+
+ List<Project> result = ws.newRequest()
+ .setParam(TEXT_QUERY, "project")
+ .executeProtobuf(SearchMyScannableProjectsResponse.class)
+ .getProjectsList();
+
+ assertThat(result)
+ .extracting(Project::getKey, Project::getName)
+ .containsExactlyInAnyOrder(
+ tuple("project:three", "Project Three"),
+ tuple("project:four", "Project Four"));
+ }
+
+ @Test
+ public void projects_not_filtered_by_empty_query() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three"));
+ ProjectDto project4 = db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four"));
+ userSession.addProjectPermission(SCAN, project1, project2, project3, project4);
+
+ List<Project> result = ws.newRequest()
+ .setParam(TEXT_QUERY, "")
+ .executeProtobuf(SearchMyScannableProjectsResponse.class)
+ .getProjectsList();
+
+ assertThat(result)
+ .extracting(Project::getKey, Project::getName)
+ .containsExactlyInAnyOrder(
+ tuple("project:one", "Projet Un"),
+ tuple("project:two", "Projet Deux"),
+ tuple("project:three", "Project Three"),
+ tuple("project:four", "Project Four"));
+ }
+
+ @Test
+ public void projects_filtered_by_scan_permission() {
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three"));
+ db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four"));
+
+ List<Project> result = ws.newRequest()
+ .executeProtobuf(SearchMyScannableProjectsResponse.class)
+ .getProjectsList();
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void projects_filtered_for_anonymous_user() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three"));
+ ProjectDto project4 = db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four"));
+ userSession.addProjectPermission(SCAN, project1, project2, project3, project4);
+
+ WsActionTester ws = new WsActionTester(
+ new SearchMyScannableProjectsAction(db.getDbClient(), new ProjectFinder(db.getDbClient(), userSession.anonymous())));
+
+ List<Project> result = ws.newRequest()
+ .executeProtobuf(SearchMyScannableProjectsResponse.class)
+ .getProjectsList();
+
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void projects_not_filtered_due_to_global_scan_permission() {
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux"));
+ db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three"));
+ db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four"));
+ userSession.addPermission(GlobalPermission.SCAN);
+
+ List<Project> result = ws.newRequest()
+ .executeProtobuf(SearchMyScannableProjectsResponse.class)
+ .getProjectsList();
+
+ assertThat(result)
+ .extracting(Project::getKey, Project::getName)
+ .containsExactlyInAnyOrder(
+ tuple("project:one", "Projet Un"),
+ tuple("project:two", "Projet Deux"),
+ tuple("project:three", "Project Three"),
+ tuple("project:four", "Project Four"));
+ }
+
+ @Test
+ public void json_example() {
+ ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project-key-1").setName("Project 1"));
+ ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project-key-2").setName("Project 2"));
+ ProjectDto project3 = db.components().insertPublicProjectDto(p -> p.setDbKey("public-project-without-scan-permissions")
+ .setName("Public Project with Scan Permissions"));
+ userSession.addProjectPermission(SCAN, project1, project2);
+ userSession.registerProjects(project3);
+
+ String result = ws.newRequest()
+ .execute().getInput();
+
+ assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
+ assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result);
+ }
+
+ @Test
+ public void definition() {
+ WebService.Action definition = ws.getDef();
+
+ assertThat(definition.key()).isEqualTo("search_my_scannable_projects");
+ assertThat(definition.since()).isEqualTo("9.5");
+ assertThat(definition.isInternal()).isTrue();
+ assertThat(definition.isPost()).isFalse();
+ assertThat(definition.responseExampleAsString()).isNotEmpty();
+
+ assertThat(definition.params())
+ .extracting(Param::key, Param::isRequired)
+ .containsExactlyInAnyOrder(
+ tuple("q", false));
+ }
+
+}
diff --git a/sonar-ws/src/main/protobuf/ws-projectanalyses.proto b/sonar-ws/src/main/protobuf/ws-projectanalyses.proto
index 7a8ab2e5dd3..c435380d59a 100644
--- a/sonar-ws/src/main/protobuf/ws-projectanalyses.proto
+++ b/sonar-ws/src/main/protobuf/ws-projectanalyses.proto
@@ -87,3 +87,4 @@ message Project {
optional string oldBranch = 5;
optional string newBranch = 6;
}
+
diff --git a/sonar-ws/src/main/protobuf/ws-projects.proto b/sonar-ws/src/main/protobuf/ws-projects.proto
index d20ba59fb2d..db29fdb2a85 100644
--- a/sonar-ws/src/main/protobuf/ws-projects.proto
+++ b/sonar-ws/src/main/protobuf/ws-projects.proto
@@ -73,3 +73,13 @@ message SearchWsResponse {
optional string revision = 8;
}
}
+
+// WS api/projects/search_my_scannable_projects
+message SearchMyScannableProjectsResponse {
+ message Project {
+ optional string key = 1;
+ optional string name = 2;
+ }
+
+ repeated Project projects = 1;
+}