aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/root/ws/RootWsModule.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/root/ws/SearchAction.java90
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/root/ws/RootWsModuleTest.java40
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/root/ws/SearchActionTest.java146
-rw-r--r--sonar-db/src/main/java/org/sonar/db/user/UserDao.java23
-rw-r--r--sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml6
-rw-r--r--sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java18
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/user/UserQuery.java18
-rw-r--r--sonar-ws/src/main/protobuf/ws-root.proto36
9 files changed, 361 insertions, 19 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/root/ws/RootWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/root/ws/RootWsModule.java
index 2b5edc86b7a..4e49416f721 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/root/ws/RootWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/root/ws/RootWsModule.java
@@ -26,6 +26,7 @@ public class RootWsModule extends Module {
protected void configureModule() {
add(RootWs.class,
SetRootWsAction.class,
- UnsetRootWsAction.class);
+ UnsetRootWsAction.class,
+ SearchAction.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/root/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/root/ws/SearchAction.java
new file mode 100644
index 00000000000..35b4c02cf65
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/root/ws/SearchAction.java
@@ -0,0 +1,90 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.root.ws;
+
+import java.util.List;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.user.UserQuery;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.WsRoot;
+
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+
+public class SearchAction implements RootWsAction {
+ private final UserSession userSession;
+ private final DbClient dbClient;
+
+ public SearchAction(UserSession userSession, DbClient dbClient) {
+ this.userSession = userSession;
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public void define(WebService.NewController controller) {
+ controller.createAction("search")
+ .setInternal(true)
+ .setPost(false)
+ .setDescription("Search for root user.<br/>" +
+ "Requires to be root.")
+ .setSince("6.2")
+ .setHandler(this);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ userSession.checkIsRoot();
+
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ List<UserDto> userDtos = dbClient.userDao().selectUsers(
+ dbSession,
+ UserQuery.builder()
+ .mustBeRoot()
+ .build());
+
+ writeResponse(request, response, userDtos);
+ }
+ }
+
+ private static void writeResponse(Request request, Response response, List<UserDto> dtos) {
+ WsRoot.SearchWsResponse.Builder responseBuilder = WsRoot.SearchWsResponse.newBuilder();
+ WsRoot.Root.Builder rootBuilder = WsRoot.Root.newBuilder();
+ dtos.forEach(dto -> responseBuilder.addRoots(toRoot(rootBuilder, dto)));
+ writeProtobuf(responseBuilder.build(), request, response);
+ }
+
+ private static WsRoot.Root toRoot(WsRoot.Root.Builder builder, UserDto dto) {
+ builder.clear();
+ if (dto.getLogin() != null) {
+ builder.setLogin(dto.getLogin());
+ }
+ if (dto.getName() != null) {
+ builder.setName(dto.getName());
+ }
+ if (dto.getEmail() != null) {
+ builder.setEmail(dto.getEmail());
+ }
+ return builder.build();
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/root/ws/RootWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/root/ws/RootWsModuleTest.java
new file mode 100644
index 00000000000..23337775764
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/root/ws/RootWsModuleTest.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.root.ws;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RootWsModuleTest {
+ private static final int ADAPTERS_IN_EMPTY_CONTAINER = 2;
+
+ private RootWsModule underTest = new RootWsModule();
+
+ @Test
+ public void verify_number_of_components_added_by_module() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(container.getPicoContainer().getComponentAdapters()).hasSize(ADAPTERS_IN_EMPTY_CONTAINER + 4);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/root/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/root/ws/SearchActionTest.java
new file mode 100644
index 00000000000..b3332b42f08
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/root/ws/SearchActionTest.java
@@ -0,0 +1,146 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.root.ws;
+
+import java.io.IOException;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDao;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsRoot;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SearchActionTest {
+
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public UserSessionRule userSessionRule = UserSessionRule.standalone();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private UserDao userDao = dbTester.getDbClient().userDao();
+ private DbSession dbSession = dbTester.getSession();
+ private SearchAction underTest = new SearchAction(userSessionRule, dbTester.getDbClient());
+ private WsActionTester wsTester = new WsActionTester(underTest);
+
+ @Test
+ public void verify_definition() {
+ WebService.Action action = wsTester.getDef();
+ assertThat(action.key()).isEqualTo("search");
+ assertThat(action.isInternal()).isTrue();
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.since()).isEqualTo("6.2");
+ assertThat(action.description()).isEqualTo("Search for root user.<br/>" +
+ "Requires to be root.");
+ assertThat(action.responseExample()).isNull();
+ assertThat(action.deprecatedKey()).isNull();
+ assertThat(action.deprecatedSince()).isNull();
+ assertThat(action.handler()).isSameAs(underTest);
+ assertThat(action.params()).isEmpty();
+ }
+
+ @Test
+ public void execute_fails_with_ForbiddenException_when_user_is_not_logged_in() {
+ expectInsufficientPrivilegesForbiddenException();
+
+ executeRequest();
+ }
+
+ @Test
+ public void execute_fails_with_ForbiddenException_when_user_is_not_root() {
+ userSessionRule.login();
+
+ expectInsufficientPrivilegesForbiddenException();
+
+ executeRequest();
+ }
+
+ @Test
+ public void execute_returns_empty_list_of_root_when_DB_is_empty() {
+ makeAuthenticatedUserRoot();
+
+ assertThat(executeRequest()).isEmpty();
+ }
+
+ @Test
+ public void execute_does_not_fail_when_root_user_has_neither_email_nor_name() {
+ makeAuthenticatedUserRoot();
+ UserDto rootDto = userDao.insert(dbSession, UserTesting.newUserDto().setName(null).setEmail(null));
+ userDao.setRoot(dbSession, rootDto.getLogin(), true);
+ dbSession.commit();
+
+ List<WsRoot.Root> roots = executeRequest();
+ assertThat(roots).hasSize(1);
+ WsRoot.Root root = roots.iterator().next();
+ assertThat(root.getLogin()).isEqualTo(rootDto.getLogin());
+ assertThat(root.hasName()).isFalse();
+ assertThat(root.hasEmail()).isFalse();
+ }
+
+ @Test
+ public void execute_returns_root_users_sorted_by_name() {
+ makeAuthenticatedUserRoot();
+ userDao.insert(dbSession, UserTesting.newUserDto().setName("ddd"));
+ UserDto root1 = userDao.insert(dbSession, UserTesting.newUserDto().setName("ccc"));
+ userDao.setRoot(dbSession, root1.getLogin(), true);
+ UserDto root2 = userDao.insert(dbSession, UserTesting.newUserDto().setName("bbb"));
+ userDao.setRoot(dbSession, root2.getLogin(), true);
+ userDao.insert(dbSession, UserTesting.newUserDto().setName("aaa"));
+ dbSession.commit();
+
+ assertThat(executeRequest())
+ .extracting(WsRoot.Root::getName)
+ .containsExactly("bbb", "ccc");
+ }
+
+ private UserSessionRule makeAuthenticatedUserRoot() {
+ return userSessionRule.login().setRoot();
+ }
+
+ private List<WsRoot.Root> executeRequest() {
+ TestRequest request = wsTester.newRequest()
+ .setMediaType(MediaTypes.PROTOBUF);
+ try {
+ return WsRoot.SearchWsResponse.parseFrom(request.execute().getInputStream()).getRootsList();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void expectInsufficientPrivilegesForbiddenException() {
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+ }
+
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserDao.java b/sonar-db/src/main/java/org/sonar/db/user/UserDao.java
index 5e1fe961e72..c4f1dc95e9e 100644
--- a/sonar-db/src/main/java/org/sonar/db/user/UserDao.java
+++ b/sonar-db/src/main/java/org/sonar/db/user/UserDao.java
@@ -51,11 +51,8 @@ public class UserDao implements Dao {
}
public UserDto selectUserById(long userId) {
- DbSession session = mybatis.openSession(false);
- try {
+ try (DbSession session = mybatis.openSession(false)) {
return selectUserById(session, userId);
- } finally {
- MyBatis.closeQuietly(session);
}
}
@@ -80,11 +77,8 @@ public class UserDao implements Dao {
*/
@CheckForNull
public UserDto selectActiveUserByLogin(String login) {
- DbSession session = mybatis.openSession(false);
- try {
+ try (DbSession session = mybatis.openSession(false)) {
return selectActiveUserByLogin(session, login);
- } finally {
- MyBatis.closeQuietly(session);
}
}
@@ -107,11 +101,8 @@ public class UserDao implements Dao {
*/
@Deprecated
public List<UserDto> selectByLogins(Collection<String> logins) {
- DbSession session = mybatis.openSession(false);
- try {
+ try (DbSession session = mybatis.openSession(false)) {
return selectByLogins(session, logins);
- } finally {
- MyBatis.closeQuietly(session);
}
}
@@ -127,17 +118,13 @@ public class UserDao implements Dao {
}
public List<UserDto> selectUsers(UserQuery query) {
- DbSession session = mybatis.openSession(false);
- try {
+ try (DbSession session = mybatis.openSession(false)) {
return selectUsers(session, query);
- } finally {
- MyBatis.closeQuietly(session);
}
}
public List<UserDto> selectUsers(DbSession dbSession, UserQuery query) {
- UserMapper mapper = getMapper(dbSession);
- return mapper.selectUsers(query);
+ return getMapper(dbSession).selectUsers(query);
}
public long countRootUsersButLogin(DbSession dbSession, String login) {
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
index f40d1e36fcd..8456c837539 100644
--- a/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
+++ b/sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
@@ -88,6 +88,12 @@
<if test="searchText != null">
AND (u.login LIKE #{searchTextSql} ESCAPE '/' OR u.name LIKE #{searchTextSql} ESCAPE '/')
</if>
+ <if test="mustBeRoot != null and mustBeRoot==true">
+ AND u.is_root = ${_true}
+ </if>
+ <if test="mustBeRoot != null and mustBeRoot==false">
+ AND u.is_root = ${_false}
+ </if>
</where>
ORDER BY u.name
</select>
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
index f1d7de6201f..c8cc20bb731 100644
--- a/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
+++ b/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
@@ -193,6 +193,24 @@ public class UserDaoTest {
}
@Test
+ public void selectUsers_returns_both_only_root_or_only_non_root_depending_on_mustBeRoot_and_mustNotBeRoot_calls_on_query() {
+ UserDto user1 = insertUser(true);
+ UserDto root1 = insertRootUser(newUserDto());
+ UserDto user2 = insertUser(true);
+ UserDto root2 = insertRootUser(newUserDto());
+
+ assertThat(underTest.selectUsers(UserQuery.builder().build()))
+ .extracting(UserDto::getLogin)
+ .containsOnly(user1.getLogin(), user2.getLogin(), root1.getLogin(), root2.getLogin());
+ assertThat(underTest.selectUsers(UserQuery.builder().mustBeRoot().build()))
+ .extracting(UserDto::getLogin)
+ .containsOnly(root1.getLogin(), root2.getLogin());
+ assertThat(underTest.selectUsers(UserQuery.builder().mustNotBeRoot().build()))
+ .extracting(UserDto::getLogin)
+ .containsOnly(user1.getLogin(), user2.getLogin());
+ }
+
+ @Test
public void countRootUsersButLogin_returns_0_when_there_is_no_user_at_all() {
assertThat(underTest.countRootUsersButLogin(session, "bla")).isEqualTo(0);
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/user/UserQuery.java b/sonar-plugin-api/src/main/java/org/sonar/api/user/UserQuery.java
index 76a3c0fef59..136e16a1f39 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/user/UserQuery.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/user/UserQuery.java
@@ -36,6 +36,7 @@ public class UserQuery {
private final Collection<String> logins;
private final boolean includeDeactivated;
private final String searchText;
+ private final Boolean mustBeRoot;
// for internal use in MyBatis
final String searchTextSql;
@@ -44,6 +45,7 @@ public class UserQuery {
this.logins = builder.logins;
this.includeDeactivated = builder.includeDeactivated;
this.searchText = builder.searchText;
+ this.mustBeRoot = builder.mustBeRoot;
this.searchTextSql = searchTextToSql(searchText);
}
@@ -75,6 +77,11 @@ public class UserQuery {
return searchText;
}
+ @CheckForNull
+ public Boolean mustBeRoot() {
+ return mustBeRoot;
+ }
+
public static Builder builder() {
return new Builder();
}
@@ -83,6 +90,7 @@ public class UserQuery {
private boolean includeDeactivated = false;
private Collection<String> logins;
private String searchText;
+ private Boolean mustBeRoot;
private Builder() {
}
@@ -108,6 +116,16 @@ public class UserQuery {
return this;
}
+ public Builder mustBeRoot() {
+ this.mustBeRoot = true;
+ return this;
+ }
+
+ public Builder mustNotBeRoot() {
+ this.mustBeRoot = false;
+ return this;
+ }
+
public UserQuery build() {
if (logins != null && logins.size() >= 1000) {
throw new IllegalArgumentException("Max number of logins is 1000. Got " + logins.size());
diff --git a/sonar-ws/src/main/protobuf/ws-root.proto b/sonar-ws/src/main/protobuf/ws-root.proto
new file mode 100644
index 00000000000..032ee5ab24b
--- /dev/null
+++ b/sonar-ws/src/main/protobuf/ws-root.proto
@@ -0,0 +1,36 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2015 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.
+
+syntax = "proto2";
+
+package sonarqube.ws.root;
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "WsRoot";
+option optimize_for = SPEED;
+
+// WS api/root/search
+message SearchWsResponse {
+ repeated Root roots = 1;
+}
+
+message Root {
+ optional string login = 1;
+ optional string name = 2;
+ optional string email = 3;
+}