]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7042 WS user_tokens/revoke revoke an access token
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 25 Nov 2015 08:41:19 +0000 (09:41 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 26 Nov 2015 13:12:36 +0000 (14:12 +0100)
13 files changed:
server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java
sonar-db/src/main/java/org/sonar/db/user/UserTokenDao.java
sonar-db/src/main/java/org/sonar/db/user/UserTokenMapper.java
sonar-db/src/main/resources/org/sonar/db/user/UserTokenMapper.xml
sonar-db/src/test/java/org/sonar/db/user/UserTokenDaoTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/RevokeWsRequest.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

index 2fd1753fe2ca79793abdd4d028acd28428f97939..5583e636dddc5ffa9f0f17a3bb55047d1f191674 100644 (file)
@@ -22,6 +22,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.UserTokensWs;
 
 public class UserTokenModule extends Module {
@@ -30,6 +31,7 @@ public class UserTokenModule extends Module {
     add(
       UserTokensWs.class,
       GenerateAction.class,
+      RevokeAction.class,
       UserTokenAuthenticator.class,
       TokenGeneratorImpl.class);
   }
index 7ed59918edaba47b144b5761e6a60ac193581e73..300f729faa2996a51b2351fa3dc6fafc567cb429 100644 (file)
@@ -50,7 +50,7 @@ public class GenerateAction implements UserTokensWsAction {
   private final System2 system;
   private final TokenGenerator tokenGenerator;
 
-  public GenerateAction(UserSession userSession, DbClient dbClient, System2 system, TokenGenerator tokenGenerator) {
+  public GenerateAction(DbClient dbClient, UserSession userSession, System2 system, TokenGenerator tokenGenerator) {
     this.userSession = userSession;
     this.dbClient = dbClient;
     this.system = system;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/RevokeAction.java
new file mode 100644 (file)
index 0000000..32dece2
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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 org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.client.usertoken.RevokeWsRequest;
+
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_REVOKE;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME;
+
+public class RevokeAction implements UserTokensWsAction {
+  private final DbClient dbClient;
+  private final UserSession userSession;
+
+  public RevokeAction(DbClient dbClient, UserSession userSession) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction(ACTION_REVOKE)
+      .setDescription("Revoke a user access token. <br/>"+
+        "It requires administration permissions.")
+      .setSince("5.3")
+      .setPost(true)
+      .setHandler(this);
+
+    action.createParam(PARAM_LOGIN)
+      .setRequired(true)
+      .setDescription("User login")
+      .setExampleValue("g.hopper");
+
+    action.createParam(PARAM_NAME)
+      .setRequired(true)
+      .setDescription("Token name")
+      .setExampleValue("Project scan on Travis");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    doHandle(toRevokeWsRequest(request));
+    response.noContent();
+  }
+
+  private void doHandle(RevokeWsRequest request) {
+    userSession.checkLoggedIn().checkGlobalPermission(SYSTEM_ADMIN);
+
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      dbClient.userTokenDao().deleteByLoginAndName(dbSession, request.getLogin(), request.getName());
+      dbSession.commit();
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
+  }
+
+  private static RevokeWsRequest toRevokeWsRequest(Request request) {
+    return new RevokeWsRequest()
+      .setLogin(request.mandatoryParam(PARAM_LOGIN))
+      .setName(request.mandatoryParam(PARAM_NAME));
+  }
+}
index a4190e479ef8541ec3372b7d6a033951e2cbfee3..e0b0e11ca9b1f9d9795de8505c8a372dba93d001 100644 (file)
@@ -79,7 +79,7 @@ public class GenerateActionTest {
     userDb.insertUser(newUserDto().setLogin(ADA_LOVELACE));
 
     ws = new WsActionTester(
-      new GenerateAction(userSession, db.getDbClient(), System2.INSTANCE, tokenGenerator));
+      new GenerateAction(db.getDbClient(), userSession, System2.INSTANCE, tokenGenerator));
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java
new file mode 100644 (file)
index 0000000..e9e0995
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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 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.UserTokenDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.user.UserTokenTesting.newUserToken;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME;
+
+@Category(DbTests.class)
+public class RevokeActionTest {
+  static final String GRACE_HOPPER = "grace.hopper";
+  static final String ADA_LOVELACE = "ada.lovelace";
+  static final String TOKEN_NAME = "token-name";
+
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  DbClient dbClient = db.getDbClient();
+  DbSession dbSession = db.getSession();
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  WsActionTester ws;
+
+  @Before
+  public void setUp() {
+    userSession
+      .login()
+      .setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+
+    ws = new WsActionTester(
+      new RevokeAction(dbClient, userSession));
+  }
+
+  @Test
+  public void delete_token_in_db() {
+    insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-delete"));
+    insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-keep-1"));
+    insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName("token-to-keep-2"));
+    insertUserToken(newUserToken().setLogin(ADA_LOVELACE).setName("token-to-delete"));
+
+    String response = newRequest(GRACE_HOPPER, "token-to-delete");
+
+    assertThat(response).isEmpty();
+    assertThat(dbClient.userTokenDao().selectByLogin(dbSession, GRACE_HOPPER)).extracting("name").containsOnly("token-to-keep-1", "token-to-keep-2");
+    assertThat(dbClient.userTokenDao().selectByLogin(dbSession, ADA_LOVELACE)).extracting("name").containsOnly("token-to-delete");
+  }
+
+  @Test
+  public void does_not_fail_when_incorrect_login_or_name() {
+    insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName(TOKEN_NAME));
+
+    newRequest(ADA_LOVELACE, "another-token-name");
+  }
+
+  @Test
+  public void fail_if_not_logged_in() {
+    userSession.anonymous();
+    insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName(TOKEN_NAME));
+    expectedException.expect(UnauthorizedException.class);
+
+    newRequest(GRACE_HOPPER, TOKEN_NAME);
+  }
+
+  @Test
+  public void fail_if_insufficient_privileges() {
+    userSession.login().setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
+    insertUserToken(newUserToken().setLogin(GRACE_HOPPER).setName(TOKEN_NAME));
+    expectedException.expect(ForbiddenException.class);
+
+    newRequest(GRACE_HOPPER, TOKEN_NAME);
+  }
+
+  private String newRequest(String login, String name) {
+    return ws.newRequest()
+      .setParam(PARAM_LOGIN, login)
+      .setParam(PARAM_NAME, name)
+      .execute().getInput();
+  }
+
+  private void insertUserToken(UserTokenDto userToken) {
+    dbClient.userTokenDao().insert(dbSession, userToken);
+    dbSession.commit();
+  }
+}
index 4f3ad84603e9007285c7a976450e3b46d26ac8c7..b4b3b561762332e971f15498a244f5c105dec8a7 100644 (file)
@@ -45,7 +45,8 @@ public class UserTokensWsTest {
     TokenGenerator tokenGenerator = mock(TokenGenerator.class);
 
     ws = new WsTester(new UserTokensWs(
-      new GenerateAction(userSession, dbClient, system, tokenGenerator)));
+      new GenerateAction(dbClient, userSession, system, tokenGenerator),
+      new RevokeAction(dbClient, userSession)));
   }
 
   @Test
