]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7048 WS user_tokens/search list access tokens of a user
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 26 Nov 2015 07:40:44 +0000 (08:40 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 26 Nov 2015 13:13:28 +0000 (14:13 +0100)
server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/SearchAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/search-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/SearchWsRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java
sonar-ws/src/main/protobuf/ws-user_tokens.proto

index 5583e636dddc5ffa9f0f17a3bb55047d1f191674..61a7e61d0b45f7309c03f2ac22c72dd9859a4bc2 100644 (file)
@@ -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 (file)
index 0000000..1010aff
--- /dev/null
@@ -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. <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);
+  }
+}
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 (file)
index 0000000..b19fd60
--- /dev/null
@@ -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"
+    }
+  ]
+}
index e4bd3a614c5a0f5ad524ab2b62db2bcc13a4375c..ddc7ac50ab0301d131b3f736548ff73c13481f2a 100644 (file)
@@ -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 (file)
index 0000000..1c0051c
--- /dev/null
@@ -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");
+  }
+}
index b4b3b561762332e971f15498a244f5c105dec8a7..9acd50fd37390ba2d92152128d6c833c84905ed4 100644 (file)
@@ -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 (file)
index 0000000..ee38993
--- /dev/null
@@ -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;
+  }
+}
index 3c956477f4b0442b08ce542fe9bcf87c0dc2316f..fdd7eeca7217b9b548d2bb5c04a71eb04bf9c675 100644 (file)
 
 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;
   }
index 37ddfa0099bc2cf2863f15ce8fa831077b554616..203ee5243a851a5fa8f9413b462f1311c2cf3fcc 100644 (file)
@@ -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";
index 8ba07bbf483f0265c3ff9fc0a294d162540aa717..f25403ea6aa7662ea61d8d2387882fd9f4cc42b3 100644 (file)
@@ -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;
+  }
+}