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 {
UserTokensWs.class,
GenerateAction.class,
RevokeAction.class,
+ SearchAction.class,
UserTokenAuthenticator.class,
TokenGeneratorImpl.class);
}
--- /dev/null
+/*
+ * 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. <br />" +
+ "The login must exist and active.<br />" +
+ "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<UserTokenDto> 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<UserTokenDto> 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);
+ }
+}
--- /dev/null
+{
+ "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"
+ }
+ ]
+}
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);
}
}
--- /dev/null
+/*
+ * 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");
+ }
+}
ws = new WsTester(new UserTokensWs(
new GenerateAction(dbClient, userSession, system, tokenGenerator),
- new RevokeAction(dbClient, userSession)));
+ new RevokeAction(dbClient, userSession),
+ new SearchAction(dbClient, userSession)));
}
@Test
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();
+ }
}
--- /dev/null
+/*
+ * 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;
+ }
+}
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;
.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;
}
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";
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;
+ }
+}