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;
UserIndexer.class,
UserIndex.class,
UserUpdater.class,
+ UserTokenModule.class,
// groups
GroupMembershipService.class,
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();
}
import java.util.List;
import java.util.Map;
+import static java.lang.String.format;
+
@ServerSide
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;
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken;
+
+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);
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken;
+
+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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken;
+
+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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.usertoken;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken.ws;
+
+import com.google.common.base.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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken.ws;
+
+import 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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken.ws;
+
+import org.sonar.server.ws.WsAction;
+
+public interface UserTokensWsAction extends WsAction {
+ // marker interface
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonar.server.usertoken.ws;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+{
+ "login": "grace.hopper",
+ "name": "Third Party Application",
+ "token": "123456789"
+}
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;
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;
@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);
.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.");
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;
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;
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 {
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)
createUser();
userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
- tester.newPostRequest("api/users", "deactivate")
+ ws.newPostRequest("api/users", "deactivate")
.setParam("login", "admin")
.execute();
}
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();
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken;
+
+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]+");
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken;
+
+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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken.ws;
+
+import java.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);
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.usertoken.ws;
+
+import 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();
+ }
+}
--- /dev/null
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2014 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# SonarQube is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 3 of the License, or (at your option) any later version.
+#
+# SonarQube is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#
+# 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
+
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(
SnapshotDao.class,
UserDao.class,
UserGroupDao.class,
+ UserTokenDao.class,
WidgetDao.class,
WidgetPropertyDao.class).build();
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 {
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;
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);
return userGroupDao;
}
+ public UserTokenDao userTokenDao() {
+ return userTokenDao;
+ }
+
public GroupMembershipDao groupMembershipDao() {
return groupMembershipDao;
}
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;
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");
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,
* 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
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.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;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.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);
+}
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
"snapshots",
"users",
"user_roles",
+ "user_tokens",
"widgets",
"widget_properties"
);
--- /dev/null
+<?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>
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;
"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
-- ----------------------------------------------
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");
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);
}
}
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;
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() {
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();
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();
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.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;
+ }
+}
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));
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.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();
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.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());
+ }
+}
@ExtensionPoint
public interface RequestHandler {
- void handle(Request wsRequest, Response wsResponse) throws Exception;
+ void handle(Request request, Response response) throws Exception;
}
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);
}
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;
private final ComponentsWsClient componentsWsClient;
private final QualityProfilesWsClient qualityProfilesWsClient;
private final IssuesWsClient issuesWsClient;
+ private final UserTokensWsClient userTokensWsClient;
public WsClient(WsConnector wsConnector) {
this.wsConnector = wsConnector;
this.componentsWsClient = new ComponentsWsClient(this);
this.qualityProfilesWsClient = new QualityProfilesWsClient(this);
this.issuesWsClient = new IssuesWsClient(this);
+ userTokensWsClient = new UserTokensWsClient(this);
}
public String execute(WsRequest wsRequest) {
public IssuesWsClient issuesWsClient() {
return issuesWsClient;
}
+
+ public UserTokensWsClient userTokensWsClient() {
+ return userTokensWsClient;
+ }
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.usertoken;
+
+public class 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;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.usertoken;
+
+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;
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.usertoken;
+
+public class 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
+ }
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+@ParametersAreNonnullByDefault
+package org.sonarqube.ws.client.usertoken;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
--- /dev/null
+// 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;
+}