From: Teryk Bellahsene Date: Thu, 26 Nov 2015 07:40:44 +0000 (+0100) Subject: SONAR-7048 WS user_tokens/search list access tokens of a user X-Git-Tag: 5.3-RC1~155 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=598d9e8a08c0bcfeac733ae8a7c65743289a96d7;p=sonarqube.git SONAR-7048 WS user_tokens/search list access tokens of a user --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java index 5583e636ddd..61a7e61d0b4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java @@ -23,6 +23,7 @@ package org.sonar.server.usertoken; import org.sonar.core.platform.Module; import org.sonar.server.usertoken.ws.GenerateAction; import org.sonar.server.usertoken.ws.RevokeAction; +import org.sonar.server.usertoken.ws.SearchAction; import org.sonar.server.usertoken.ws.UserTokensWs; public class UserTokenModule extends Module { @@ -32,6 +33,7 @@ public class UserTokenModule extends Module { UserTokensWs.class, GenerateAction.class, RevokeAction.class, + SearchAction.class, UserTokenAuthenticator.class, TokenGeneratorImpl.class); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java new file mode 100644 index 00000000000..1010aff2294 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java @@ -0,0 +1,110 @@ +/* + * 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.usertoken.ws; + +import java.util.Date; +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.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.user.UserTokenDto; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.WsUserTokens.SearchWsResponse; +import org.sonarqube.ws.client.usertoken.SearchWsRequest; + +import static org.sonar.api.utils.DateUtils.formatDateTime; +import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_SEARCH; +import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN; + +public class SearchAction implements UserTokensWsAction { + private final DbClient dbClient; + private final UserSession userSession; + + public SearchAction(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction(ACTION_SEARCH) + .setDescription("List the access tokens of a user.
" + + "The login must exist and active.
" + + "It requires administration permissions.") + .setResponseExample(getClass().getResource("search-example.json")) + .setSince("5.3") + .setHandler(this); + + action.createParam(PARAM_LOGIN) + .setRequired(true) + .setDescription("User login") + .setExampleValue("g.hopper"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + SearchWsResponse searchWsResponse = doHandle(toSearchWsRequest(request)); + writeProtobuf(searchWsResponse, request, response); + } + + private SearchWsResponse doHandle(SearchWsRequest request) { + userSession.checkLoggedIn().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN); + + DbSession dbSession = dbClient.openSession(false); + try { + String login = request.getLogin(); + checkLoginExists(dbSession, login); + List userTokens = dbClient.userTokenDao().selectByLogin(dbSession, login); + return buildResponse(login, userTokens); + } finally { + dbClient.closeSession(dbSession); + } + } + + private static SearchWsRequest toSearchWsRequest(Request request) { + return new SearchWsRequest() + .setLogin(request.mandatoryParam(PARAM_LOGIN)); + } + + private static SearchWsResponse buildResponse(String login, List userTokensDto) { + SearchWsResponse.Builder searchWsResponse = SearchWsResponse.newBuilder(); + SearchWsResponse.UserToken.Builder userTokenBuilder = SearchWsResponse.UserToken.newBuilder(); + searchWsResponse.setLogin(login); + for (UserTokenDto userTokenDto : userTokensDto) { + userTokenBuilder + .clear() + .setName(userTokenDto.getName()) + .setCreatedAt(formatDateTime(new Date(userTokenDto.getCreatedAt()))); + searchWsResponse.addUserTokens(userTokenBuilder); + } + + return searchWsResponse.build(); + } + + private void checkLoginExists(DbSession dbSession, String login) { + checkFound(dbClient.userDao().selectByLogin(dbSession, login), "User with login '%s' not found", login); + } +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/search-example.json new file mode 100644 index 00000000000..b19fd6032ba --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/search-example.json @@ -0,0 +1,17 @@ +{ + "login": "grace.hopper", + "userTokens": [ + { + "name": "Project scan on AppVeyor", + "createdAt": "2015-08-02T15:44:27+0200" + }, + { + "name": "Project scan on Jenkins", + "createdAt": "2015-04-08T21:57:47+0200" + }, + { + "name": "Project scan on Travis", + "createdAt": "2015-11-26T08:31:07+0100" + } + ] +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java index e4bd3a614c5..ddc7ac50ab0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java @@ -30,6 +30,6 @@ public class UserTokenModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new UserTokenModule().configure(container); - assertThat(container.size()).isEqualTo(6); + assertThat(container.size()).isEqualTo(8); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java new file mode 100644 index 00000000000..1c0051c2ca9 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java @@ -0,0 +1,139 @@ +/* + * 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.usertoken.ws; + +import com.google.common.base.Throwables; +import java.io.IOException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.System2; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.user.UserDbTester; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; +import org.sonar.test.DbTests; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.WsUserTokens.SearchWsResponse; + +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.db.user.UserTokenTesting.newUserToken; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN; + +@Category(DbTests.class) +public class SearchActionTest { + static final String GRACE_HOPPER = "grace.hopper"; + static final String ADA_LOVELACE = "ada.lovelace"; + static final String TOKEN_NAME = "token-name"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + UserDbTester userDb = new UserDbTester(db); + DbClient dbClient = db.getDbClient(); + DbSession dbSession = db.getSession(); + + WsActionTester ws = new WsActionTester(new SearchAction(dbClient, userSession)); + + @Before + public void setUp() { + userSession.login().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + userDb.insertUser(newUserDto().setLogin(GRACE_HOPPER)); + userDb.insertUser(newUserDto().setLogin(ADA_LOVELACE)); + } + + @Test + public void search_json_example() { + dbClient.userTokenDao().insert(dbSession, newUserToken() + .setCreatedAt(1448523067221L) + .setName("Project scan on Travis") + .setLogin(GRACE_HOPPER)); + dbClient.userTokenDao().insert(dbSession, newUserToken() + .setCreatedAt(1438523067221L) + .setName("Project scan on AppVeyor") + .setLogin(GRACE_HOPPER)); + dbClient.userTokenDao().insert(dbSession, newUserToken() + .setCreatedAt(1428523067221L) + .setName("Project scan on Jenkins") + .setLogin(GRACE_HOPPER)); + dbClient.userTokenDao().insert(dbSession, newUserToken() + .setCreatedAt(141456787123L) + .setName("Project scan on Travis") + .setLogin(ADA_LOVELACE)); + dbSession.commit(); + String response = ws.newRequest() + .setParam(PARAM_LOGIN, GRACE_HOPPER) + .execute().getInput(); + + assertJson(response).isSimilarTo(getClass().getResource("search-example.json")); + } + + @Test + public void fail_when_login_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("User with login 'unknown-login' not found"); + + newRequest("unknown-login"); + } + + @Test + public void fail_when_not_logged_in() { + userSession.anonymous(); + expectedException.expect(UnauthorizedException.class); + + newRequest(GRACE_HOPPER); + } + + @Test + public void fail_when_insufficient_privileges() { + userSession.login().setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION); + expectedException.expect(ForbiddenException.class); + + newRequest(GRACE_HOPPER); + } + + private SearchWsResponse newRequest(String login) { + TestResponse response = ws.newRequest() + .setMediaType(MediaTypes.PROTOBUF) + .setParam(PARAM_LOGIN, login) + .execute(); + try { + return SearchWsResponse.parseFrom(response.getInputStream()); + } catch (IOException e) { + Throwables.propagate(e); + } + + throw new IllegalStateException("unreachable"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java index b4b3b561762..9acd50fd373 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java @@ -46,7 +46,8 @@ public class UserTokensWsTest { ws = new WsTester(new UserTokensWs( new GenerateAction(dbClient, userSession, system, tokenGenerator), - new RevokeAction(dbClient, userSession))); + new RevokeAction(dbClient, userSession), + new SearchAction(dbClient, userSession))); } @Test @@ -71,4 +72,14 @@ public class UserTokensWsTest { assertThat(action.param("login").isRequired()).isTrue(); assertThat(action.param("name").isRequired()).isTrue(); } + + @Test + public void search_action() { + WebService.Action action = ws.action(CONTROLLER_KEY, "search"); + + assertThat(action).isNotNull(); + assertThat(action.since()).isEqualTo("5.3"); + assertThat(action.isPost()).isFalse(); + assertThat(action.param("login").isRequired()).isTrue(); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/SearchWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/SearchWsRequest.java new file mode 100644 index 00000000000..ee38993534b --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/SearchWsRequest.java @@ -0,0 +1,34 @@ +/* + * 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.sonarqube.ws.client.usertoken; + +public class SearchWsRequest { + private String login; + + public String getLogin() { + return login; + } + + public SearchWsRequest setLogin(String login) { + this.login = login; + return this; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java index 3c956477f4b..fdd7eeca721 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java @@ -20,12 +20,15 @@ package org.sonarqube.ws.client.usertoken; +import org.sonarqube.ws.WsComponents.SearchWsResponse; import org.sonarqube.ws.WsUserTokens.GenerateWsResponse; import org.sonarqube.ws.client.WsClient; +import static org.sonarqube.ws.client.WsRequest.newGetRequest; import static org.sonarqube.ws.client.WsRequest.newPostRequest; import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_GENERATE; import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_REVOKE; +import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_SEARCH; import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN; import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME; import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.USER_TOKENS_ENDPOINT; @@ -53,6 +56,14 @@ public class UserTokensWsClient { .setParam(PARAM_NAME, request.getName())); } + public SearchWsResponse search(SearchWsRequest request) { + return wsClient.execute( + newGetRequest(action(ACTION_SEARCH)) + .setParam(PARAM_LOGIN, request.getLogin()), + SearchWsResponse.parser() + ); + } + private static String action(String action) { return USER_TOKENS_ENDPOINT + SLASH + action; } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java index 37ddfa0099b..203ee5243a8 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java @@ -24,6 +24,7 @@ public class UserTokensWsParameters { public static final String USER_TOKENS_ENDPOINT = "api/user_tokens"; public static final String ACTION_GENERATE = "generate"; public static final String ACTION_REVOKE = "revoke"; + public static final String ACTION_SEARCH = "search"; public static final String PARAM_LOGIN = "login"; public static final String PARAM_NAME = "name"; diff --git a/sonar-ws/src/main/protobuf/ws-user_tokens.proto b/sonar-ws/src/main/protobuf/ws-user_tokens.proto index 8ba07bbf483..f25403ea6aa 100644 --- a/sonar-ws/src/main/protobuf/ws-user_tokens.proto +++ b/sonar-ws/src/main/protobuf/ws-user_tokens.proto @@ -30,3 +30,14 @@ message GenerateWsResponse { optional string name = 2; optional string token = 3; } + +// WS api/user_tokens/search +message SearchWsResponse { + optional string login = 1; + repeated UserToken userTokens = 2; + + message UserToken { + optional string name = 1; + optional string createdAt = 2; + } +}