From 556ba8154b2c6406b15cd4357cc74a06f3094e3a Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Wed, 11 Feb 2015 15:03:51 +0100 Subject: [PATCH] SONAR-6156 The search of projects on contextualized Issues page should support views --- .../server/component/db/ComponentDao.java | 10 ++ .../component/db/ComponentIndexDao.java | 36 +++++ .../server/component/ws/ComponentsWs.java | 5 +- .../server/component/ws/SearchAction.java | 135 ++++++++++++++++++ .../java/org/sonar/server/db/DbClient.java | 7 + .../server/platform/ServerComponents.java | 6 +- .../org/sonar/server/user/UserSession.java | 13 +- .../sonar/server/batch/IssuesActionTest.java | 3 + .../server/component/db/ComponentDaoTest.java | 21 +++ .../component/db/ComponentIndexDaoTest.java | 65 +++++++++ .../component/ws/ComponentAppActionTest.java | 2 +- .../server/component/ws/ComponentsWsTest.java | 14 +- .../component/ws/SearchActionMediumTest.java | 113 +++++++++++++++ .../sonar/server/user/UserSessionTest.java | 42 +++++- ...s_from_query_and_view_or_sub_view_uuid.xml | 32 +++++ ...rn_only_authorized_projects_from_view.json | 11 ++ ...urn_only_authorized_projects_from_view.xml | 30 ++++ .../return_only_first_page.json | 5 + .../return_paged_result.json | 5 + .../return_projects_from_subview.json | 11 ++ .../return_projects_from_view.json | 15 ++ .../ws/SearchActionMediumTest/shared.xml | 35 +++++ .../sonar/core/component/ComponentDto.java | 2 +- .../component/db/ComponentIndexMapper.java | 30 ++++ .../core/component/db/ComponentMapper.java | 11 +- .../org/sonar/core/persistence/MyBatis.java | 79 ++-------- .../org/sonar/core/user/AuthorizationDao.java | 61 ++++---- .../sonar/core/user/AuthorizationMapper.java | 38 +++++ .../component/db/ComponentIndexMapper.xml | 16 +++ .../core/component/db/ComponentMapper.xml | 13 ++ .../sonar/core/user/AuthorizationMapper.xml | 27 ++++ .../sonar/core/user/AuthorizationDaoTest.java | 60 +++++--- .../user_should_be_authorized.xml | 3 - 33 files changed, 817 insertions(+), 139 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentIndexDao.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentIndexDaoTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchActionMediumTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentIndexDaoTest/select_project_ids_from_query_and_view_or_sub_view_uuid.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.xml create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_first_page.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_paged_result.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_subview.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_view.json create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/shared.xml create mode 100644 sonar-core/src/main/java/org/sonar/core/component/db/ComponentIndexMapper.java create mode 100644 sonar-core/src/main/java/org/sonar/core/user/AuthorizationMapper.java create mode 100644 sonar-core/src/main/resources/org/sonar/core/component/db/ComponentIndexMapper.xml diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java index 85361463f9d..27cd270de81 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentDao.java @@ -103,6 +103,15 @@ public class ComponentDao extends BaseDao return mapper(session).selectModuleFilesTree(rootComponentUuid, Scopes.FILE); } + public List getByIds(final DbSession session, Collection ids) { + return DaoUtils.executeLargeInputs(ids, new Function, List>() { + @Override + public List apply(List partition) { + return mapper(session).findByIds(partition); + } + }); + } + public List getByUuids(final DbSession session, Collection uuids) { return DaoUtils.executeLargeInputs(uuids, new Function, List>() { @Override @@ -154,4 +163,5 @@ public class ComponentDao extends BaseDao public List selectProjectsFromView(DbSession session, String viewUuid, String projectViewUuid) { return mapper(session).selectProjectsFromView("%." + viewUuid + ".%", projectViewUuid); } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentIndexDao.java b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentIndexDao.java new file mode 100644 index 00000000000..7243b6125e2 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/db/ComponentIndexDao.java @@ -0,0 +1,36 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.component.db; + +import org.sonar.api.ServerComponent; +import org.sonar.core.component.db.ComponentIndexMapper; +import org.sonar.core.persistence.DaoComponent; +import org.sonar.core.persistence.DbSession; + +import java.util.List; + +public class ComponentIndexDao implements ServerComponent, DaoComponent { + + public List selectProjectIdsFromQueryAndViewOrSubViewUuid(DbSession session, String query, String viewOrSubViewUuid) { + return session.getMapper(ComponentIndexMapper.class).selectProjectIdsFromQueryAndViewOrSubViewUuid(query + "%", "%." + viewOrSubViewUuid + ".%"); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java index edf407a500d..94cadaa25f7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ComponentsWs.java @@ -27,9 +27,11 @@ import org.sonar.api.server.ws.WebService; public class ComponentsWs implements WebService { private final ComponentAppAction appAction; + private final SearchAction searchAction; - public ComponentsWs(ComponentAppAction appAction) { + public ComponentsWs(ComponentAppAction appAction, SearchAction searchAction) { this.appAction = appAction; + this.searchAction = searchAction; } @Override @@ -39,6 +41,7 @@ public class ComponentsWs implements WebService { .setDescription("Components management"); appAction.define(controller); + searchAction.define(controller); defineSuggestionsAction(controller); controller.done(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java new file mode 100644 index 00000000000..50599c6d058 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/SearchAction.java @@ -0,0 +1,135 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.component.ws; + +import com.google.common.collect.Sets; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.RequestHandler; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.text.JsonWriter; +import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.MyBatis; +import org.sonar.server.db.DbClient; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.user.UserSession; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Sets.newLinkedHashSet; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; + +public class SearchAction implements RequestHandler { + + private static final short MINIMUM_SEARCH_CHARACTERS = 2; + + private static final String PARAM_COMPONENT_UUID = "componentUuid"; + private static final String PARAM_QUERY = "q"; + + private final DbClient dbClient; + + public SearchAction(DbClient dbClient) { + this.dbClient = dbClient; + } + + void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("search") + .setDescription("Search for components. Currently limited to projects in a view or a sub-view") + .setSince("5.1") + .setInternal(true) + .setHandler(this); + + action + .createParam(PARAM_COMPONENT_UUID) + .setRequired(true) + .setDescription("View or sub view UUID") + .setExampleValue("d6d9e1e5-5e13-44fa-ab82-3ec29efa8935"); + + action + .createParam(PARAM_QUERY) + .setRequired(true) + .setDescription("UTF-8 search query") + .setExampleValue("sonar"); + + action.addPagingParams(10); + } + + @Override + public void handle(Request request, Response response) { + String query = request.mandatoryParam(PARAM_QUERY); + if (query.length() < MINIMUM_SEARCH_CHARACTERS) { + throw new IllegalArgumentException(String.format("Minimum search is %s characters", MINIMUM_SEARCH_CHARACTERS)); + } + String viewOrSubUuid = request.mandatoryParam(PARAM_COMPONENT_UUID); + + JsonWriter json = response.newJsonWriter(); + json.beginObject(); + + DbSession session = dbClient.openSession(false); + try { + ComponentDto componentDto = dbClient.componentDao().getByUuid(session, viewOrSubUuid); + UserSession.get().checkProjectUuidPermission(UserRole.USER, componentDto.projectUuid()); + + Set projectIds = newLinkedHashSet(dbClient.componentIndexDao().selectProjectIdsFromQueryAndViewOrSubViewUuid(session, query, componentDto.uuid())); + Collection authorizedProjectIds = dbClient.authorizationDao().keepAuthorizedProjectIds(session, projectIds, UserSession.get().userId(), UserRole.USER); + + SearchOptions options = new SearchOptions(); + options.setPage(request.mandatoryParamAsInt(PAGE), request.mandatoryParamAsInt(PAGE_SIZE)); + Set pagedProjectIds = pagedProjectIds(authorizedProjectIds, options); + + List projects = dbClient.componentDao().getByIds(session, pagedProjectIds); + + options.writeJson(json, authorizedProjectIds.size()); + json.name("components").beginArray(); + for (ComponentDto project : projects) { + json.beginObject(); + json.prop("uuid", project.uuid()); + json.prop("name", project.name()); + json.endObject(); + } + json.endArray(); + } finally { + MyBatis.closeQuietly(session); + } + + json.endObject(); + json.close(); + } + + private Set pagedProjectIds(Collection projectIds, SearchOptions options) { + Set results = Sets.newLinkedHashSet(); + int index = 0; + for (Long projectId : projectIds) { + if (index >= options.getOffset() && results.size() < options.getLimit()) { + results.add(projectId); + } else if (results.size() >= options.getLimit()) { + break; + } + index++; + } + return results; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java index ea6ac8f2980..3c34603fe53 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java @@ -36,6 +36,7 @@ import org.sonar.core.user.AuthorDao; import org.sonar.core.user.AuthorizationDao; import org.sonar.server.activity.db.ActivityDao; import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.component.db.ComponentIndexDao; import org.sonar.server.component.db.SnapshotDao; import org.sonar.server.computation.db.AnalysisReportDao; import org.sonar.server.dashboard.db.DashboardDao; @@ -87,6 +88,7 @@ public class DbClient implements ServerComponent { private final WidgetPropertyDao widgetPropertyDao; private final FileSourceDao fileSourceDao; private final AuthorDao authorDao; + private final ComponentIndexDao componentIndexDao; public DbClient(Database db, MyBatis myBatis, DaoComponent... daoComponents) { this.db = db; @@ -120,6 +122,7 @@ public class DbClient implements ServerComponent { widgetPropertyDao = getDao(map, WidgetPropertyDao.class); fileSourceDao = getDao(map, FileSourceDao.class); authorDao = getDao(map, AuthorDao.class); + componentIndexDao = getDao(map, ComponentIndexDao.class); } public Database database() { @@ -226,6 +229,10 @@ public class DbClient implements ServerComponent { return authorDao; } + public ComponentIndexDao componentIndexDao() { + return componentIndexDao; + } + private K getDao(Map map, Class clazz) { return (K) map.get(clazz); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 9de6fee0478..f6f1357628b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -85,6 +85,7 @@ import org.sonar.server.component.ComponentService; import org.sonar.server.component.DefaultComponentFinder; import org.sonar.server.component.DefaultRubyComponentService; import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.component.db.ComponentIndexDao; import org.sonar.server.component.db.SnapshotDao; import org.sonar.server.component.ws.*; import org.sonar.server.computation.AnalysisReportQueue; @@ -243,9 +244,10 @@ class ServerComponents { MetricDao.class, ComponentDao.class, SnapshotDao.class, - DbClient.class, MeasureFilterDao.class, AnalysisReportDao.class, + ComponentIndexDao.class, + DbClient.class, // Elasticsearch SearchClient.class, @@ -502,11 +504,11 @@ class ServerComponents { pico.addSingleton(DefaultComponentFinder.class); pico.addSingleton(DefaultRubyComponentService.class); pico.addSingleton(ComponentService.class); - pico.addSingleton(ComponentDao.class); pico.addSingleton(ResourcesWs.class); pico.addSingleton(ComponentsWs.class); pico.addSingleton(ProjectsWs.class); pico.addSingleton(ComponentAppAction.class); + pico.addSingleton(org.sonar.server.component.ws.SearchAction.class); pico.addSingleton(EventsWs.class); pico.addSingleton(ComponentCleanerService.class); diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java index ba6c33c9dc3..06d92231bd4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserSession.java @@ -179,6 +179,16 @@ public class UserSession { return this; } + /** + * Ensures that user implies the specified project permission. If not a {@link org.sonar.server.exceptions.ForbiddenException} is thrown. + */ + public UserSession checkProjectUuidPermission(String projectPermission, String projectUuid) { + if (!hasProjectPermissionByUuid(projectPermission, projectUuid)) { + throw new ForbiddenException(INSUFFICIENT_PRIVILEGES_MESSAGE); + } + return this; + } + /** * Does the user have the given project permission ? */ @@ -222,12 +232,11 @@ public class UserSession { } /** - * Does the user have the given project permission for a component ? + * Does the user have the given project permission for a component key ? */ public boolean hasComponentPermission(String permission, String componentKey) { String projectKey = projectKeyByComponentKey.get(componentKey); if (projectKey == null) { - // TODO use method using UUID ResourceDto project = resourceDao().getRootProjectByComponentKey(componentKey); if (project == null) { return false; diff --git a/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java index f7db3d0126b..e5dcb669c8a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/batch/IssuesActionTest.java @@ -24,6 +24,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.sonar.api.platform.Server; import org.sonar.api.web.UserRole; import org.sonar.core.permission.GlobalPermissions; @@ -36,9 +37,11 @@ import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.issue.db.IssueDao; import org.sonar.server.user.MockUserSession; import org.sonar.server.ws.WsTester; +import org.sonar.test.DbTests; import static org.mockito.Mockito.mock; +@Category(DbTests.class) public class IssuesActionTest { private final static String PROJECT_KEY = "struts"; diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java index 53b7462d1df..fde5e2162b9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentDaoTest.java @@ -163,6 +163,27 @@ public class ComponentDaoTest extends AbstractDaoTestCase { assertThat(dao.getByKeys(session, "unknown")).isEmpty(); } + @Test + public void get_by_ids() { + setupData("shared"); + + List results = dao.getByIds(session, newArrayList(4L)); + assertThat(results).hasSize(1); + + ComponentDto result = results.get(0); + assertThat(result).isNotNull(); + assertThat(result.key()).isEqualTo("org.struts:struts-core:src/org/struts/RequestContext.java"); + assertThat(result.path()).isEqualTo("src/org/struts/RequestContext.java"); + assertThat(result.name()).isEqualTo("RequestContext.java"); + assertThat(result.longName()).isEqualTo("org.struts.RequestContext"); + assertThat(result.qualifier()).isEqualTo("FIL"); + assertThat(result.scope()).isEqualTo("FIL"); + assertThat(result.language()).isEqualTo("java"); + assertThat(result.parentProjectId()).isEqualTo(2); + + assertThat(dao.getByIds(session, newArrayList(555L))).isEmpty(); + } + @Test public void get_by_uuids() { setupData("shared"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentIndexDaoTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentIndexDaoTest.java new file mode 100644 index 00000000000..76d9248a85a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/db/ComponentIndexDaoTest.java @@ -0,0 +1,65 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.component.db; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.test.DbTests; + +import static org.assertj.core.api.Assertions.assertThat; + +@Category(DbTests.class) +public class ComponentIndexDaoTest { + + @Rule + public DbTester dbTester = new DbTester(); + + DbSession session; + + ComponentIndexDao dao; + + @Before + public void createDao() throws Exception { + session = dbTester.myBatis().openSession(false); + dao = new ComponentIndexDao(); + } + + @After + public void tearDown() throws Exception { + session.close(); + } + + @Test + public void select_project_ids_from_query_and_view_or_sub_view_uuid() throws Exception { + dbTester.prepareDbUnit(getClass(), "select_project_ids_from_query_and_view_or_sub_view_uuid.xml"); + String viewUuid = "EFGH"; + + assertThat(dao.selectProjectIdsFromQueryAndViewOrSubViewUuid(session, "project", viewUuid)).containsOnly(1L, 2L); + assertThat(dao.selectProjectIdsFromQueryAndViewOrSubViewUuid(session, "one", viewUuid)).containsOnly(1L); + assertThat(dao.selectProjectIdsFromQueryAndViewOrSubViewUuid(session, "two", viewUuid)).containsOnly(2L); + assertThat(dao.selectProjectIdsFromQueryAndViewOrSubViewUuid(session, "unknown", viewUuid)).isEmpty(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentAppActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentAppActionTest.java index bf54f209aaa..19496fb97ad 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentAppActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentAppActionTest.java @@ -101,7 +101,7 @@ public class ComponentAppActionTest { when(measureDao.findByComponentKeyAndMetricKeys(anyString(), anyListOf(String.class), eq(session))).thenReturn(measures); - tester = new WsTester(new ComponentsWs(new ComponentAppAction(dbClient, durations, i18n))); + tester = new WsTester(new ComponentsWs(new ComponentAppAction(dbClient, durations, i18n), mock(SearchAction.class))); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java index 0e88b962bec..6d216685dc7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ComponentsWsTest.java @@ -38,7 +38,7 @@ public class ComponentsWsTest { @Before public void setUp() throws Exception { - WsTester tester = new WsTester(new ComponentsWs(new ComponentAppAction(mock(DbClient.class), mock(Durations.class), mock(I18n.class)))); + WsTester tester = new WsTester(new ComponentsWs(new ComponentAppAction(mock(DbClient.class), mock(Durations.class), mock(I18n.class)), new SearchAction(mock(DbClient.class)))); controller = tester.controller("api/components"); } @@ -47,7 +47,7 @@ public class ComponentsWsTest { assertThat(controller).isNotNull(); assertThat(controller.description()).isNotEmpty(); assertThat(controller.since()).isEqualTo("4.2"); - assertThat(controller.actions()).hasSize(2); + assertThat(controller.actions()).hasSize(3); } @Test @@ -71,4 +71,14 @@ public class ComponentsWsTest { assertThat(action.params()).hasSize(2); } + @Test + public void define_search_action() throws Exception { + WebService.Action action = controller.action("search"); + assertThat(action).isNotNull(); + assertThat(action.isInternal()).isTrue(); + assertThat(action.isPost()).isFalse(); + assertThat(action.handler()).isNotNull(); + assertThat(action.params()).hasSize(4); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchActionMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchActionMediumTest.java new file mode 100644 index 00000000000..ab80ff1e2be --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/SearchActionMediumTest.java @@ -0,0 +1,113 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.component.ws; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.web.UserRole; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.user.AuthorizationDao; +import org.sonar.server.component.db.ComponentDao; +import org.sonar.server.component.db.ComponentIndexDao; +import org.sonar.server.db.DbClient; +import org.sonar.server.user.MockUserSession; +import org.sonar.server.ws.WsTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Fail.failBecauseExceptionWasNotThrown; +import static org.mockito.Mockito.mock; + +public class SearchActionMediumTest { + + @Rule + public DbTester dbTester = new DbTester(); + + WsTester tester; + + @Before + public void setUp() throws Exception { + DbClient dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), + new ComponentDao(), new AuthorizationDao(dbTester.myBatis()), new ComponentIndexDao() + ); + tester = new WsTester(new ComponentsWs(mock(ComponentAppAction.class), new SearchAction(dbClient))); + } + + @Test + public void return_projects_from_view() throws Exception { + dbTester.prepareDbUnit(getClass(), "shared.xml"); + MockUserSession.set().setLogin("john").addProjectUuidPermissions(UserRole.USER, "EFGH"); + + WsTester.TestRequest request = tester.newGetRequest("api/components", "search").setParam("componentUuid", "EFGH").setParam("q", "st"); + request.execute().assertJson(getClass(), "return_projects_from_view.json"); + } + + @Test + public void return_projects_from_subview() throws Exception { + dbTester.prepareDbUnit(getClass(), "shared.xml"); + MockUserSession.set().setLogin("john").addComponentUuidPermission(UserRole.USER, "EFGH", "FGHI"); + + WsTester.TestRequest request = tester.newGetRequest("api/components", "search").setParam("componentUuid", "FGHI").setParam("q", "st"); + request.execute().assertJson(getClass(), "return_projects_from_subview.json"); + } + + @Test + public void return_only_authorized_projects_from_view() throws Exception { + dbTester.prepareDbUnit(getClass(), "return_only_authorized_projects_from_view.xml"); + MockUserSession.set().setLogin("john").addProjectUuidPermissions(UserRole.USER, "EFGH"); + + WsTester.TestRequest request = tester.newGetRequest("api/components", "search").setParam("componentUuid", "EFGH").setParam("q", "st"); + request.execute().assertJson(getClass(), "return_only_authorized_projects_from_view.json"); + } + + @Test + public void return_paged_result() throws Exception { + dbTester.prepareDbUnit(getClass(), "shared.xml"); + MockUserSession.set().setLogin("john").addProjectUuidPermissions(UserRole.USER, "EFGH"); + + WsTester.TestRequest request = tester.newGetRequest("api/components", "search").setParam("componentUuid", "EFGH").setParam("q", "st").setParam("p", "2").setParam("ps", "1"); + request.execute().assertJson(getClass(), "return_paged_result.json", false); + } + + @Test + public void return_only_first_page() throws Exception { + dbTester.prepareDbUnit(getClass(), "shared.xml"); + MockUserSession.set().setLogin("john").addProjectUuidPermissions(UserRole.USER, "EFGH"); + + WsTester.TestRequest request = tester.newGetRequest("api/components", "search").setParam("componentUuid", "EFGH").setParam("q", "st").setParam("p", "1").setParam("ps", "1"); + request.execute().assertJson(getClass(), "return_only_first_page.json", false); + } + + @Test + public void fail_when_search_param_is_too_short() throws Exception { + dbTester.prepareDbUnit(getClass(), "shared.xml"); + MockUserSession.set().setLogin("john").addProjectUuidPermissions(UserRole.USER, "EFGH"); + + WsTester.TestRequest request = tester.newGetRequest("api/components", "search").setParam("componentUuid", "EFGH").setParam("q", "s"); + + try { + request.execute(); + failBecauseExceptionWasNotThrown(IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Minimum search is 2 characters"); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionTest.java index e85708125fa..299b2631a71 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserSessionTest.java @@ -22,10 +22,12 @@ package org.sonar.server.user; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.web.UserRole; +import org.sonar.core.component.ComponentDto; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.resource.ResourceDao; import org.sonar.core.resource.ResourceDto; import org.sonar.core.user.AuthorizationDao; +import org.sonar.server.component.ComponentTesting; import org.sonar.server.exceptions.ForbiddenException; import javax.annotation.Nullable; @@ -148,6 +150,28 @@ public class UserSessionTest { session.checkProjectPermission(UserRole.USER, "com.foo:Bar"); } + @Test + public void check_project_uuid_permission_ok() throws Exception { + AuthorizationDao authorizationDao = mock(AuthorizationDao.class); + UserSession session = new SpyUserSession("marius", authorizationDao).setUserId(1); + + ComponentDto project = ComponentTesting.newProjectDto(); + when(authorizationDao.selectAuthorizedRootProjectsUuids(1, UserRole.USER)).thenReturn(newArrayList(project.uuid())); + + session.checkProjectUuidPermission(UserRole.USER, project.uuid()); + } + + @Test(expected = ForbiddenException.class) + public void check_project_uuid_permission_ko() throws Exception { + AuthorizationDao authorizationDao = mock(AuthorizationDao.class); + UserSession session = new SpyUserSession("marius", authorizationDao).setUserId(1); + + ComponentDto project = ComponentTesting.newProjectDto(); + when(authorizationDao.selectAuthorizedRootProjectsUuids(1, UserRole.USER)).thenReturn(newArrayList(project.uuid())); + + session.checkProjectUuidPermission(UserRole.USER, "another project"); + } + @Test public void has_component_permission() throws Exception { AuthorizationDao authorizationDao = mock(AuthorizationDao.class); @@ -164,7 +188,7 @@ public class UserSessionTest { } @Test - public void check_component_permission_ok() throws Exception { + public void check_component_key_permission_ok() throws Exception { AuthorizationDao authorizationDao = mock(AuthorizationDao.class); ResourceDao resourceDao = mock(ResourceDao.class); UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1); @@ -176,7 +200,7 @@ public class UserSessionTest { } @Test(expected = ForbiddenException.class) - public void check_component_permission_ko() throws Exception { + public void check_component_key_permission_ko() throws Exception { AuthorizationDao authorizationDao = mock(AuthorizationDao.class); ResourceDao resourceDao = mock(ResourceDao.class); UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1); @@ -188,7 +212,7 @@ public class UserSessionTest { } @Test(expected = ForbiddenException.class) - public void check_component_permission_when_project_not_found() throws Exception { + public void check_component_key_permission_when_project_not_found() throws Exception { AuthorizationDao authorizationDao = mock(AuthorizationDao.class); ResourceDao resourceDao = mock(ResourceDao.class); UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1); @@ -198,6 +222,18 @@ public class UserSessionTest { session.checkComponentPermission(UserRole.USER, "com.foo:Bar:BarFile.xoo"); } + @Test(expected = ForbiddenException.class) + public void check_component_dto_permission_ko() throws Exception { + AuthorizationDao authorizationDao = mock(AuthorizationDao.class); + ResourceDao resourceDao = mock(ResourceDao.class); + UserSession session = new SpyUserSession("marius", authorizationDao, resourceDao).setUserId(1); + + ComponentDto project = ComponentTesting.newProjectDto(); + when(authorizationDao.selectAuthorizedRootProjectsKeys(1, UserRole.USER)).thenReturn(newArrayList(project.uuid())); + + session.checkComponentPermission(UserRole.USER, "another"); + } + static class SpyUserSession extends UserSession { private AuthorizationDao authorizationDao; private ResourceDao resourceDao; diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentIndexDaoTest/select_project_ids_from_query_and_view_or_sub_view_uuid.xml b/server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentIndexDaoTest/select_project_ids_from_query_and_view_or_sub_view_uuid.xml new file mode 100644 index 00000000000..aff69473ce1 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/db/ComponentIndexDaoTest/select_project_ids_from_query_and_view_or_sub_view_uuid.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.json new file mode 100644 index 00000000000..9676629feeb --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.json @@ -0,0 +1,11 @@ +{ + "total": 1, + "p": 1, + "ps": 10, + "components": [ + { + "uuid": "JKLM", + "name": "Struts" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.xml b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.xml new file mode 100644 index 00000000000..0b5c0343840 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_authorized_projects_from_view.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_first_page.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_first_page.json new file mode 100644 index 00000000000..e913f5e4c67 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_only_first_page.json @@ -0,0 +1,5 @@ +{ + "total": 2, + "p": 1, + "ps": 1 +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_paged_result.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_paged_result.json new file mode 100644 index 00000000000..d871108edce --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_paged_result.json @@ -0,0 +1,5 @@ +{ + "total": 2, + "p": 2, + "ps": 1 +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_subview.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_subview.json new file mode 100644 index 00000000000..9676629feeb --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_subview.json @@ -0,0 +1,11 @@ +{ + "total": 1, + "p": 1, + "ps": 10, + "components": [ + { + "uuid": "JKLM", + "name": "Struts" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_view.json b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_view.json new file mode 100644 index 00000000000..f7f99cc9d73 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/return_projects_from_view.json @@ -0,0 +1,15 @@ +{ + "total": 2, + "p": 1, + "ps": 10, + "components": [ + { + "uuid": "JKLM", + "name": "Struts" + }, + { + "uuid": "KLMN", + "name": "Elasticsearch" + } + ] +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/shared.xml b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/shared.xml new file mode 100644 index 00000000000..687ef3d7b14 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/component/ws/SearchActionMediumTest/shared.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java b/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java index ca3a0b67a30..ecad07f8bbd 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java +++ b/sonar-core/src/main/java/org/sonar/core/component/ComponentDto.java @@ -105,7 +105,7 @@ public class ComponentDto extends Dto implements Component { } /** - * Return the root project id. On a root project, return itself + * Return the root project uuid. On a root project, return itself */ public String projectUuid() { return projectUuid; diff --git a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentIndexMapper.java b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentIndexMapper.java new file mode 100644 index 00000000000..114b8da6ba0 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentIndexMapper.java @@ -0,0 +1,30 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.component.db; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ComponentIndexMapper { + + List selectProjectIdsFromQueryAndViewOrSubViewUuid(@Param("query") String query, @Param("viewOrSubViewUuid") String viewOrSubViewUuid); +} diff --git a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java index 269ede5a461..1f057c62dc8 100644 --- a/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/component/db/ComponentMapper.java @@ -35,21 +35,12 @@ import java.util.List; */ public interface ComponentMapper { - /** - * Warning, projectId is always null - */ @CheckForNull ComponentDto selectByKey(String key); - /** - * Warning, projectId is always null - */ @CheckForNull ComponentDto selectById(long id); - /** - * Warning, projectId is always null - */ @CheckForNull ComponentDto selectByUuid(String uuid); @@ -65,6 +56,8 @@ public interface ComponentMapper { List findByKeys(@Param("keys") Collection keys); + List findByIds(@Param("ids") Collection ids); + List findByUuids(@Param("uuids") Collection uuids); List selectExistingUuids(@Param("uuids") Collection uuids); diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java index 0390d6fabdf..2b5fa87277f 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/MyBatis.java @@ -25,11 +25,7 @@ import com.google.common.io.Closeables; import org.apache.ibatis.builder.xml.XMLMapperBuilder; import org.apache.ibatis.logging.LogFactory; import org.apache.ibatis.mapping.Environment; -import org.apache.ibatis.session.Configuration; -import org.apache.ibatis.session.ExecutorType; -import org.apache.ibatis.session.SqlSession; -import org.apache.ibatis.session.SqlSessionFactory; -import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.apache.ibatis.session.*; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.apache.ibatis.type.JdbcType; import org.slf4j.LoggerFactory; @@ -43,19 +39,13 @@ import org.sonar.core.component.ComponentDto; import org.sonar.core.component.FilePathWithHashDto; import org.sonar.core.component.SnapshotDto; import org.sonar.core.component.UuidWithProjectUuidDto; +import org.sonar.core.component.db.ComponentIndexMapper; import org.sonar.core.component.db.ComponentMapper; import org.sonar.core.component.db.SnapshotMapper; import org.sonar.core.computation.db.AnalysisReportDto; import org.sonar.core.computation.db.AnalysisReportMapper; import org.sonar.core.config.Logback; -import org.sonar.core.dashboard.ActiveDashboardDto; -import org.sonar.core.dashboard.ActiveDashboardMapper; -import org.sonar.core.dashboard.DashboardDto; -import org.sonar.core.dashboard.DashboardMapper; -import org.sonar.core.dashboard.WidgetDto; -import org.sonar.core.dashboard.WidgetMapper; -import org.sonar.core.dashboard.WidgetPropertyDto; -import org.sonar.core.dashboard.WidgetPropertyMapper; +import org.sonar.core.dashboard.*; import org.sonar.core.dependency.DependencyDto; import org.sonar.core.dependency.DependencyMapper; import org.sonar.core.dependency.ResourceSnapshotDto; @@ -64,33 +54,11 @@ import org.sonar.core.duplication.DuplicationMapper; import org.sonar.core.duplication.DuplicationUnitDto; import org.sonar.core.graph.jdbc.GraphDto; import org.sonar.core.graph.jdbc.GraphDtoMapper; -import org.sonar.core.issue.db.ActionPlanDto; -import org.sonar.core.issue.db.ActionPlanMapper; -import org.sonar.core.issue.db.ActionPlanStatsDto; -import org.sonar.core.issue.db.ActionPlanStatsMapper; -import org.sonar.core.issue.db.BatchIssueDto; -import org.sonar.core.issue.db.IssueChangeDto; -import org.sonar.core.issue.db.IssueChangeMapper; -import org.sonar.core.issue.db.IssueDto; -import org.sonar.core.issue.db.IssueFilterDto; -import org.sonar.core.issue.db.IssueFilterFavouriteDto; -import org.sonar.core.issue.db.IssueFilterFavouriteMapper; -import org.sonar.core.issue.db.IssueFilterMapper; -import org.sonar.core.issue.db.IssueMapper; -import org.sonar.core.measure.db.MeasureDto; -import org.sonar.core.measure.db.MeasureFilterDto; -import org.sonar.core.measure.db.MeasureFilterMapper; -import org.sonar.core.measure.db.MeasureMapper; -import org.sonar.core.measure.db.MetricDto; -import org.sonar.core.measure.db.MetricMapper; +import org.sonar.core.issue.db.*; +import org.sonar.core.measure.db.*; import org.sonar.core.notification.db.NotificationQueueDto; import org.sonar.core.notification.db.NotificationQueueMapper; -import org.sonar.core.permission.GroupWithPermissionDto; -import org.sonar.core.permission.PermissionTemplateDto; -import org.sonar.core.permission.PermissionTemplateGroupDto; -import org.sonar.core.permission.PermissionTemplateMapper; -import org.sonar.core.permission.PermissionTemplateUserDto; -import org.sonar.core.permission.UserWithPermissionDto; +import org.sonar.core.permission.*; import org.sonar.core.persistence.dialect.Dialect; import org.sonar.core.persistence.migration.v44.Migration44Mapper; import org.sonar.core.persistence.migration.v45.Migration45Mapper; @@ -100,22 +68,9 @@ import org.sonar.core.properties.PropertyDto; import org.sonar.core.purge.IdUuidPair; import org.sonar.core.purge.PurgeMapper; import org.sonar.core.purge.PurgeableSnapshotDto; -import org.sonar.core.qualitygate.db.ProjectQgateAssociationDto; -import org.sonar.core.qualitygate.db.ProjectQgateAssociationMapper; -import org.sonar.core.qualitygate.db.QualityGateConditionDto; -import org.sonar.core.qualitygate.db.QualityGateConditionMapper; -import org.sonar.core.qualitygate.db.QualityGateDto; -import org.sonar.core.qualitygate.db.QualityGateMapper; -import org.sonar.core.qualityprofile.db.ActiveRuleDto; -import org.sonar.core.qualityprofile.db.ActiveRuleMapper; -import org.sonar.core.qualityprofile.db.ActiveRuleParamDto; -import org.sonar.core.qualityprofile.db.QualityProfileDto; -import org.sonar.core.qualityprofile.db.QualityProfileMapper; -import org.sonar.core.resource.ResourceDto; -import org.sonar.core.resource.ResourceIndexDto; -import org.sonar.core.resource.ResourceIndexerMapper; -import org.sonar.core.resource.ResourceKeyUpdaterMapper; -import org.sonar.core.resource.ResourceMapper; +import org.sonar.core.qualitygate.db.*; +import org.sonar.core.qualityprofile.db.*; +import org.sonar.core.resource.*; import org.sonar.core.rule.RuleDto; import org.sonar.core.rule.RuleMapper; import org.sonar.core.rule.RuleParamDto; @@ -125,19 +80,7 @@ import org.sonar.core.technicaldebt.db.CharacteristicMapper; import org.sonar.core.technicaldebt.db.RequirementMigrationDto; import org.sonar.core.template.LoadedTemplateDto; import org.sonar.core.template.LoadedTemplateMapper; -import org.sonar.core.user.AuthorDto; -import org.sonar.core.user.AuthorMapper; -import org.sonar.core.user.GroupDto; -import org.sonar.core.user.GroupMapper; -import org.sonar.core.user.GroupMembershipDto; -import org.sonar.core.user.GroupMembershipMapper; -import org.sonar.core.user.GroupRoleDto; -import org.sonar.core.user.RoleMapper; -import org.sonar.core.user.UserDto; -import org.sonar.core.user.UserGroupDto; -import org.sonar.core.user.UserGroupMapper; -import org.sonar.core.user.UserMapper; -import org.sonar.core.user.UserRoleDto; +import org.sonar.core.user.*; import javax.annotation.Nullable; @@ -262,7 +205,7 @@ public class MyBatis implements BatchComponent, ServerComponent { GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class, MeasureMapper.class, MetricMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, SnapshotMapper.class, ProjectQgateAssociationMapper.class, - AnalysisReportMapper.class, + AnalysisReportMapper.class, ComponentIndexMapper.class, Migration45Mapper.class, Migration50Mapper.class }; loadMappers(conf, mappers); diff --git a/sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java b/sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java index 530c9609f4d..31b778a4a5c 100644 --- a/sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java +++ b/sonar-core/src/main/java/org/sonar/core/user/AuthorizationDao.java @@ -19,14 +19,17 @@ */ package org.sonar.core.user; -import com.google.common.collect.ImmutableMap; +import com.google.common.base.Function; import com.google.common.collect.Sets; import org.apache.ibatis.session.SqlSession; import org.sonar.api.ServerComponent; import org.sonar.core.persistence.DaoComponent; +import org.sonar.core.persistence.DaoUtils; +import org.sonar.core.persistence.DbSession; import org.sonar.core.persistence.MyBatis; import javax.annotation.Nullable; + import java.util.*; import static com.google.common.collect.Maps.newHashMap; @@ -40,38 +43,48 @@ public class AuthorizationDao implements ServerComponent, DaoComponent { this.mybatis = mybatis; } - public Set keepAuthorizedComponentKeys(Set componentKeys, @Nullable Integer userId, String role) { - SqlSession session = mybatis.openSession(false); - try { - return keepAuthorizedComponentKeys(componentKeys, userId, role, session); + public Collection keepAuthorizedProjectIds(final DbSession session, final Collection componentIds, @Nullable final Integer userId, final String role) { + if (componentIds.isEmpty()) { + return Collections.emptySet(); + } + return DaoUtils.executeLargeInputs(componentIds, new Function, List>() { + @Override + public List apply(List partition) { + if (userId == null) { + return session.getMapper(AuthorizationMapper.class).keepAuthorizedProjectIdsForAnonymous(role, componentIds); + } else { + return session.getMapper(AuthorizationMapper.class).keepAuthorizedProjectIdsForUser(userId, role, componentIds); + } + } + }); + } + /** + * Used by the Views Plugin + */ + public boolean isAuthorizedComponentKey(String componentKey, @Nullable Integer userId, String role) { + DbSession session = mybatis.openSession(false); + try { + return keepAuthorizedComponentKeys(session, Sets.newHashSet(componentKey), userId, role).size() == 1; } finally { MyBatis.closeQuietly(session); } } - public Set keepAuthorizedComponentKeys(Set componentKeys, @Nullable Integer userId, String role, SqlSession session) { + private Set keepAuthorizedComponentKeys(final DbSession session, final Set componentKeys, @Nullable final Integer userId, final String role) { if (componentKeys.isEmpty()) { return Collections.emptySet(); } - String sql; - Map params; - if (userId == null) { - sql = "keepAuthorizedComponentKeysForAnonymous"; - params = ImmutableMap.of("role", role, "componentKeys", componentKeys); - } else { - sql = "keepAuthorizedComponentKeysForUser"; - params = ImmutableMap.of(USER_ID_PARAM, userId, "role", role, "componentKeys", componentKeys); - } - - return Sets.newHashSet(session.selectList(sql, params)); - } - - /** - * Used by the Views Plugin - */ - public boolean isAuthorizedComponentKey(String componentKey, @Nullable Integer userId, String role) { - return keepAuthorizedComponentKeys(Sets.newHashSet(componentKey), userId, role).size() == 1; + return Sets.newHashSet(DaoUtils.executeLargeInputs(componentKeys, new Function, List>() { + @Override + public List apply(List partition) { + if (userId == null) { + return session.getMapper(AuthorizationMapper.class).keepAuthorizedComponentKeysForAnonymous(role, componentKeys); + } else { + return session.getMapper(AuthorizationMapper.class).keepAuthorizedComponentKeysForUser(userId, role, componentKeys); + } + } + })); } public Collection selectAuthorizedRootProjectsKeys(@Nullable Integer userId, String role) { diff --git a/sonar-core/src/main/java/org/sonar/core/user/AuthorizationMapper.java b/sonar-core/src/main/java/org/sonar/core/user/AuthorizationMapper.java new file mode 100644 index 00000000000..10d137dffbd --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/user/AuthorizationMapper.java @@ -0,0 +1,38 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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.core.user; + +import org.apache.ibatis.annotations.Param; + +import java.util.Collection; +import java.util.List; + +public interface AuthorizationMapper { + + List keepAuthorizedProjectIdsForAnonymous(@Param("role") String role, @Param("componentIds") Collection componentIds); + + List keepAuthorizedProjectIdsForUser(@Param("userId") Integer userId, @Param("role") String role, @Param("componentIds") Collection componentIds); + + List keepAuthorizedComponentKeysForAnonymous(@Param("role") String role, @Param("componentKeys") Collection componentKeys); + + List keepAuthorizedComponentKeysForUser(@Param("userId") Integer userId, @Param("role") String role, @Param("componentKeys") Collection componentKeys); + +} diff --git a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentIndexMapper.xml b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentIndexMapper.xml new file mode 100644 index 00000000000..6b80ef94aac --- /dev/null +++ b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentIndexMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml index 98426094cd2..ee96cd81563 100644 --- a/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/component/db/ComponentMapper.xml @@ -86,6 +86,19 @@ + + + + + + diff --git a/sonar-core/src/test/java/org/sonar/core/user/AuthorizationDaoTest.java b/sonar-core/src/test/java/org/sonar/core/user/AuthorizationDaoTest.java index 2e267d391ad..521d0af66a3 100644 --- a/sonar-core/src/test/java/org/sonar/core/user/AuthorizationDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/user/AuthorizationDaoTest.java @@ -20,19 +20,33 @@ package org.sonar.core.user; import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.sonar.core.persistence.AbstractDaoTestCase; +import org.sonar.core.persistence.DbSession; import java.util.Collection; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; public class AuthorizationDaoTest extends AbstractDaoTestCase { private static final int USER = 100; - private static final String PROJECT = "pj-w-snapshot", PACKAGE = "pj-w-snapshot:package", FILE = "pj-w-snapshot:file", FILE_IN_OTHER_PROJECT = "another", - EMPTY_PROJECT = "pj-wo-snapshot"; + private static final Long PROJECT_ID = 300L, EMPTY_PROJECT_ID = 400L; + private static final String PROJECT = "pj-w-snapshot"; + + DbSession session; + + @Before + public void setUp() throws Exception { + session = getMyBatis().openSession(false); + } + + @After + public void tearDown() throws Exception { + session.close(); + } @Test public void user_should_be_authorized() { @@ -40,15 +54,15 @@ public class AuthorizationDaoTest extends AbstractDaoTestCase { setupData("user_should_be_authorized"); AuthorizationDao authorization = new AuthorizationDao(getMyBatis()); - Set componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT, EMPTY_PROJECT), + Collection componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID, EMPTY_PROJECT_ID), USER, "user"); - assertThat(componentIds).containsOnly(PROJECT, PACKAGE, FILE, EMPTY_PROJECT); + assertThat(componentIds).containsOnly(PROJECT_ID, EMPTY_PROJECT_ID); // user does not have the role "admin" - componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE), + componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID), USER, "admin"); assertThat(componentIds).isEmpty(); } @@ -72,15 +86,15 @@ public class AuthorizationDaoTest extends AbstractDaoTestCase { setupData("group_should_be_authorized"); AuthorizationDao authorization = new AuthorizationDao(getMyBatis()); - Set componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT, EMPTY_PROJECT), + Collection componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID, EMPTY_PROJECT_ID), USER, "user"); - assertThat(componentIds).containsOnly(PROJECT, PACKAGE, FILE, EMPTY_PROJECT); + assertThat(componentIds).containsOnly(PROJECT_ID, EMPTY_PROJECT_ID); // group does not have the role "admin" - componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT, EMPTY_PROJECT), + componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID, EMPTY_PROJECT_ID), USER, "admin"); assertThat(componentIds).isEmpty(); } @@ -91,15 +105,15 @@ public class AuthorizationDaoTest extends AbstractDaoTestCase { setupData("group_should_have_global_authorization"); AuthorizationDao authorization = new AuthorizationDao(getMyBatis()); - Set componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT, EMPTY_PROJECT), + Collection componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID, EMPTY_PROJECT_ID), USER, "user"); - assertThat(componentIds).containsOnly(PROJECT, PACKAGE, FILE, EMPTY_PROJECT); + assertThat(componentIds).containsOnly(PROJECT_ID, EMPTY_PROJECT_ID); // group does not have the role "admin" - componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT, EMPTY_PROJECT), + componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID, EMPTY_PROJECT_ID), USER, "admin"); assertThat(componentIds).isEmpty(); } @@ -109,15 +123,15 @@ public class AuthorizationDaoTest extends AbstractDaoTestCase { setupData("anonymous_should_be_authorized"); AuthorizationDao authorization = new AuthorizationDao(getMyBatis()); - Set componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT, EMPTY_PROJECT), + Collection componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID, EMPTY_PROJECT_ID), null, "user"); - assertThat(componentIds).containsOnly(PROJECT, PACKAGE, FILE, EMPTY_PROJECT); + assertThat(componentIds).containsOnly(PROJECT_ID, EMPTY_PROJECT_ID); // group does not have the role "admin" - componentIds = authorization.keepAuthorizedComponentKeys( - Sets.newHashSet(PROJECT, PACKAGE, FILE, FILE_IN_OTHER_PROJECT), + componentIds = authorization.keepAuthorizedProjectIds(session, + Sets.newHashSet(PROJECT_ID), null, "admin"); assertThat(componentIds).isEmpty(); } diff --git a/sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/user_should_be_authorized.xml b/sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/user_should_be_authorized.xml index b6371a7e0b1..3771e09738d 100644 --- a/sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/user_should_be_authorized.xml +++ b/sonar-core/src/test/resources/org/sonar/core/user/AuthorizationDaoTest/user_should_be_authorized.xml @@ -6,9 +6,6 @@ - - - -- 2.39.5