diff options
author | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2015-11-23 09:04:16 +0100 |
---|---|---|
committer | Teryk Bellahsene <teryk.bellahsene@sonarsource.com> | 2015-11-23 17:26:29 +0100 |
commit | 7fb14c3d55ba19b85e7361577a92ee5a0f8ccefc (patch) | |
tree | 5cfe848ea35b532f0804b7db4fcc0b9c1e0be66e | |
parent | fac2525b1ba5448bbfd2efdb672d708d889e993d (diff) | |
download | sonarqube-7fb14c3d55ba19b85e7361577a92ee5a0f8ccefc.tar.gz sonarqube-7fb14c3d55ba19b85e7361577a92ee5a0f8ccefc.zip |
SONAR-7039 Generate user tokens
46 files changed, 1391 insertions, 92 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 9b6869750ee..dd41ae98635 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -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, diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java index 5b62331ab67..39dc813d935 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/UserUpdater.java @@ -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(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java b/server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java index 2f032ba3c42..04a57149ef6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/index/UserIndex.java @@ -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 index 00000000000..46d70784021 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGenerator.java @@ -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 index 00000000000..9e7f6f9118d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/TokenGeneratorImpl.java @@ -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 index 00000000000..373264f2018 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/UserTokenModule.java @@ -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 index 00000000000..6807a6908e1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/package-info.java @@ -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 index 00000000000..7ed59918eda --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/GenerateAction.java @@ -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 index 00000000000..cc6aabc3679 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWs.java @@ -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 index 00000000000..c6b0529cfa9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/UserTokensWsAction.java @@ -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 index 00000000000..6d0b42367b0 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/usertoken/ws/package-info.java @@ -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 index 00000000000..16d5236762c --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/usertoken/ws/generate-example.json @@ -0,0 +1,5 @@ +{ + "login": "grace.hopper", + "name": "Third Party Application", + "token": "123456789" +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java index 22898e5c06e..70fc04756c9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/UserUpdaterTest.java @@ -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); diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java index 77fa6c2169b..be768255755 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CreateActionTest.java @@ -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."); diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java index 12fcc9b7230..4afc6b11123 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java @@ -20,21 +20,21 @@ 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 index 00000000000..bce967c2a3a --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/TokenGeneratorImplTest.java @@ -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 index 00000000000..91b9de139d6 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/UserTokenModuleTest.java @@ -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 index 00000000000..aad9cc18cf1 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/GenerateActionTest.java @@ -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 index 00000000000..4f3ad84603e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/usertoken/ws/UserTokensWsTest.java @@ -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 index 00000000000..e69de29bb2d --- /dev/null +++ b/server/sonar-web/npm-debug.log.d92601cb81ea7493b0878a7dda333587 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 index 00000000000..6eb27e67200 --- /dev/null +++ b/server/sonar-web/src/main/webapp/WEB-INF/db/migrate/1000_create_user_tokens.rb @@ -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 + diff --git a/sonar-db/src/main/java/org/sonar/db/DaoModule.java b/sonar-db/src/main/java/org/sonar/db/DaoModule.java index 38536bf226b..38554e038bd 100644 --- a/sonar-db/src/main/java/org/sonar/db/DaoModule.java +++ b/sonar-db/src/main/java/org/sonar/db/DaoModule.java @@ -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(); diff --git a/sonar-db/src/main/java/org/sonar/db/DbClient.java b/sonar-db/src/main/java/org/sonar/db/DbClient.java index 1759feb17be..f827492969f 100644 --- a/sonar-db/src/main/java/org/sonar/db/DbClient.java +++ b/sonar-db/src/main/java/org/sonar/db/DbClient.java @@ -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; } diff --git a/sonar-db/src/main/java/org/sonar/db/MyBatis.java b/sonar-db/src/main/java/org/sonar/db/MyBatis.java index 5887b49c205..9011a073e62 100644 --- a/sonar-db/src/main/java/org/sonar/db/MyBatis.java +++ b/sonar-db/src/main/java/org/sonar/db/MyBatis.java @@ -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, diff --git a/sonar-db/src/main/java/org/sonar/db/user/UserDao.java b/sonar-db/src/main/java/org/sonar/db/user/UserDao.java index 451554b2a49..50984dd8ac4 100644 --- a/sonar-db/src/main/java/org/sonar/db/user/UserDao.java +++ b/sonar-db/src/main/java/org/sonar/db/user/UserDao.java @@ -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 index 00000000000..27cfbcea2cd --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/user/UserTokenDao.java @@ -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 index 00000000000..28d4d2a2ecc --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/user/UserTokenDto.java @@ -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 index 00000000000..42e3d95dd17 --- /dev/null +++ b/sonar-db/src/main/java/org/sonar/db/user/UserTokenMapper.java @@ -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); +} diff --git a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java index a1c0b130446..f8d1d84e9f3 100644 --- a/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java +++ b/sonar-db/src/main/java/org/sonar/db/version/DatabaseVersion.java @@ -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 index 00000000000..b1bcf5d0709 --- /dev/null +++ b/sonar-db/src/main/resources/org/sonar/db/user/UserTokenMapper.xml @@ -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> diff --git a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql index 0671ac8a706..25a9cb3425c 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql +++ b/sonar-db/src/main/resources/org/sonar/db/version/rows-h2.sql @@ -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; diff --git a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl index f68481ba36e..403d58addd2 100644 --- a/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/sonar-db/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -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"); diff --git a/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java b/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java index 29e06e13da5..df0f4964ef9 100644 --- a/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/sonar-db/src/test/java/org/sonar/db/DaoModuleTest.java @@ -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); } } diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java b/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java index d0fe4e7ee84..2502d75ff39 100644 --- a/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java +++ b/sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java @@ -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 index 00000000000..ec25f9b0821 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/user/UserDbTester.java @@ -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; + } +} diff --git a/sonar-db/src/test/java/org/sonar/db/user/UserTesting.java b/sonar-db/src/test/java/org/sonar/db/user/UserTesting.java index 04bb4ab02bb..77799132dad 100644 --- a/sonar-db/src/test/java/org/sonar/db/user/UserTesting.java +++ b/sonar-db/src/test/java/org/sonar/db/user/UserTesting.java @@ -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 index 00000000000..55ea930b442 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/user/UserTokenDaoTest.java @@ -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 index 00000000000..84536ed7265 --- /dev/null +++ b/sonar-db/src/test/java/org/sonar/db/user/UserTokenTesting.java @@ -0,0 +1,34 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.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()); + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java index 6abc22fedcd..3ac7b9ee0ae 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/RequestHandler.java @@ -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; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java index 9abeb23dca3..d214f8d72ee 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/DateUtils.java @@ -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); } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java index b9822ee9872..39886b57a61 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java @@ -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 index 00000000000..c5072110cad --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/GenerateWsRequest.java @@ -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 index 00000000000..21e076f3594 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsClient.java @@ -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 index 00000000000..856fd6cdffd --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/UserTokensWsParameters.java @@ -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 index 00000000000..b3b9a3e7648 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/usertoken/package-info.java @@ -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 index 00000000000..8ba07bbf483 --- /dev/null +++ b/sonar-ws/src/main/protobuf/ws-user_tokens.proto @@ -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; +} |