]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7039 Generate user tokens 647/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 23 Nov 2015 08:04:16 +0000 (09:04 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 23 Nov 2015 16:26:29 +0000 (17:26 +0100)
46 files changed:
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java
server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java
server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/generate-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java [new file with mode: 0644]
server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587 [new file with mode: 0644]
server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1000_create_user_tokens.rb [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/DaoModule.java
sonar-db/src/main/java/org/sonar/db/DbClient.java
sonar-db/src/main/java/org/sonar/db/MyBatis.java
sonar-db/src/main/java/org/sonar/db/user/UserDao.java
sonar-db/src/main/java/org/sonar/db/user/UserTokenDao.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/user/UserTokenDto.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/user/UserTokenMapper.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java
sonar-db/src/main/resources/org/sonar/db/user/UserTokenMapper.xml [new file with mode: 0644]
sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql
sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl
sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java
sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
sonar-db/src/test/java/org/sonar/db/user/UserDbTester.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/user/UserTesting.java
sonar-db/src/test/java/org/sonar/db/user/UserTokenDaoTest.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/user/UserTokenTesting.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java
sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/package-info.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-user_tokens.proto [new file with mode: 0644]

index 9b6869750ee2b49a1808d92600410ed01692b2fb..dd41ae98635f3812950c4502eeedff0e262e16cf 100644 (file)
@@ -304,6 +304,7 @@ import org.sonar.server.user.ws.UserJsonWriter;
 import org.sonar.server.user.ws.UserPropertiesWs;
 import org.sonar.server.user.ws.UsersWs;
 import org.sonar.server.usergroups.ws.UserGroupsModule;
+import org.sonar.server.usertoken.UserTokenModule;
 import org.sonar.server.util.TypeValidationModule;
 import org.sonar.server.view.bridge.ViewsBootstrap;
 import org.sonar.server.view.bridge.ViewsStopper;
@@ -543,6 +544,7 @@ public class PlatformLevel4 extends PlatformLevel {
       UserIndexer.class,
       UserIndex.class,
       UserUpdater.class,
+      UserTokenModule.class,
 
       // groups
       GroupMembershipService.class,
index 5b62331ab6771c78fe585f3ea8c5ab5097a12d17..39dc813d935e7867f9fc3235dfb36900bf6273b1 100644 (file)
@@ -133,12 +133,18 @@ public class UserUpdater {
       notifyNewUser(user.getLogin(), user.getName(), user.getEmail());
       userIndexer.index();
     } finally {
-      dbSession.close();
+      dbClient.closeSession(dbSession);
     }
   }
 
   public void deactivateUserByLogin(String login) {
-    dbClient.userDao().deactivateUserByLogin(login);
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      dbClient.userTokenDao().deleteByLogin(dbSession, login);
+      dbClient.userDao().deactivateUserByLogin(dbSession, login);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
     userIndexer.index();
   }
 
index 2f032ba3c428e603e30c0d8d30bbbda58af14151..04a57149ef676d4e49b164fe8361e9f7750ccca7 100644 (file)
@@ -52,6 +52,8 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import static java.lang.String.format;
+
 @ServerSide
 public class UserIndex {
 
@@ -100,7 +102,7 @@ public class UserIndex {
   public UserDoc getByLogin(String login) {
     UserDoc userDoc = getNullableByLogin(login);
     if (userDoc == null) {
-      throw new NotFoundException(String.format("User '%s' not found", login));
+      throw new NotFoundException(format("User '%s' not found", login));
     }
     return userDoc;
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java
new file mode 100644 (file)
index 0000000..46d7078
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+public interface TokenGenerator {
+  /**
+   * Generate a token. It must be unique and non deterministic.<br />
+   * Underlying algorithm, format and max length are
+   * subject to change in subsequent SonarQube versions.
+   * <br/>
+   * Length does not exceed 40 characters (arbitrary value).
+   */
+  String generate();
+
+  /**
+   * Hash a token.<br/>
+   * Underlying algorithm, format and max length are
+   * subject to change in subsequent SonarQube versions.
+   * <br />
+   * Length must not exceed 255 characters.
+   */
+  String hash(String token);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java
new file mode 100644 (file)
index 0000000..9e7f6f9
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import java.security.SecureRandom;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+
+public class TokenGeneratorImpl implements TokenGenerator {
+  @Override
+  public String generate() {
+    SecureRandom random = new SecureRandom();
+    byte[] bytes = new byte[20];
+    random.nextBytes(bytes);
+    return Hex.encodeHexString(bytes);
+  }
+
+  @Override
+  public String hash(String token) {
+    return DigestUtils.sha384Hex(token);
+  }
+}
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
new file mode 100644 (file)
index 0000000..373264f
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.usertoken.ws.GenerateAction;
+import org.sonar.server.usertoken.ws.UserTokensWs;
+
+public class UserTokenModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      UserTokensWs.class,
+      GenerateAction.class,
+      TokenGeneratorImpl.class);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java
new file mode 100644 (file)
index 0000000..6807a69
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.usertoken;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java
new file mode 100644 (file)
index 0000000..7ed5991
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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.Optional;
+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.utils.System2;
+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.exceptions.ServerException;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.usertoken.TokenGenerator;
+import org.sonarqube.ws.WsUserTokens;
+import org.sonarqube.ws.WsUserTokens.GenerateWsResponse;
+import org.sonarqube.ws.client.usertoken.GenerateWsRequest;
+
+import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
+import static org.sonar.server.ws.WsUtils.checkFound;
+import static org.sonar.server.ws.WsUtils.checkRequest;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.ACTION_GENERATE;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_LOGIN;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME;
+
+public class GenerateAction implements UserTokensWsAction {
+  private final DbClient dbClient;
+  private final UserSession userSession;
+  private final System2 system;
+  private final TokenGenerator tokenGenerator;
+
+  public GenerateAction(UserSession userSession, DbClient dbClient, System2 system, TokenGenerator tokenGenerator) {
+    this.userSession = userSession;
+    this.dbClient = dbClient;
+    this.system = system;
+    this.tokenGenerator = tokenGenerator;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction(ACTION_GENERATE)
+      .setSince("5.3")
+      .setPost(true)
+      .setDescription("Generate a user access token. <br />" +
+        "Please keep your tokens secret. They enable to authenticate and analyze projects.")
+      .setResponseExample(getClass().getResource("generate-example.json"))
+      .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 {
+    WsUserTokens.GenerateWsResponse generateWsResponse = doHandle(toCreateWsRequest(request));
+    writeProtobuf(generateWsResponse, request, response);
+  }
+
+  private WsUserTokens.GenerateWsResponse doHandle(GenerateWsRequest request) {
+    userSession.checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      checkWsRequest(dbSession, request);
+
+      String token = tokenGenerator.generate();
+      String tokenHash = hashToken(dbSession, token);
+
+      UserTokenDto userTokenDto = insertTokenInDb(dbSession, request, tokenHash);
+
+      return buildResponse(userTokenDto, token);
+    } finally {
+      dbClient.closeSession(dbSession);
+    }
+  }
+
+  private String hashToken(DbSession dbSession, String token) {
+    String tokenHash = tokenGenerator.hash(token);
+    Optional<UserTokenDto> userToken = dbClient.userTokenDao().selectByTokenHash(dbSession, tokenHash);
+    if (userToken.isPresent()) {
+      throw new ServerException(HTTP_INTERNAL_ERROR, "Error while generating token. Please try again.");
+    }
+
+    return tokenHash;
+  }
+
+  private void checkWsRequest(DbSession dbSession, GenerateWsRequest request) {
+    checkLoginExists(dbSession, request);
+
+    Optional<UserTokenDto> userTokenDto = dbClient.userTokenDao().selectByLoginAndName(dbSession, request.getLogin(), request.getName());
+    checkRequest(!userTokenDto.isPresent(), "A user token with login '%s' and name '%s' already exists", request.getLogin(), request.getName());
+  }
+
+  private void checkLoginExists(DbSession dbSession, GenerateWsRequest request) {
+    checkFound(dbClient.userDao().selectByLogin(dbSession, request.getLogin()), "User with login '%s' not found", request.getLogin());
+  }
+
+  private UserTokenDto insertTokenInDb(DbSession dbSession, GenerateWsRequest request, String tokenHash) {
+    UserTokenDto userTokenDto = new UserTokenDto()
+      .setLogin(request.getLogin())
+      .setName(request.getName())
+      .setTokenHash(tokenHash)
+      .setCreatedAt(system.now());
+
+    dbClient.userTokenDao().insert(dbSession, userTokenDto);
+    dbSession.commit();
+    return userTokenDto;
+  }
+
+  private static GenerateWsRequest toCreateWsRequest(Request request) {
+    return new GenerateWsRequest()
+      .setLogin(request.mandatoryParam(PARAM_LOGIN))
+      .setName(request.mandatoryParam(PARAM_NAME));
+  }
+
+  private static GenerateWsResponse buildResponse(UserTokenDto userTokenDto, String token) {
+    return WsUserTokens.GenerateWsResponse.newBuilder()
+      .setLogin(userTokenDto.getLogin())
+      .setName(userTokenDto.getName())
+      .setToken(token)
+      .build();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java
new file mode 100644 (file)
index 0000000..cc6aabc
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.WebService;
+
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.USER_TOKENS_ENDPOINT;
+
+public class UserTokensWs implements WebService {
+  private final UserTokensWsAction[] actions;
+
+  public UserTokensWs(UserTokensWsAction... actions) {
+    this.actions = actions;
+  }
+
+  @Override
+  public void define(Context context) {
+    NewController controller = context.createController(USER_TOKENS_ENDPOINT)
+      .setDescription("User token management. To enhance security, tokens can be used to take the place " +
+        "of user credentials in analysis configuration. A token can be revoked at any time.")
+      .setSince("5.3");
+
+    for (UserTokensWsAction action : actions) {
+      action.define(controller);
+    }
+
+    controller.done();
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsAction.java
new file mode 100644 (file)
index 0000000..c6b0529
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * 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.server.ws.WsAction;
+
+public interface UserTokensWsAction extends WsAction {
+  // marker interface
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/package-info.java
new file mode 100644 (file)
index 0000000..6d0b423
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.usertoken.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/generate-example.json b/server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/generate-example.json
new file mode 100644 (file)
index 0000000..16d5236
--- /dev/null
@@ -0,0 +1,5 @@
+{
+  "login": "grace.hopper",
+  "name": "Third Party Application",
+  "token": "123456789"
+}
index 22898e5c06e06f621915b5db485daafedfb8416d..70fc04756c96c3912c82627498ce6c7262462f69 100644 (file)
@@ -23,7 +23,6 @@ package org.sonar.server.user;
 import com.google.common.base.Strings;
 import java.util.List;
 import org.elasticsearch.search.SearchHit;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
@@ -43,6 +42,7 @@ import org.sonar.db.user.GroupMembershipQuery;
 import org.sonar.db.user.UserDao;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserGroupDao;
+import org.sonar.db.user.UserTokenDao;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
@@ -90,27 +90,21 @@ public class UserUpdaterTest {
 
   @Before
   public void setUp() {
-    db.truncateTables();
     es.truncateIndices();
     settings = new Settings();
-    session = db.myBatis().openSession(false);
+    session = db.getSession();
     userDao = new UserDao(db.myBatis(), system2);
     groupDao = new GroupDao(system2);
     UserGroupDao userGroupDao = new UserGroupDao();
     GroupMembershipDao groupMembershipDao = new GroupMembershipDao(db.myBatis());
     groupMembershipFinder = new GroupMembershipFinder(userDao, groupMembershipDao);
 
-    DbClient dbClient = new DbClient(db.database(), db.myBatis(), userDao, groupDao, userGroupDao);
+    DbClient dbClient = new DbClient(db.database(), db.myBatis(), userDao, groupDao, userGroupDao, new UserTokenDao());
     userIndexer = (UserIndexer) new UserIndexer(dbClient, es.client()).setEnabled(true);
     userUpdater = new UserUpdater(newUserNotifier, settings, dbClient,
       userIndexer, system2, realmFactory);
   }
 
-  @After
-  public void tearDown() {
-    session.close();
-  }
-
   @Test
   public void create_user() {
     when(system2.now()).thenReturn(1418215735482L);
index 77fa6c2169b02ab16a6021725ec92a21a9cd4f9d..be76825575551ce378f9f2ccce3c366a6aaefbf7 100644 (file)
@@ -161,7 +161,7 @@ public class CreateActionTest {
       .setName("John")
       .setActive(true));
     session.commit();
-    dbClient.userDao().deactivateUserByLogin("john");
+    dbClient.userDao().deactivateUserByLogin(session, "john");
     userIndexer.index();
 
     when(i18n.message(Locale.FRENCH, "user.reactivated", "user.reactivated", "john")).thenReturn("The user 'john' has been reactivated.");
index 12fcc9b7230ab362cf21703008df5b59e82158d7..4afc6b111233b90615fd2eb818b5e5713d1feada 100644 (file)
 
 package org.sonar.server.user.ws;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.sonar.api.config.Settings;
-import org.sonar.api.i18n.I18n;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.user.GroupMembershipDao;
+import org.sonar.db.user.UserDao;
 import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTokenDao;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.BadRequestException;
@@ -44,7 +44,6 @@ import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.user.NewUserNotifier;
 import org.sonar.server.user.SecurityRealmFactory;
 import org.sonar.server.user.UserUpdater;
-import org.sonar.db.user.UserDao;
 import org.sonar.server.user.index.UserDoc;
 import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.user.index.UserIndexDefinition;
@@ -54,6 +53,7 @@ import org.sonar.test.DbTests;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.sonar.db.user.UserTokenTesting.newUserToken;
 
 @Category(DbTests.class)
 public class DeactivateActionTest {
@@ -61,63 +61,54 @@ public class DeactivateActionTest {
   static final Settings settings = new Settings();
 
   @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  public DbTester db = DbTester.create(System2.INSTANCE);
   @ClassRule
   public static final EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(settings));
   @Rule
   public UserSessionRule userSessionRule = UserSessionRule.standalone();
 
   WebService.Controller controller;
-
-  WsTester tester;
-
+  WsTester ws;
   UserIndex index;
-
   DbClient dbClient;
-
   UserIndexer userIndexer;
 
-  DbSession session;
-
-  I18n i18n = mock(I18n.class);
+  DbSession dbSession;
 
   @Before
   public void setUp() {
-    dbTester.truncateTables();
     esTester.truncateIndices();
 
     System2 system2 = new System2();
-    UserDao userDao = new UserDao(dbTester.myBatis(), system2);
-    dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), userDao, new GroupMembershipDao(dbTester.myBatis()));
-    session = dbClient.openSession(false);
-    session.commit();
+    UserDao userDao = new UserDao(db.myBatis(), system2);
+    dbClient = new DbClient(db.database(), db.myBatis(), userDao, new GroupMembershipDao(db.myBatis()), new UserTokenDao());
+    dbSession = db.getSession();
+    dbSession.commit();
 
     userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true);
     index = new UserIndex(esTester.client());
-    tester = new WsTester(new UsersWs(new DeactivateAction(index,
+    ws = new WsTester(new UsersWs(new DeactivateAction(index,
       new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2, mock(SecurityRealmFactory.class)), userSessionRule,
       new UserJsonWriter(userSessionRule), dbClient)));
-    controller = tester.controller("api/users");
-
-  }
+    controller = ws.controller("api/users");
 
-  @After
-  public void tearDown() {
-    session.close();
   }
 
   @Test
   public void deactivate_user() throws Exception {
     createUser();
+    assertThat(dbClient.userTokenDao().selectByLogin(dbSession, "john")).isNotEmpty();
+    db.commit();
 
     userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
-    tester.newPostRequest("api/users", "deactivate")
+    ws.newPostRequest("api/users", "deactivate")
       .setParam("login", "john")
       .execute()
       .assertJson(getClass(), "deactivate_user.json");
 
     UserDoc user = index.getByLogin("john");
     assertThat(user.active()).isFalse();
+    assertThat(dbClient.userTokenDao().selectByLogin(dbSession, "john")).isEmpty();
   }
 
   @Test(expected = BadRequestException.class)
@@ -125,7 +116,7 @@ public class DeactivateActionTest {
     createUser();
 
     userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
-    tester.newPostRequest("api/users", "deactivate")
+    ws.newPostRequest("api/users", "deactivate")
       .setParam("login", "admin")
       .execute();
   }
@@ -135,25 +126,26 @@ public class DeactivateActionTest {
     createUser();
 
     userSessionRule.login("not_admin");
-    tester.newPostRequest("api/users", "deactivate")
+    ws.newPostRequest("api/users", "deactivate")
       .setParam("login", "john").execute();
   }
 
   @Test(expected = NotFoundException.class)
   public void fail_on_unknown_user() throws Exception {
     userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
-    tester.newPostRequest("api/users", "deactivate")
+    ws.newPostRequest("api/users", "deactivate")
       .setParam("login", "john").execute();
   }
 
   private void createUser() {
-    dbClient.userDao().insert(session, new UserDto()
+    dbClient.userDao().insert(dbSession, new UserDto()
       .setActive(true)
       .setEmail("john@email.com")
       .setLogin("john")
       .setName("John")
       .setScmAccounts("jn"));
-    session.commit();
+    dbClient.userTokenDao().insert(dbSession, newUserToken().setLogin("john"));
+    dbSession.commit();
     userIndexer.index();
   }
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java
new file mode 100644 (file)
index 0000000..bce967c
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TokenGeneratorImplTest {
+  TokenGeneratorImpl underTest = new TokenGeneratorImpl();
+
+  @Test
+  public void generate_different_tokens() {
+    // this test is not enough to ensure that generated strings are unique,
+    // but it still does a simple and stupid verification
+    String firstToken = underTest.generate();
+    String secondToken = underTest.generate();
+
+    assertThat(firstToken)
+      .isNotEqualTo(secondToken)
+      .hasSize(40);
+  }
+
+  @Test
+  public void hash_token() {
+    String hash = underTest.hash("1234567890123456789012345678901234567890");
+
+    assertThat(hash)
+      .hasSize(96)
+      .isEqualTo("b2501fc3833ae6feba7dc8a973a22d709b7c796ee97cbf66db2c22df873a9fa147b1b630878f771457b7769efd9ffa0d")
+      .matches("[0-9a-f]+");
+  }
+}
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
new file mode 100644 (file)
index 0000000..91b9de1
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UserTokenModuleTest {
+  @Test
+  public void verify_count_of_added_components() {
+    ComponentContainer container = new ComponentContainer();
+    new UserTokenModule().configure(container);
+    assertThat(container.size()).isEqualTo(5);
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java
new file mode 100644 (file)
index 0000000..aad9cc1
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDbTester;
+import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.ServerException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.usertoken.TokenGenerator;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.WsUserTokens.GenerateWsResponse;
+
+import static com.google.common.base.Throwables.propagate;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+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;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME;
+
+public class GenerateActionTest {
+  private static final String GRACE_HOPPER = "grace.hopper";
+  private static final String ADA_LOVELACE = "ada.lovelace";
+  private static final String TOKEN_NAME = "Third Party Application";
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  UserDbTester userDb = new UserDbTester(db);
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  TokenGenerator tokenGenerator = mock(TokenGenerator.class);
+  WsActionTester ws;
+
+  @Before
+  public void setUp() {
+    when(tokenGenerator.generate()).thenReturn("123456789");
+    when(tokenGenerator.hash(anyString())).thenReturn("987654321");
+    userSession
+      .login()
+      .setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    userDb.insertUser(newUserDto().setLogin(GRACE_HOPPER));
+    userDb.insertUser(newUserDto().setLogin(ADA_LOVELACE));
+
+    ws = new WsActionTester(
+      new GenerateAction(userSession, db.getDbClient(), System2.INSTANCE, tokenGenerator));
+  }
+
+  @Test
+  public void return_json_example() {
+    String response = ws.newRequest()
+      .setMediaType(MediaTypes.JSON)
+      .setParam(PARAM_LOGIN, GRACE_HOPPER)
+      .setParam(PARAM_NAME, TOKEN_NAME)
+      .execute().getInput();
+
+    assertJson(response).isSimilarTo(getClass().getResource("generate-example.json"));
+  }
+
+  @Test
+  public void fail_if_login_does_not_exist() {
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("User with login 'unknown-login' not found");
+
+    newRequest("unknown-login", "any-name");
+  }
+
+  @Test
+  public void fail_if_token_with_same_login_and_name_exists() {
+    newRequest(GRACE_HOPPER, TOKEN_NAME);
+    expectedException.expect(BadRequestException.class);
+    expectedException.expectMessage("A user token with login 'grace.hopper' and name 'Third Party Application' already exists");
+
+    newRequest(GRACE_HOPPER, TOKEN_NAME);
+  }
+
+  @Test
+  public void fail_if_token_hash_already_exists_in_db() {
+    when(tokenGenerator.hash(anyString())).thenReturn("987654321");
+    db.getDbClient().userTokenDao().insert(db.getSession(), newUserToken().setTokenHash("987654321"));
+    db.commit();
+    expectedException.expect(ServerException.class);
+    expectedException.expectMessage("Error while generating token. Please try again.");
+
+    newRequest(GRACE_HOPPER, TOKEN_NAME);
+  }
+
+  private GenerateWsResponse newRequest(String login, String name) {
+    InputStream responseStream = ws.newRequest()
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam(PARAM_LOGIN, login)
+      .setParam(PARAM_NAME, name)
+      .execute().getInputStream();
+
+    try {
+      return GenerateWsResponse.parseFrom(responseStream);
+    } catch (IOException e) {
+      throw propagate(e);
+    }
+  }
+}
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
new file mode 100644 (file)
index 0000000..4f3ad84
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.usertoken.TokenGenerator;
+import org.sonar.server.ws.WsTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class UserTokensWsTest {
+  static final String CONTROLLER_KEY = "api/user_tokens";
+
+  WsTester ws;
+
+  @Before
+  public void setUp() {
+    UserSession userSession = mock(UserSession.class);
+    DbClient dbClient = mock(DbClient.class);
+    System2 system = mock(System2.class);
+    TokenGenerator tokenGenerator = mock(TokenGenerator.class);
+
+    ws = new WsTester(new UserTokensWs(
+      new GenerateAction(userSession, dbClient, system, tokenGenerator)));
+  }
+
+  @Test
+  public void generate_action() {
+    WebService.Action action = ws.action(CONTROLLER_KEY, "generate");
+
+    assertThat(action).isNotNull();
+    assertThat(action.since()).isEqualTo("5.3");
+    assertThat(action.responseExampleAsString()).isNotEmpty();
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.param("login").isRequired()).isTrue();
+    assertThat(action.param("name").isRequired()).isTrue();
+  }
+}
diff --git a/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587 b/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1000_create_user_tokens.rb b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1000_create_user_tokens.rb
new file mode 100644 (file)
index 0000000..6eb27e6
--- /dev/null
@@ -0,0 +1,38 @@
+#
+# 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.
+#
+#
+# SonarQube 5.3
+# SONAR-7039
+#
+class CreateUserTokens < ActiveRecord::Migration
+
+  def self.up
+    create_table 'user_tokens' do |t|
+      t.column 'login', :string, :limit => 255, :null => false
+      t.column 'name', :string, :limit => 255, :null => false
+      t.column 'token_hash', :string, :limit => 255, :null => false
+      t.column 'created_at', :big_integer, :null => false
+    end
+    add_index 'user_tokens', 'token_hash', :name => 'user_tokens_token_hash', :unique => true
+    add_index 'user_tokens', ['login','name'], :name => 'user_tokens_login_name', :unique => true
+  end
+
+end
+
index 38536bf226b9d431b450be0bffbbafb6d97c70c7..38554e038bd3e377d82c4572963d48b12fb500bb 100644 (file)
@@ -68,6 +68,7 @@ import org.sonar.db.user.GroupMembershipDao;
 import org.sonar.db.user.RoleDao;
 import org.sonar.db.user.UserDao;
 import org.sonar.db.user.UserGroupDao;
+import org.sonar.db.user.UserTokenDao;
 
 public class DaoModule extends Module {
   private static final List<Class<? extends Dao>> classes = ImmutableList.<Class<? extends Dao>>builder().add(
@@ -114,6 +115,7 @@ public class DaoModule extends Module {
     SnapshotDao.class,
     UserDao.class,
     UserGroupDao.class,
+    UserTokenDao.class,
     WidgetDao.class,
     WidgetPropertyDao.class).build();
 
index 1759feb17beeb547efc6e44eba015a7457dc5792..f827492969f2002ba3720d20c71f7f5306e95488 100644 (file)
@@ -67,6 +67,7 @@ import org.sonar.db.user.GroupMembershipDao;
 import org.sonar.db.user.RoleDao;
 import org.sonar.db.user.UserDao;
 import org.sonar.db.user.UserGroupDao;
+import org.sonar.db.user.UserTokenDao;
 
 public class DbClient {
 
@@ -86,6 +87,7 @@ public class DbClient {
   private final AuthorizationDao authorizationDao;
   private final UserDao userDao;
   private final UserGroupDao userGroupDao;
+  private final UserTokenDao userTokenDao;
   private final GroupMembershipDao groupMembershipDao;
   private final RoleDao roleDao;
   private final PermissionDao permissionDao;
@@ -140,6 +142,7 @@ public class DbClient {
     authorizationDao = getDao(map, AuthorizationDao.class);
     userDao = getDao(map, UserDao.class);
     userGroupDao = getDao(map, UserGroupDao.class);
+    userTokenDao = getDao(map, UserTokenDao.class);
     groupMembershipDao = getDao(map, GroupMembershipDao.class);
     roleDao = getDao(map, RoleDao.class);
     permissionDao = getDao(map, PermissionDao.class);
@@ -263,6 +266,10 @@ public class DbClient {
     return userGroupDao;
   }
 
+  public UserTokenDao userTokenDao() {
+    return userTokenDao;
+  }
+
   public GroupMembershipDao groupMembershipDao() {
     return groupMembershipDao;
   }
index 5887b49c205e3ccdb3239377aff0c33de5f37242..9011a073e62c7cd6595dcd1b69e28b1fa9427220 100644 (file)
@@ -126,6 +126,8 @@ import org.sonar.db.user.UserGroupDto;
 import org.sonar.db.user.UserGroupMapper;
 import org.sonar.db.user.UserMapper;
 import org.sonar.db.user.UserRoleDto;
+import org.sonar.db.user.UserTokenDto;
+import org.sonar.db.user.UserTokenMapper;
 import org.sonar.db.version.SchemaMigrationDto;
 import org.sonar.db.version.SchemaMigrationMapper;
 import org.sonar.db.version.v45.Migration45Mapper;
@@ -211,6 +213,7 @@ public class MyBatis {
     confBuilder.loadAlias("Event", EventDto.class);
     confBuilder.loadAlias("CustomMeasure", CustomMeasureDto.class);
     confBuilder.loadAlias("ViewsSnapshot", ViewsSnapshotDto.class);
+    confBuilder.loadAlias("UserToken", UserTokenDto.class);
 
     // AuthorizationMapper has to be loaded before IssueMapper because this last one used it
     confBuilder.loadMapper("org.sonar.db.user.AuthorizationMapper");
@@ -224,9 +227,9 @@ public class MyBatis {
       IsAliveMapper.class,
       LoadedTemplateMapper.class, MeasureFilterMapper.class, PermissionTemplateMapper.class, PropertiesMapper.class, PurgeMapper.class,
       ResourceKeyUpdaterMapper.class, ResourceIndexMapper.class, RoleMapper.class, RuleMapper.class,
-      SchemaMigrationMapper.class, UserMapper.class, GroupMapper.class, UserGroupMapper.class, WidgetMapper.class, WidgetPropertyMapper.class,
-      FileSourceMapper.class, ActionPlanMapper.class,
-      ActionPlanStatsMapper.class,
+      SchemaMigrationMapper.class, WidgetMapper.class, WidgetPropertyMapper.class,
+      UserMapper.class, GroupMapper.class, UserGroupMapper.class, UserTokenMapper.class,
+      FileSourceMapper.class, ActionPlanMapper.class, ActionPlanStatsMapper.class,
       NotificationQueueMapper.class, CharacteristicMapper.class,
       GroupMembershipMapper.class, QualityProfileMapper.class, ActiveRuleMapper.class,
       MeasureMapper.class, MetricMapper.class, CustomMeasureMapper.class, QualityGateMapper.class, QualityGateConditionMapper.class, ComponentMapper.class, SnapshotMapper.class,
index 451554b2a4914afca66b2f66e64b926dcce8b66f..50984dd8ac4f5283dc81cbd5117b6b6ec96e03de 100644 (file)
@@ -131,31 +131,25 @@ public class UserDao implements Dao {
    * Deactivate a user and drops all his preferences.
    * @return false if the user does not exist, true if the existing user has been deactivated
    */
-  public boolean deactivateUserByLogin(String login) {
-    SqlSession session = mybatis.openSession(false);
-    try {
-      UserMapper mapper = session.getMapper(UserMapper.class);
-      UserDto dto = mapper.selectUserByLogin(login);
-      if (dto == null) {
-        return false;
-      }
-
-      mapper.removeUserFromGroups(dto.getId());
-      mapper.deleteUserActiveDashboards(dto.getId());
-      mapper.deleteUnsharedUserDashboards(dto.getId());
-      mapper.deleteUnsharedUserIssueFilters(dto.getLogin());
-      mapper.deleteUserIssueFilterFavourites(dto.getLogin());
-      mapper.deleteUnsharedUserMeasureFilters(dto.getId());
-      mapper.deleteUserMeasureFilterFavourites(dto.getId());
-      mapper.deleteUserProperties(dto.getId());
-      mapper.deleteUserRoles(dto.getId());
-      mapper.deactivateUser(dto.getId(), system2.now());
-      session.commit();
-      return true;
-
-    } finally {
-      MyBatis.closeQuietly(session);
+  public boolean deactivateUserByLogin(DbSession dbSession, String login) {
+    UserMapper mapper = dbSession.getMapper(UserMapper.class);
+    UserDto dto = mapper.selectUserByLogin(login);
+    if (dto == null) {
+      return false;
     }
+
+    mapper.removeUserFromGroups(dto.getId());
+    mapper.deleteUserActiveDashboards(dto.getId());
+    mapper.deleteUnsharedUserDashboards(dto.getId());
+    mapper.deleteUnsharedUserIssueFilters(dto.getLogin());
+    mapper.deleteUserIssueFilterFavourites(dto.getLogin());
+    mapper.deleteUnsharedUserMeasureFilters(dto.getId());
+    mapper.deleteUserMeasureFilterFavourites(dto.getId());
+    mapper.deleteUserProperties(dto.getId());
+    mapper.deleteUserRoles(dto.getId());
+    mapper.deactivateUser(dto.getId(), system2.now());
+    dbSession.commit();
+    return true;
   }
 
   @CheckForNull
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserTokenDao.java b/sonar-db/src/main/java/org/sonar/db/user/UserTokenDao.java
new file mode 100644 (file)
index 0000000..27cfbce
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.db.user;
+
+import com.google.common.base.Optional;
+import java.util.List;
+import org.sonar.db.Dao;
+import org.sonar.db.DbSession;
+import org.sonar.db.RowNotFoundException;
+
+import static java.lang.String.format;
+
+public class UserTokenDao implements Dao {
+  public void insert(DbSession dbSession, UserTokenDto userTokenDto) {
+    mapper(dbSession).insert(userTokenDto);
+  }
+
+  public UserTokenDto selectOrFailByTokenHash(DbSession dbSession, String tokenHash) {
+    UserTokenDto userToken = mapper(dbSession).selectByTokenHash(tokenHash);
+    if (userToken == null) {
+      throw new RowNotFoundException(format("User token with token hash '%s' not found", tokenHash));
+    }
+
+    return userToken;
+  }
+
+  public Optional<UserTokenDto> selectByTokenHash(DbSession dbSession, String tokenHash) {
+    return Optional.fromNullable(mapper(dbSession).selectByTokenHash(tokenHash));
+  }
+
+  public Optional<UserTokenDto> selectByLoginAndName(DbSession dbSession, String login, String name) {
+    return Optional.fromNullable(mapper(dbSession).selectByLoginAndName(login, name));
+  }
+
+  public List<UserTokenDto> selectByLogin(DbSession dbSession, String login) {
+    return mapper(dbSession).selectByLogin(login);
+  }
+
+  public void deleteByLogin(DbSession dbSession, String login) {
+    mapper(dbSession).deleteByLogin(login);
+  }
+
+  private static UserTokenMapper mapper(DbSession dbSession) {
+    return dbSession.getMapper(UserTokenMapper.class);
+  }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserTokenDto.java b/sonar-db/src/main/java/org/sonar/db/user/UserTokenDto.java
new file mode 100644 (file)
index 0000000..28d4d2a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.db.user;
+
+public class UserTokenDto {
+  private String login;
+  private String name;
+  private String tokenHash;
+  private Long createdAt;
+
+  public String getLogin() {
+    return login;
+  }
+
+  public UserTokenDto setLogin(String login) {
+    this.login = login;
+    return this;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public UserTokenDto setName(String name) {
+    this.name = name;
+    return this;
+  }
+
+  public String getTokenHash() {
+    return tokenHash;
+  }
+
+  public UserTokenDto setTokenHash(String tokenHash) {
+    this.tokenHash = tokenHash;
+    return this;
+  }
+
+  public Long getCreatedAt() {
+    return createdAt;
+  }
+
+  public UserTokenDto setCreatedAt(long createdAt) {
+    this.createdAt = createdAt;
+    return this;
+  }
+}
diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserTokenMapper.java b/sonar-db/src/main/java/org/sonar/db/user/UserTokenMapper.java
new file mode 100644 (file)
index 0000000..42e3d95
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * 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.db.user;
+
+import java.util.List;
+import org.apache.ibatis.annotations.Param;
+
+public interface UserTokenMapper {
+
+  void insert(UserTokenDto userToken);
+
+  UserTokenDto selectByTokenHash(String tokenHash);
+
+  UserTokenDto selectByLoginAndName(@Param("login") String login, @Param("name") String name);
+
+  List<UserTokenDto> selectByLogin(String login);
+
+  void deleteByLogin(String login);
+}
index a1c0b13044668e2d76f66986688ef316c05dda6d..f8d1d84e9f37c4063bc25a54790a0d47587949b3 100644 (file)
@@ -29,7 +29,7 @@ import org.sonar.db.MyBatis;
 
 public class DatabaseVersion {
 
-  public static final int LAST_VERSION = 941;
+  public static final int LAST_VERSION = 1000;
 
   /**
    * The minimum supported version which can be upgraded. Lower
@@ -88,6 +88,7 @@ public class DatabaseVersion {
     "snapshots",
     "users",
     "user_roles",
+    "user_tokens",
     "widgets",
     "widget_properties"
     );
diff --git a/sonar-db/src/main/resources/org/sonar/db/user/UserTokenMapper.xml b/sonar-db/src/main/resources/org/sonar/db/user/UserTokenMapper.xml
new file mode 100644 (file)
index 0000000..b1bcf5d
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="org.sonar.db.user.UserTokenMapper">
+
+  <sql id="userTokensColumns">
+    t.login as "login",
+    t.name as "name",
+    t.token_hash as "tokenHash",
+    t.created_at as "createdAt"
+  </sql>
+
+  <insert id="insert" parameterType="UserToken" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
+    INSERT INTO user_tokens (login, name, token_hash, created_at)
+    VALUES (#{login,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{tokenHash,jdbcType=VARCHAR}, #{createdAt,jdbcType=BIGINT})
+  </insert>
+
+  <select id="selectByTokenHash" parameterType="String" resultType="UserToken">
+    SELECT
+    <include refid="userTokensColumns"/>
+    FROM user_tokens t
+    WHERE t.token_hash=#{tokenHash}
+  </select>
+
+  <select id="selectByLoginAndName" parameterType="map" resultType="UserToken">
+    SELECT
+    <include refid="userTokensColumns"/>
+    FROM user_tokens t
+    WHERE t.login=#{login} and t.name=#{name}
+  </select>
+
+  <select id="selectByLogin" parameterType="map" resultType="UserToken">
+    SELECT
+    <include refid="userTokensColumns"/>
+    FROM user_tokens t
+    WHERE t.login=#{login}
+  </select>
+
+  <delete id="deleteByLogin">
+    DELETE FROM user_tokens WHERE login=#{login}
+  </delete>
+</mapper>
index 0671ac8a706713c6dfc0d1e4f41a51d92588b52e..25a9cb3425cac578fe9c294fd4fa93aa882d811a 100644 (file)
@@ -360,6 +360,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('938');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('939');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('940');
 INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('941');
+INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('1000');
 
 INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '1418215735482', '1418215735482', null, null);
 ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2;
index f68481ba36e503f8e8fa81a06a7e78d792aafe9a..403d58addd2f41f6682280c15f039145dc917a31 100644 (file)
@@ -551,6 +551,14 @@ CREATE TABLE "CE_ACTIVITY" (
   "EXECUTION_TIME_MS" BIGINT NULL
 );
 
+CREATE TABLE "USER_TOKENS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "LOGIN" VARCHAR(255) NOT NULL,
+  "NAME" VARCHAR(255) NOT NULL,
+  "TOKEN_HASH" VARCHAR(255) NOT NULL,
+  "CREATED_AT" BIGINT NOT NULL
+);
+
 -- ----------------------------------------------
 -- DDL Statements for indexes
 -- ----------------------------------------------
@@ -698,3 +706,7 @@ CREATE UNIQUE INDEX "CE_QUEUE_UUID" ON "CE_QUEUE" ("UUID");
 CREATE UNIQUE INDEX "CE_ACTIVITY_UUID" ON "CE_ACTIVITY" ("UUID");
 
 CREATE INDEX "CE_ACTIVITY_COMPONENT_UUID" ON "CE_ACTIVITY" ("COMPONENT_UUID");
+
+CREATE UNIQUE INDEX "USER_TOKENS_TOKEN_HASH" ON "USER_TOKENS" ("TOKEN_HASH");
+
+CREATE UNIQUE INDEX "USER_TOKENS_LOGIN_NAME" ON "USER_TOKENS" ("LOGIN", "NAME");
index 29e06e13da5ec14c2b5384bb8d81f867b3580ab9..df0f4964ef924c3e5fde9d2572190a921c89997d 100644 (file)
@@ -30,6 +30,6 @@ public class DaoModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new DaoModule().configure(container);
-    assertThat(container.size()).isEqualTo(47);
+    assertThat(container.size()).isEqualTo(48);
   }
 }
index d0fe4e7ee84d20721b5c66de06b9e5093a05efff..2502d75ff39010ad7c796ad74796b0ac33146fa5 100644 (file)
@@ -22,8 +22,6 @@ package org.sonar.db.user;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import org.junit.After;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
@@ -50,19 +48,7 @@ public class UserDaoTest {
   public DbTester db = DbTester.create(system2);
 
   UserDao underTest = db.getDbClient().userDao();
-  DbSession session;
-
-  @Before
-  public void before() {
-    db.truncateTables();
-
-    this.session = db.getSession();
-  }
-
-  @After
-  public void after() {
-    this.session.close();
-  }
+  DbSession session = db.getSession();
 
   @Test
   public void selectUserByLogin_ignore_inactive() {
@@ -233,7 +219,7 @@ public class UserDaoTest {
     when(system2.now()).thenReturn(1500000000000L);
 
     String login = "marius";
-    boolean deactivated = underTest.deactivateUserByLogin(login);
+    boolean deactivated = underTest.deactivateUserByLogin(session, login);
     assertThat(deactivated).isTrue();
 
     assertThat(underTest.selectActiveUserByLogin(login)).isNull();
@@ -253,7 +239,7 @@ public class UserDaoTest {
     db.prepareDbUnit(getClass(), "deactivate_user.xml");
 
     String login = "does_not_exist";
-    boolean deactivated = underTest.deactivateUserByLogin(login);
+    boolean deactivated = underTest.deactivateUserByLogin(session, login);
     assertThat(deactivated).isFalse();
     assertThat(underTest.selectActiveUserByLogin(login)).isNull();
   }
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserDbTester.java b/sonar-db/src/test/java/org/sonar/db/user/UserDbTester.java
new file mode 100644 (file)
index 0000000..ec25f9b
--- /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.sonar.db.user;
+
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+public class UserDbTester {
+  private final DbTester db;
+  private final DbClient dbClient;
+  private final DbSession dbSession;
+
+  public UserDbTester(DbTester db) {
+    this.db = db;
+    this.dbClient = db.getDbClient();
+    this.dbSession = db.getSession();
+  }
+
+  public UserDto insertUser(UserDto userDto) {
+    UserDto updatedUser = dbClient.userDao().insert(dbSession, userDto);
+    db.commit();
+
+    return updatedUser;
+  }
+}
index 04bb4ab02bb7248b17c991d405915949afebb9b6..77799132dad21cf27a4f1b57cdfee074c4672ff2 100644 (file)
@@ -22,14 +22,13 @@ package org.sonar.db.user;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.apache.commons.lang.math.RandomUtils.nextBoolean;
 import static org.apache.commons.lang.math.RandomUtils.nextLong;
 
 public class UserTesting {
 
   public static UserDto newUserDto() {
     UserDto user = new UserDto()
-      .setActive(nextBoolean())
+      .setActive(true)
       .setName(randomAlphanumeric(30))
       .setEmail(randomAlphabetic(30))
       .setLogin(randomAlphanumeric(30));
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserTokenDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/UserTokenDaoTest.java
new file mode 100644 (file)
index 0000000..55ea930
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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.db.user;
+
+import com.google.common.base.Optional;
+import org.assertj.guava.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.user.UserTokenTesting.newUserToken;
+
+@Category(DbTests.class)
+public class UserTokenDaoTest {
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  DbSession dbSession;
+
+  UserTokenDao underTest;
+
+  @Before
+  public void setUp() {
+    underTest = db.getDbClient().userTokenDao();
+    dbSession = db.getSession();
+  }
+
+  @Test
+  public void insert_token() {
+    UserTokenDto userToken = newUserToken();
+
+    insertToken(userToken);
+
+    UserTokenDto userTokenFromDb = underTest.selectOrFailByTokenHash(dbSession, userToken.getTokenHash());
+    assertThat(userTokenFromDb).isNotNull();
+    assertThat(userTokenFromDb.getName()).isEqualTo(userToken.getName());
+    assertThat(userTokenFromDb.getCreatedAt()).isEqualTo(userToken.getCreatedAt());
+    assertThat(userTokenFromDb.getTokenHash()).isEqualTo(userToken.getTokenHash());
+    assertThat(userTokenFromDb.getLogin()).isEqualTo(userToken.getLogin());
+  }
+
+  @Test
+  public void select_by_login_and_name() {
+    UserTokenDto userToken = newUserToken().setLogin("login").setName("name").setTokenHash("token");
+    insertToken(userToken);
+
+    Optional<UserTokenDto> optionalResultByLoginAndName = underTest.selectByLoginAndName(dbSession, userToken.getLogin(), userToken.getName());
+    UserTokenDto resultByLoginAndName = optionalResultByLoginAndName.get();
+    Optional<UserTokenDto> unfoundResult1 = underTest.selectByLoginAndName(dbSession, "unknown-login", userToken.getName());
+    Optional<UserTokenDto> unfoundResult2 = underTest.selectByLoginAndName(dbSession, userToken.getLogin(), "unknown-name");
+
+    Assertions.assertThat(unfoundResult1).isAbsent();
+    Assertions.assertThat(unfoundResult2).isAbsent();
+    assertThat(resultByLoginAndName.getLogin()).isEqualTo(userToken.getLogin());
+    assertThat(resultByLoginAndName.getName()).isEqualTo(userToken.getName());
+    assertThat(resultByLoginAndName.getCreatedAt()).isEqualTo(userToken.getCreatedAt());
+    assertThat(resultByLoginAndName.getTokenHash()).isEqualTo(userToken.getTokenHash());
+  }
+
+  @Test
+  public void delete_tokens_by_login() {
+    insertToken(newUserToken().setLogin("login-to-delete"));
+    insertToken(newUserToken().setLogin("login-to-delete"));
+    insertToken(newUserToken().setLogin("login-to-keep"));
+
+    underTest.deleteByLogin(dbSession, "login-to-delete");
+    db.commit();
+
+    assertThat(underTest.selectByLogin(dbSession, "login-to-delete")).isEmpty();
+    assertThat(underTest.selectByLogin(dbSession, "login-to-keep")).hasSize(1);
+  }
+
+  private void insertToken(UserTokenDto userToken) {
+    underTest.insert(dbSession, userToken);
+    dbSession.commit();
+  }
+}
diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserTokenTesting.java b/sonar-db/src/test/java/org/sonar/db/user/UserTokenTesting.java
new file mode 100644 (file)
index 0000000..84536ed
--- /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.sonar.db.user;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.apache.commons.lang.math.RandomUtils.nextLong;
+
+public class UserTokenTesting {
+  public static UserTokenDto newUserToken() {
+    return new UserTokenDto()
+      .setLogin(randomAlphanumeric(255))
+      .setName(randomAlphanumeric(255))
+      .setTokenHash(randomAlphanumeric(40))
+      .setCreatedAt(nextLong());
+  }
+}
index 6abc22fedcded4b3ee09389d7badf0ebe84a5c03..3ac7b9ee0ae441999fa7d7ee813c403940be9c15 100644 (file)
@@ -29,6 +29,6 @@ import org.sonar.api.server.ServerSide;
 @ExtensionPoint
 public interface RequestHandler {
 
-  void handle(Request wsRequest, Response wsResponse) throws Exception;
+  void handle(Request request, Response response) throws Exception;
 
 }
index 9abeb23dca3a5ad3bbcd9a0779c014785b507efc..d214f8d72eeac5bb343309ae55aa6295d90547eb 100644 (file)
@@ -56,6 +56,10 @@ public final class DateUtils {
     return THREAD_SAFE_DATETIME_FORMAT.format(d);
   }
 
+  public static String formatDateTime(long ms) {
+    return THREAD_SAFE_DATETIME_FORMAT.format(new Date(ms));
+  }
+
   public static String formatDateTimeNullSafe(@Nullable Date date) {
     return date == null ? "" : THREAD_SAFE_DATETIME_FORMAT.format(date);
   }
index b9822ee9872af1341856b346d17ae86aec73466f..39886b57a610e819d81b2b06de0f079616966f72 100644 (file)
@@ -27,6 +27,7 @@ import org.sonarqube.ws.client.component.ComponentsWsClient;
 import org.sonarqube.ws.client.issue.IssuesWsClient;
 import org.sonarqube.ws.client.permission.PermissionsWsClient;
 import org.sonarqube.ws.client.qualityprofile.QualityProfilesWsClient;
+import org.sonarqube.ws.client.usertoken.UserTokensWsClient;
 
 import static org.sonarqube.ws.client.WsRequest.MediaType.PROTOBUF;
 
@@ -48,6 +49,7 @@ public class WsClient {
   private final ComponentsWsClient componentsWsClient;
   private final QualityProfilesWsClient qualityProfilesWsClient;
   private final IssuesWsClient issuesWsClient;
+  private final UserTokensWsClient userTokensWsClient;
 
   public WsClient(WsConnector wsConnector) {
     this.wsConnector = wsConnector;
@@ -55,6 +57,7 @@ public class WsClient {
     this.componentsWsClient = new ComponentsWsClient(this);
     this.qualityProfilesWsClient = new QualityProfilesWsClient(this);
     this.issuesWsClient = new IssuesWsClient(this);
+    userTokensWsClient = new UserTokensWsClient(this);
   }
 
   public String execute(WsRequest wsRequest) {
@@ -80,4 +83,8 @@ public class WsClient {
   public IssuesWsClient issuesWsClient() {
     return issuesWsClient;
   }
+
+  public UserTokensWsClient userTokensWsClient() {
+    return userTokensWsClient;
+  }
 }
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java
new file mode 100644 (file)
index 0000000..c507211
--- /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 GenerateWsRequest {
+  private String login;
+  private String name;
+
+  public String getLogin() {
+    return login;
+  }
+
+  public GenerateWsRequest setLogin(String login) {
+    this.login = login;
+    return this;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public GenerateWsRequest setName(String name) {
+    this.name = name;
+    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
new file mode 100644 (file)
index 0000000..21e076f
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import org.sonarqube.ws.WsUserTokens.GenerateWsResponse;
+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.PARAM_LOGIN;
+import static org.sonarqube.ws.client.usertoken.UserTokensWsParameters.PARAM_NAME;
+
+public class UserTokensWsClient {
+  private static final String SLASH = "/";
+  private final WsClient wsClient;
+
+  public UserTokensWsClient(WsClient wsClient) {
+    this.wsClient = wsClient;
+  }
+
+  public GenerateWsResponse generate(GenerateWsRequest request) {
+    return wsClient.execute(
+      newPostRequest(action(ACTION_GENERATE))
+        .setParam(PARAM_LOGIN, request.getLogin())
+        .setParam(PARAM_NAME, request.getName()),
+      GenerateWsResponse.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
new file mode 100644 (file)
index 0000000..856fd6c
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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 UserTokensWsParameters {
+  public static final String USER_TOKENS_ENDPOINT = "api/user_tokens";
+  public static final String ACTION_GENERATE = "generate";
+
+  public static final String PARAM_LOGIN = "login";
+  public static final String PARAM_NAME = "name";
+
+  private UserTokensWsParameters() {
+    // constants only
+  }
+}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/package-info.java
new file mode 100644 (file)
index 0000000..b3b9a3e
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonarqube.ws.client.usertoken;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
diff --git a/sonar-ws/src/main/protobuf/ws-user_tokens.proto b/sonar-ws/src/main/protobuf/ws-user_tokens.proto
new file mode 100644 (file)
index 0000000..8ba07bb
--- /dev/null
@@ -0,0 +1,32 @@
+// 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.usertoken;
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "WsUserTokens";
+option optimize_for = SPEED;
+
+// WS api/user_tokens/generate
+message GenerateWsResponse {
+  optional string login = 1;
+  optional string name = 2;
+  optional string token = 3;
+}