From 7a26b1d96d75751ffebf9d1ea8bbfc3f12d72ca2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Mon, 4 May 2015 15:41:31 +0200 Subject: [PATCH] SONAR-6468 New WS to change a user's password --- .../server/platform/ServerComponents.java | 1 + .../server/user/ws/ChangePasswordAction.java | 79 +++++++++ .../user/ws/ChangePasswordActionTest.java | 150 ++++++++++++++++++ .../org/sonar/server/user/ws/UsersWsTest.java | 14 +- 4 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 14edd65aa0a..0fd77b02562 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -759,6 +759,7 @@ class ServerComponents { pico.addSingleton(org.sonar.server.user.ws.CreateAction.class); pico.addSingleton(org.sonar.server.user.ws.UpdateAction.class); pico.addSingleton(org.sonar.server.user.ws.DeactivateAction.class); + pico.addSingleton(org.sonar.server.user.ws.ChangePasswordAction.class); pico.addSingleton(org.sonar.server.user.ws.CurrentUserAction.class); pico.addSingleton(org.sonar.server.user.ws.SearchAction.class); pico.addSingleton(org.sonar.server.issue.ws.AuthorsAction.class); diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java new file mode 100644 index 00000000000..14cbf2edd0a --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/ChangePasswordAction.java @@ -0,0 +1,79 @@ +/* + * 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.user.ws; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.server.user.UpdateUser; +import org.sonar.server.user.UserSession; +import org.sonar.server.user.UserUpdater; + +public class ChangePasswordAction implements BaseUsersWsAction { + + private static final String PARAM_LOGIN = "login"; + private static final String PARAM_PASSWORD = "password"; + private static final String PARAM_PASSWORD_CONFIRMATION = "password_confirmation"; + + private final UserUpdater userUpdater; + + public ChangePasswordAction(UserUpdater userUpdater) { + this.userUpdater = userUpdater; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("change_password") + .setDescription("Update a user's password. Requires Administer System permission.") + .setSince("5.2") + .setPost(true) + .setHandler(this); + + action.createParam(PARAM_LOGIN) + .setDescription("User login") + .setRequired(true) + .setExampleValue("myuser"); + + action.createParam(PARAM_PASSWORD) + .setDescription("New password") + .setRequired(true) + .setExampleValue("mypassword"); + + action.createParam(PARAM_PASSWORD_CONFIRMATION) + .setDescription("Must be the same value as \"password\"") + .setRequired(true) + .setExampleValue("mypassword"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + UserSession.get().checkLoggedIn().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN); + + String login = request.mandatoryParam(PARAM_LOGIN); + UpdateUser updateUser = UpdateUser.create(login) + .setPassword(request.mandatoryParam(PARAM_PASSWORD)) + .setPasswordConfirmation(request.mandatoryParam(PARAM_PASSWORD_CONFIRMATION)); + + userUpdater.update(updateUser); + response.noContent(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java new file mode 100644 index 00000000000..037783c1493 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/ChangePasswordActionTest.java @@ -0,0 +1,150 @@ +/* + * 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.user.ws; + +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.sonar.api.config.Settings; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.core.permission.GlobalPermissions; +import org.sonar.core.persistence.DbSession; +import org.sonar.core.persistence.DbTester; +import org.sonar.core.user.GroupDto; +import org.sonar.core.user.UserDto; +import org.sonar.server.db.DbClient; +import org.sonar.server.es.EsTester; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.MockUserSession; +import org.sonar.server.user.NewUserNotifier; +import org.sonar.server.user.UserUpdater; +import org.sonar.server.user.db.GroupDao; +import org.sonar.server.user.db.UserDao; +import org.sonar.server.user.db.UserGroupDao; +import org.sonar.server.user.index.UserIndex; +import org.sonar.server.user.index.UserIndexDefinition; +import org.sonar.server.user.index.UserIndexer; +import org.sonar.server.ws.WsTester; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ChangePasswordActionTest { + + static final Settings settings = new Settings().setProperty("sonar.defaultGroup", "sonar-users"); + + @ClassRule + public static final DbTester dbTester = new DbTester(); + + @ClassRule + public static final EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(settings)); + + WebService.Controller controller; + + WsTester tester; + + UserIndex index; + + DbClient dbClient; + + UserIndexer userIndexer; + + DbSession session; + + @Before + public void setUp() throws Exception { + dbTester.truncateTables(); + esTester.truncateIndices(); + + System2 system2 = new System2(); + UserDao userDao = new UserDao(dbTester.myBatis(), system2); + UserGroupDao userGroupDao = new UserGroupDao(); + GroupDao groupDao = new GroupDao(); + dbClient = new DbClient(dbTester.database(), dbTester.myBatis(), userDao, userGroupDao, groupDao); + session = dbClient.openSession(false); + groupDao.insert(session, new GroupDto().setName("sonar-users")); + session.commit(); + + userIndexer = (UserIndexer) new UserIndexer(dbClient, esTester.client()).setEnabled(true); + index = new UserIndex(esTester.client()); + tester = new WsTester(new UsersWs(new ChangePasswordAction(new UserUpdater(mock(NewUserNotifier.class), settings, dbClient, userIndexer, system2)))); + controller = tester.controller("api/users"); + + MockUserSession.set().setLogin("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN); + } + + @After + public void tearDown() throws Exception { + session.close(); + } + + @Test(expected = ForbiddenException.class) + public void fail_on_missing_permission() throws Exception { + createUser(); + + MockUserSession.set().setLogin("polop"); + tester.newPostRequest("api/users", "change_password") + .setParam("login", "john") + .execute(); + } + + @Test(expected = NotFoundException.class) + public void fail_on_unknown_user() throws Exception { + tester.newPostRequest("api/users", "change_password") + .setParam("login", "polop") + .setParam("password", "polop") + .setParam("password_confirmation", "polop") + .execute(); + } + + @Test + public void update_password() throws Exception { + createUser(); + session.clearCache(); + String originalPassword = dbClient.userDao().selectByLogin(session, "john").getCryptedPassword(); + + tester.newPostRequest("api/users", "change_password") + .setParam("login", "john") + .setParam("password", "Valar Morghulis") + .setParam("password_confirmation", "Valar Morghulis") + .execute() + .assertNoContent(); + + session.clearCache(); + String newPassword = dbClient.userDao().selectByLogin(session, "john").getCryptedPassword(); + assertThat(newPassword).isNotEqualTo(originalPassword); + } + + private void createUser() { + dbClient.userDao().insert(session, new UserDto() + .setEmail("john@email.com") + .setLogin("john") + .setName("John") + .setScmAccounts(newArrayList("jn")) + .setActive(true)); + session.commit(); + userIndexer.index(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java index 3ab08ea313e..73d65418c7e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsTest.java @@ -42,7 +42,9 @@ public class UsersWsTest { new CreateAction(mock(UserIndex.class), mock(UserUpdater.class), mock(I18n.class)), new UpdateAction(mock(UserIndex.class), mock(UserUpdater.class)), new CurrentUserAction(), - new DeactivateAction(mock(UserIndex.class), mock(UserUpdater.class)))); + new DeactivateAction(mock(UserIndex.class), mock(UserUpdater.class)), + new ChangePasswordAction(mock(UserUpdater.class)), + new CurrentUserAction())); controller = tester.controller("api/users"); } @@ -51,7 +53,7 @@ public class UsersWsTest { assertThat(controller).isNotNull(); assertThat(controller.description()).isNotEmpty(); assertThat(controller.since()).isEqualTo("3.6"); - assertThat(controller.actions()).hasSize(5); + assertThat(controller.actions()).hasSize(7); } @Test @@ -80,6 +82,14 @@ public class UsersWsTest { assertThat(action.params()).hasSize(4); } + @Test + public void define_change_password_action() throws Exception { + WebService.Action action = controller.action("change_password"); + assertThat(action).isNotNull(); + assertThat(action.isPost()).isTrue(); + assertThat(action.params()).hasSize(3); + } + @Test public void define_deactivate_action() throws Exception { WebService.Action action = controller.action("deactivate"); -- 2.39.5