@@ -59,4 +60,15 @@ public class UserTokensWsTest {
     assertThat(action.param("login").isRequired()).isTrue();
     assertThat(action.param("name").isRequired()).isTrue();
   }
+
+  @Test
+  public void revoke_action() {
+    WebService.Action action = ws.action(CONTROLLER_KEY, "revoke");
+
+    assertThat(action).isNotNull();
+    assertThat(action.since()).isEqualTo("5.3");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.param("login").isRequired()).isTrue();
+    assertThat(action.param("name").isRequired()).isTrue();
+  }
 }
index 27cfbcea2cd379f943b7c9db36f66d65672c5c0b..d9aa3221ba96b3acf6bdff4b80b1a9cc5f7bd9a0 100644 (file)
@@ -58,6 +58,10 @@ public class UserTokenDao implements Dao {
     mapper(dbSession).deleteByLogin(login);
   }
 
+  public void deleteByLoginAndName(DbSession dbSession, String login, String name) {
+    mapper(dbSession).deleteByLoginAndName(login, name);
+  }
+
   private static UserTokenMapper mapper(DbSession dbSession) {
     return dbSession.getMapper(UserTokenMapper.class);
   }
index 42e3d95dd1795542e7bd82d495a76149588711c9..fd72e0c2dc83aa4a87943dcc81a2a0331ab41738 100644 (file)
@@ -24,7 +24,6 @@ import java.util.List;
 import org.apache.ibatis.annotations.Param;
 
 public interface UserTokenMapper {
-
   void insert(UserTokenDto userToken);
 
   UserTokenDto selectByTokenHash(String tokenHash);
@@ -34,4 +33,6 @@ public interface UserTokenMapper {
   List<UserTokenDto> selectByLogin(String login);
 
   void deleteByLogin(String login);
+
+  void deleteByLoginAndName(@Param("login") String login, @Param("name") String name);
 }
index b1bcf5d0709d3a731a20f3c039d927544ba97546..1b74df52c2a041befe3f0c20959e1d375fe47a2f 100644 (file)
@@ -39,4 +39,9 @@
   <delete id="deleteByLogin">
     DELETE FROM user_tokens WHERE login=#{login}
   </delete>
+
+  <delete id="deleteByLoginAndName">
+    DELETE FROM user_tokens WHERE login=#{login} and name=#{name}
+  </delete>
+
 </mapper>
index be50fa659fa995242bfe2b4749eb0aa80b0d7f54..a636eb8871d9c2c5db4ef620ba9f6269080cfd8d 100644 (file)
@@ -116,6 +116,20 @@ public class UserTokenDaoTest {
     assertThat(underTest.selectByLogin(dbSession, "login-to-keep")).hasSize(1);
   }
 
+  @Test
+  public void delete_token_by_login_and_name() {
+    insertToken(newUserToken().setLogin("login").setName("name"));
+    insertToken(newUserToken().setLogin("login").setName("another-name"));
+    insertToken(newUserToken().setLogin("another-login").setName("name"));
+
+    underTest.deleteByLoginAndName(dbSession, "login", "name");
+    db.commit();
+
+    Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "login", "name")).isAbsent();
+    Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "login", "another-name")).isPresent();
+    Assertions.assertThat(underTest.selectByLoginAndName(dbSession, "another-login", "name")).isPresent();
+  }
+
   private void insertToken(UserTokenDto userToken) {
     underTest.insert(dbSession, userToken);
     dbSession.commit();
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/RevokeWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/RevokeWsRequest.java
new file mode 100644 (file)
index 0000000..8b2e8fd
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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 RevokeWsRequest {
+  private String login;
+  private String name;
+
+  public String getLogin() {
+    return login;
+  }
+
+  public RevokeWsRequest setLogin(String login) {
+    this.login = login;
+    return this;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public RevokeWsRequest setName(String name) {
+    this.name = name;
+    return this;
+  }
+}
index 21e076f3594fbf7e492621be33d7efab77e87f37..3c956477f4b0442b08ce542fe9bcf87c0dc2316f 100644 (file)
@@ -25,9 +25,10 @@ import org.sonarqube.ws.client.WsClient;
 
 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.USER_TOKENS_ENDPOINT;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_REVOKE;
 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;
 
 public class UserTokensWsClient {
   private static final String SLASH = "/";
@@ -45,6 +46,13 @@ public class UserTokensWsClient {
       GenerateWsResponse.parser());
   }
 
+  public void revoke(RevokeWsRequest request) {
+    wsClient.execute(
+      newPostRequest(action(ACTION_REVOKE))
+        .setParam(PARAM_LOGIN, request.getLogin())
+        .setParam(PARAM_NAME, request.getName()));
+  }
+
   private static String action(String action) {
     return USER_TOKENS_ENDPOINT + SLASH + action;
   }
index 856fd6cdffd3ecb8f9c843ffd113126b5ac48fcf..37ddfa0099bc2cf2863f15ce8fa831077b554616 100644 (file)
@@ -23,6 +23,7 @@ package org.sonarqube.ws.client.usertoken;
 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 PARAM_LOGIN = "login";
   public static final String PARAM_NAME = "name";