From 347f6c674430812b544478140297d80027dd6d85 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Wed, 12 Dec 2018 10:16:11 +0100 Subject: [PATCH] SONAR-11579 Create WS to store/load user settings --- .../ComputeEngineContainerImplTest.java | 4 +- .../java/org/sonar/db/version/SqTables.java | 1 + .../org/sonar/db/version/schema-h2.ddl | 12 ++ .../src/main/java/org/sonar/db/DaoModule.java | 2 + .../src/main/java/org/sonar/db/DbClient.java | 7 + .../src/main/java/org/sonar/db/MyBatis.java | 2 + .../org/sonar/db/user/UserPropertiesDao.java | 58 ++++++++ .../sonar/db/user/UserPropertiesMapper.java | 35 +++++ .../org/sonar/db/user/UserPropertyDto.java | 80 +++++++++++ .../sonar/db/user/UserPropertiesMapper.xml | 51 +++++++ .../test/java/org/sonar/db/DaoModuleTest.java | 2 +- .../java/org/sonar/db/user/UserDaoTest.java | 26 ++-- .../java/org/sonar/db/user/UserDbTester.java | 12 ++ .../sonar/db/user/UserPropertiesDaoTest.java | 127 +++++++++++++++++ .../java/org/sonar/db/user/UserTesting.java | 9 ++ .../MigrationConfigurationModule.java | 2 + .../AddUniqueIndexInUserPropertiesTable.java | 58 ++++++++ .../v76/CreateUserPropertiesTable.java | 94 +++++++++++++ .../db/migration/version/v76/DbVersion76.java | 33 +++++ .../migration/version/v76/package-info.java | 24 ++++ .../MigrationConfigurationModuleTest.java | 2 +- ...eateOrganizationsAlmBindingsTableTest.java | 1 - ...dUniqueIndexInUserPropertiesTableTest.java | 55 ++++++++ .../v76/CreateUserPropertiesTableTest.java | 63 +++++++++ .../version/v76/DbVersion76Test.java | 41 ++++++ .../user_properties.sql | 10 ++ .../sonar/server/user/ws/CurrentAction.java | 14 +- .../server/user/ws/DeactivateAction.java | 1 + .../server/user/ws/SetSettingAction.java | 88 ++++++++++++ .../sonar/server/user/ws/UsersWsModule.java | 3 +- .../server/user/ws/CurrentActionTest.java | 65 ++++++--- .../server/user/ws/DeactivateActionTest.java | 88 ++++++------ .../server/user/ws/SetSettingActionTest.java | 130 ++++++++++++++++++ .../server/user/ws/UsersWsModuleTest.java | 2 +- .../ws/client/users/SetSettingRequest.java | 64 +++++++++ .../ws/client/users/UsersService.java | 24 +++- sonar-ws/src/main/protobuf/ws-users.proto | 6 + 37 files changed, 1202 insertions(+), 94 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesMapper.java create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertyDto.java create mode 100644 server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserPropertiesMapper.xml create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTable.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTable.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java create mode 100644 server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/package-info.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTableTest.java create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java create mode 100644 server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest/user_properties.sql create mode 100644 server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetSettingRequest.java diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 6e026264551..046bfd3d8c0 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -115,13 +115,13 @@ public class ComputeEngineContainerImplTest { ); assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize( CONTAINER_ITSELF - + 19 // MigrationConfigurationModule + + 20 // MigrationConfigurationModule + 17 // level 2 ); assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize( COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION + 26 // level 1 - + 59 // content of DaoModule + + 60 // content of DaoModule + 3 // content of EsModule + 54 // content of CorePropertyDefinitions + 1 // StopFlagContainer diff --git a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java index 1135f040301..48708e80f74 100644 --- a/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java +++ b/server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java @@ -108,6 +108,7 @@ public final class SqTables { "schema_migrations", "snapshots", "users", + "user_properties", "user_roles", "user_tokens", "webhooks", diff --git a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl index e40e736765d..6790a438c54 100644 --- a/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl +++ b/server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl @@ -936,3 +936,15 @@ CREATE TABLE "ORGANIZATION_ALM_BINDINGS" ( ); CREATE UNIQUE INDEX "ORG_ALM_BINDINGS_ORG" ON "ORGANIZATION_ALM_BINDINGS" ("ORGANIZATION_UUID"); CREATE UNIQUE INDEX "ORG_ALM_BINDINGS_INSTALL" ON "ORGANIZATION_ALM_BINDINGS" ("ALM_APP_INSTALL_UUID"); + +CREATE TABLE "USER_PROPERTIES" ( + "UUID" VARCHAR(40) NOT NULL, + "USER_UUID" VARCHAR(255) NOT NULL, + "KEE" VARCHAR(100) NOT NULL, + "TEXT_VALUE" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL, + + CONSTRAINT "PK_USER_PROPERTIES" PRIMARY KEY ("UUID") +); +CREATE UNIQUE INDEX "USER_PROPERTIES_USER_UUID_KEE" ON "USER_PROPERTIES" ("USER_UUID", "KEE"); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java index ae037109b9d..e392da3ab61 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java @@ -79,6 +79,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.UserPropertiesDao; import org.sonar.db.user.UserTokenDao; import org.sonar.db.webhook.WebhookDao; import org.sonar.db.webhook.WebhookDeliveryDao; @@ -144,6 +145,7 @@ public class DaoModule extends Module { UserDao.class, UserGroupDao.class, UserPermissionDao.class, + UserPropertiesDao.class, UserTokenDao.class, WebhookDao.class, WebhookDeliveryDao.class)); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java index 5d9bf7277bd..2a056737912 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java @@ -77,6 +77,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.UserPropertiesDao; import org.sonar.db.user.UserTokenDao; import org.sonar.db.webhook.WebhookDao; import org.sonar.db.webhook.WebhookDeliveryDao; @@ -103,6 +104,7 @@ public class DbClient { private final UserDao userDao; private final UserGroupDao userGroupDao; private final UserTokenDao userTokenDao; + private final UserPropertiesDao userPropertiesDao; private final GroupMembershipDao groupMembershipDao; private final RoleDao roleDao; private final GroupPermissionDao groupPermissionDao; @@ -172,6 +174,7 @@ public class DbClient { userDao = getDao(map, UserDao.class); userGroupDao = getDao(map, UserGroupDao.class); userTokenDao = getDao(map, UserTokenDao.class); + userPropertiesDao = getDao(map, UserPropertiesDao.class); groupMembershipDao = getDao(map, GroupMembershipDao.class); roleDao = getDao(map, RoleDao.class); groupPermissionDao = getDao(map, GroupPermissionDao.class); @@ -301,6 +304,10 @@ public class DbClient { return userTokenDao; } + public UserPropertiesDao userPropertiesDao() { + return userPropertiesDao; + } + public GroupMembershipDao groupMembershipDao() { return groupMembershipDao; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java index 931268827af..1172e62b86d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java @@ -131,6 +131,7 @@ import org.sonar.db.user.UserDto; import org.sonar.db.user.UserGroupDto; import org.sonar.db.user.UserGroupMapper; import org.sonar.db.user.UserMapper; +import org.sonar.db.user.UserPropertiesMapper; import org.sonar.db.user.UserTokenCount; import org.sonar.db.user.UserTokenDto; import org.sonar.db.user.UserTokenMapper; @@ -261,6 +262,7 @@ public class MyBatis implements Startable { UserGroupMapper.class, UserMapper.class, UserPermissionMapper.class, + UserPropertiesMapper.class, UserTokenMapper.class, WebhookMapper.class, WebhookDeliveryMapper.class diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java new file mode 100644 index 00000000000..835679c1f81 --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.Dao; +import org.sonar.db.DbSession; + +public class UserPropertiesDao implements Dao { + + private final System2 system2; + private final UuidFactory uuidFactory; + + public UserPropertiesDao(System2 system2, UuidFactory uuidFactory) { + this.system2 = system2; + this.uuidFactory = uuidFactory; + } + + public List selectByUser(DbSession session, UserDto user) { + return mapper(session).selectByUserUuid(user.getUuid()); + } + + public UserPropertyDto insertOrUpdate(DbSession session, UserPropertyDto dto) { + long now = system2.now(); + if (mapper(session).update(dto, now) == 0) { + mapper(session).insert(dto.setUuid(uuidFactory.create()), now); + } + return dto; + } + + public void deleteByUser(DbSession session, UserDto user) { + mapper(session).deleteByUserUuid(user.getUuid()); + } + + private static UserPropertiesMapper mapper(DbSession session) { + return session.getMapper(UserPropertiesMapper.class); + } + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesMapper.java new file mode 100644 index 00000000000..53a1ac7987d --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesMapper.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 UserPropertiesMapper { + + List selectByUserUuid(@Param("userUuid") String userUuid); + + void insert(@Param("userProperty") UserPropertyDto userPropertyDto, @Param("now") long now); + + int update(@Param("userProperty") UserPropertyDto userPropertyDto, @Param("now") long now); + + void deleteByUserUuid(@Param("userUuid") String userUuid); + +} diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertyDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertyDto.java new file mode 100644 index 00000000000..7ea88d0acae --- /dev/null +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertyDto.java @@ -0,0 +1,80 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 UserPropertyDto { + + /** + * Unique UUID identifier. Max size is 40. Can't be null. + */ + private String uuid; + + /** + * The UUID of the user the settings belongs to. Max size is 255. Can't be null. + */ + private String userUuid; + + /** + * The key of the settings. Max size is 100. Can't be null. + */ + private String key; + + /** + * The value of the settings. Max size is 4000. Can't be null. + */ + private String value; + + public String getUuid() { + return uuid; + } + + UserPropertyDto setUuid(String uuid) { + this.uuid = uuid; + return this; + } + + public String getUserUuid() { + return userUuid; + } + + public UserPropertyDto setUserUuid(String userUuid) { + this.userUuid = userUuid; + return this; + } + + public String getKey() { + return key; + } + + public UserPropertyDto setKey(String key) { + this.key = key; + return this; + } + + public String getValue() { + return value; + } + + public UserPropertyDto setValue(String value) { + this.value = value; + return this; + } + +} diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserPropertiesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserPropertiesMapper.xml new file mode 100644 index 00000000000..28016ebf74e --- /dev/null +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserPropertiesMapper.xml @@ -0,0 +1,51 @@ + + + + + + + us.uuid as uuid, + us.user_uuid as userUuid, + us.kee as "key", + us.text_value as "value" + + + + + + INSERT INTO user_properties ( + uuid, + user_uuid, + kee, + text_value, + created_at, + updated_at + ) VALUES ( + #{userProperty.uuid,jdbcType=VARCHAR}, + #{userProperty.userUuid,jdbcType=VARCHAR}, + #{userProperty.key,jdbcType=VARCHAR}, + #{userProperty.value,jdbcType=VARCHAR}, + #{now,jdbcType=BIGINT}, + #{now,jdbcType=BIGINT} + ) + + + + UPDATE user_properties SET + text_value = #{userProperty.value, jdbcType=VARCHAR}, + updated_at = #{now,jdbcType=BIGINT} + WHERE + user_uuid = #{userProperty.userUuid, jdbcType=VARCHAR} + AND kee = #{userProperty.key, jdbcType=VARCHAR} + + + + DELETE FROM user_properties WHERE user_uuid=#{userUuid,jdbcType=VARCHAR} + + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java index f03bfc80e0b..2b32e081041 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java +++ b/server/sonar-db-dao/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(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 59); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 60); } } diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java index 761b5626427..d7baf7809e2 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java @@ -23,12 +23,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.user.UserQuery; import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.System2; +import org.sonar.api.utils.internal.TestSystem2; import org.sonar.db.DatabaseUtils; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -42,15 +41,13 @@ import static java.util.Collections.emptyList; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.groups.Tuple.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.sonar.db.user.GroupTesting.newGroupDto; import static org.sonar.db.user.UserTesting.newUserDto; public class UserDaoTest { private static final long NOW = 1_500_000_000_000L; - private System2 system2 = mock(System2.class); + private TestSystem2 system2 = new TestSystem2().setNow(NOW); @Rule public DbTester db = DbTester.create(system2); @@ -59,11 +56,6 @@ public class UserDaoTest { private DbSession session = db.getSession(); private UserDao underTest = db.getDbClient().userDao(); - @Before - public void setUp() { - when(system2.now()).thenReturn(NOW); - } - @Test public void selectByUuid() { UserDto user1 = db.users().insertUser(); @@ -363,11 +355,11 @@ public class UserDaoTest { public void countNewUsersSince() { assertThat(underTest.countNewUsersSince(session, 400L)).isEqualTo(0); - when(system2.now()).thenReturn(100L); + system2.setNow(100L); insertNonRootUser(newUserDto()); - when(system2.now()).thenReturn(200L); + system2.setNow(200L); insertNonRootUser(newUserDto()); - when(system2.now()).thenReturn(300L); + system2.setNow(300L); insertNonRootUser(newUserDto()); assertThat(underTest.countNewUsersSince(session, 50L)).isEqualTo(3); @@ -707,25 +699,25 @@ public class UserDaoTest { assertThat(underTest.selectByLogin(session, otherUser.getLogin()).isRoot()).isEqualTo(false); // does not fail when changing to same value - when(system2.now()).thenReturn(15_000L); + system2.setNow(15_000L); commit(() -> underTest.setRoot(session, login, false)); verifyRootAndUpdatedAt(login, false, 15_000L); verifyRootAndUpdatedAt(otherUser.getLogin(), false, otherUser.getUpdatedAt()); // change value - when(system2.now()).thenReturn(26_000L); + system2.setNow(26_000L); commit(() -> underTest.setRoot(session, login, true)); verifyRootAndUpdatedAt(login, true, 26_000L); verifyRootAndUpdatedAt(otherUser.getLogin(), false, otherUser.getUpdatedAt()); // does not fail when changing to same value - when(system2.now()).thenReturn(37_000L); + system2.setNow(37_000L); commit(() -> underTest.setRoot(session, login, true)); verifyRootAndUpdatedAt(login, true, 37_000L); verifyRootAndUpdatedAt(otherUser.getLogin(), false, otherUser.getUpdatedAt()); // change value back - when(system2.now()).thenReturn(48_000L); + system2.setNow(48_000L); commit(() -> underTest.setRoot(session, login, false)); verifyRootAndUpdatedAt(login, false, 48_000L); verifyRootAndUpdatedAt(otherUser.getLogin(), false, otherUser.getUpdatedAt()); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java index bc9c2d79378..516695d11ef 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java @@ -44,6 +44,7 @@ import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.user.GroupTesting.newGroupDto; import static org.sonar.db.user.UserTesting.newDisabledUser; import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.db.user.UserTesting.newUserSettingDto; import static org.sonar.db.user.UserTokenTesting.newUserToken; public class UserDbTester { @@ -110,6 +111,17 @@ public class UserDbTester { return Optional.ofNullable(dbClient.userDao().selectByLogin(db.getSession(), login)); } + // USER SETTINGS + + @SafeVarargs + public final UserPropertyDto insertUserSetting(UserDto user, Consumer... populators) { + UserPropertyDto dto = newUserSettingDto(user); + stream(populators).forEach(p -> p.accept(dto)); + dbClient.userPropertiesDao().insertOrUpdate(db.getSession(), dto); + db.commit(); + return dto; + } + // GROUPS public GroupDto insertGroup(OrganizationDto organization, String name) { diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java new file mode 100644 index 00000000000..1f193603dc4 --- /dev/null +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java @@ -0,0 +1,127 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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 java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.db.DbTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +public class UserPropertiesDaoTest { + + private static final long NOW = 1_500_000_000_000L; + + private TestSystem2 system2 = new TestSystem2().setNow(NOW); + + @Rule + public DbTester db = DbTester.create(system2); + + private UserPropertiesDao underTest = db.getDbClient().userPropertiesDao(); + + @Test + public void select_by_user() { + UserDto user = db.users().insertUser(); + UserPropertyDto userSetting1 = db.users().insertUserSetting(user); + UserPropertyDto userSetting2 = db.users().insertUserSetting(user); + UserDto anotherUser = db.users().insertUser(); + UserPropertyDto userSetting3 = db.users().insertUserSetting(anotherUser); + + List results = underTest.selectByUser(db.getSession(), user); + + assertThat(results) + .extracting(UserPropertyDto::getUuid) + .containsExactlyInAnyOrder(userSetting1.getUuid(), userSetting2.getUuid()) + .doesNotContain(userSetting3.getUuid()); + } + + @Test + public void insert() { + UserDto user = db.users().insertUser(); + + UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey("a_key") + .setValue("a_value")); + + Map map = db.selectFirst(db.getSession(), "select uuid as \"uuid\",\n" + + " user_uuid as \"userUuid\",\n" + + " kee as \"key\",\n" + + " text_value as \"value\"," + + " created_at as \"createdAt\",\n" + + " updated_at as \"updatedAt\"" + + " from user_properties"); + assertThat(map).contains( + entry("uuid", userSetting.getUuid()), + entry("userUuid", user.getUuid()), + entry("key", "a_key"), + entry("value", "a_value"), + entry("createdAt", NOW), + entry("updatedAt", NOW)); + } + + @Test + public void update() { + UserDto user = db.users().insertUser(); + UserPropertyDto userProperty = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey("a_key") + .setValue("old_value")); + + system2.setNow(2_000_000_000_000L); + underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() + .setUserUuid(user.getUuid()) + .setKey("a_key") + .setValue("new_value")); + + Map map = db.selectFirst(db.getSession(), "select uuid as \"uuid\",\n" + + " user_uuid as \"userUuid\",\n" + + " kee as \"key\",\n" + + " text_value as \"value\"," + + " created_at as \"createdAt\",\n" + + " updated_at as \"updatedAt\"" + + " from user_properties"); + assertThat(map).contains( + entry("uuid", userProperty.getUuid()), + entry("userUuid", user.getUuid()), + entry("key", "a_key"), + entry("value", "new_value"), + entry("createdAt", NOW), + entry("updatedAt", 2_000_000_000_000L)); + } + + @Test + public void delete_by_user() { + UserDto user = db.users().insertUser(); + db.users().insertUserSetting(user); + db.users().insertUserSetting(user); + UserDto anotherUser = db.users().insertUser(); + db.users().insertUserSetting(anotherUser); + + underTest.deleteByUser(db.getSession(), user); + + assertThat(underTest.selectByUser(db.getSession(), user)).isEmpty(); + assertThat(underTest.selectByUser(db.getSession(), anotherUser)).hasSize(1); + } +} diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java index 8be30e35c60..50e39364a6b 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java @@ -20,6 +20,7 @@ package org.sonar.db.user; import javax.annotation.Nullable; +import org.sonar.core.util.Uuids; import static java.util.Collections.singletonList; import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; @@ -87,4 +88,12 @@ public class UserTesting { .setCryptedPassword(null) .setSalt(null); } + + public static UserPropertyDto newUserSettingDto(UserDto user) { + return new UserPropertyDto() + .setUuid(Uuids.createFast()) + .setUserUuid(user.getUuid()) + .setKey(randomAlphanumeric(20)) + .setValue(randomAlphanumeric(100)); + } } diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java index a196b02f2bc..4a6c471f6bd 100644 --- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java @@ -39,6 +39,7 @@ import org.sonar.server.platform.db.migration.version.v72.DbVersion72; import org.sonar.server.platform.db.migration.version.v73.DbVersion73; import org.sonar.server.platform.db.migration.version.v74.DbVersion74; import org.sonar.server.platform.db.migration.version.v75.DbVersion75; +import org.sonar.server.platform.db.migration.version.v76.DbVersion76; public class MigrationConfigurationModule extends Module { @Override @@ -61,6 +62,7 @@ public class MigrationConfigurationModule extends Module { DbVersion73.class, DbVersion74.class, DbVersion75.class, + DbVersion76.class, // migration steps MigrationStepRegistryImpl.class, diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTable.java new file mode 100644 index 00000000000..1fd4df143da --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTable.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.SupportsBlueGreen; +import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +@SupportsBlueGreen +public class AddUniqueIndexInUserPropertiesTable extends DdlChange { + + private static final String TABLE_NAME = "user_properties"; + + public AddUniqueIndexInUserPropertiesTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new CreateIndexBuilder(getDialect()) + .addColumn(newVarcharColumnDefBuilder() + .setColumnName("user_uuid") + .setIsNullable(false) + .setLimit(255) + .build()) + .addColumn(newVarcharColumnDefBuilder() + .setColumnName("kee") + .setIsNullable(false) + .setLimit(100) + .build()) + .setUnique(true) + .setTable(TABLE_NAME) + .setName("user_properties_user_uuid_kee") + .build()); + } + +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTable.java new file mode 100644 index 00000000000..59942a74a08 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTable.java @@ -0,0 +1,94 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import java.sql.Connection; +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.DatabaseUtils; +import org.sonar.server.platform.db.migration.SupportsBlueGreen; +import org.sonar.server.platform.db.migration.def.BigIntegerColumnDef; +import org.sonar.server.platform.db.migration.def.VarcharColumnDef; +import org.sonar.server.platform.db.migration.sql.CreateTableBuilder; +import org.sonar.server.platform.db.migration.step.DdlChange; + +import static org.sonar.server.platform.db.migration.def.BigIntegerColumnDef.newBigIntegerColumnDefBuilder; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.UUID_SIZE; +import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVarcharColumnDefBuilder; + +@SupportsBlueGreen +public class CreateUserPropertiesTable extends DdlChange { + + private static final String TABLE_NAME = "user_properties"; + + private static final VarcharColumnDef UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("uuid") + .setIsNullable(false) + .setLimit(UUID_SIZE) + .build(); + private static final VarcharColumnDef USER_UUID_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("user_uuid") + .setIsNullable(false) + .setLimit(255) + .build(); + private static final VarcharColumnDef KEY_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("kee") + .setIsNullable(false) + .setLimit(100) + .build(); + private static final VarcharColumnDef TEXT_VALUE_COLUMN = newVarcharColumnDefBuilder() + .setColumnName("text_value") + .setIsNullable(false) + .setLimit(4000) + .build(); + private static final BigIntegerColumnDef CREATED_AT_COLUMN = newBigIntegerColumnDefBuilder() + .setColumnName("created_at") + .setIsNullable(false) + .build(); + private static final BigIntegerColumnDef UPDATED_AT_COLUMN = newBigIntegerColumnDefBuilder() + .setColumnName("updated_at") + .setIsNullable(false) + .build(); + + public CreateUserPropertiesTable(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + if (tableExists()) { + return; + } + context.execute(new CreateTableBuilder(getDialect(), TABLE_NAME) + .addPkColumn(UUID_COLUMN) + .addColumn(USER_UUID_COLUMN) + .addColumn(KEY_COLUMN) + .addColumn(TEXT_VALUE_COLUMN) + .addColumn(CREATED_AT_COLUMN) + .addColumn(UPDATED_AT_COLUMN) + .build()); + } + + private boolean tableExists() throws SQLException { + try (Connection connection = getDatabase().getDataSource().getConnection()) { + return DatabaseUtils.tableExists(TABLE_NAME, connection); + } + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java new file mode 100644 index 00000000000..41364b7a1c0 --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import org.sonar.server.platform.db.migration.step.MigrationStepRegistry; +import org.sonar.server.platform.db.migration.version.DbVersion; + +public class DbVersion76 implements DbVersion { + + @Override + public void addSteps(MigrationStepRegistry registry) { + registry + .add(2500, "Create table USER_PROPERTIES", CreateUserPropertiesTable.class) + .add(2501, "Add index in table USER_PROPERTIES", AddUniqueIndexInUserPropertiesTable.class); + } +} diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/package-info.java new file mode 100644 index 00000000000..8cad3547ccb --- /dev/null +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java index 12e13344290..4c987cbb1ec 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java @@ -37,7 +37,7 @@ public class MigrationConfigurationModuleTest { assertThat(container.getPicoContainer().getComponentAdapters()) .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER // DbVersion classes - + 16 + + 17 // Others + 3); } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java index 7cac352e8f6..23c52fb4443 100644 --- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java @@ -63,5 +63,4 @@ public class CreateOrganizationsAlmBindingsTableTest { db.assertUniqueIndex(TABLE, "org_alm_bindings_org", "organization_uuid"); db.assertUniqueIndex(TABLE, "org_alm_bindings_install", "alm_app_install_uuid"); } - } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest.java new file mode 100644 index 00000000000..6978ae7f7d5 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.db.CoreDbTester; + +public class AddUniqueIndexInUserPropertiesTableTest { + + private static final String TABLE = "user_properties"; + + @Rule + public final CoreDbTester db = CoreDbTester.createForSchema(AddUniqueIndexInUserPropertiesTableTest.class, "user_properties.sql"); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private AddUniqueIndexInUserPropertiesTable underTest = new AddUniqueIndexInUserPropertiesTable(db.database()); + + @Test + public void creates_index() throws SQLException { + underTest.execute(); + + db.assertUniqueIndex(TABLE, "user_properties_user_uuid_kee", "user_uuid", "kee"); + } + + @Test + public void migration_is_not_re_entrant() throws SQLException { + underTest.execute(); + + expectedException.expect(IllegalStateException.class); + + underTest.execute(); + } + +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTableTest.java new file mode 100644 index 00000000000..16151e85456 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTableTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import java.sql.SQLException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.CoreDbTester; + +import static java.sql.Types.BIGINT; +import static java.sql.Types.VARCHAR; + +public class CreateUserPropertiesTableTest { + + private static final String TABLE = "user_properties"; + + @Rule + public final CoreDbTester db = CoreDbTester.createEmpty(); + + private CreateUserPropertiesTable underTest = new CreateUserPropertiesTable(db.database()); + + @Test + public void creates_table() throws SQLException { + underTest.execute(); + + checkTable(); + } + + @Test + public void migration_is_reentrant() throws SQLException { + underTest.execute(); + underTest.execute(); + + checkTable(); + } + + private void checkTable() { + db.assertPrimaryKey(TABLE, "pk_user_properties", "uuid"); + db.assertColumnDefinition(TABLE, "uuid", VARCHAR, 40, false); + db.assertColumnDefinition(TABLE, "user_uuid", VARCHAR, 255, false); + db.assertColumnDefinition(TABLE, "kee", VARCHAR, 100, false); + db.assertColumnDefinition(TABLE, "text_value", VARCHAR, 4000, false); + db.assertColumnDefinition(TABLE, "created_at", BIGINT, null, false); + db.assertColumnDefinition(TABLE, "updated_at", BIGINT, null, false); + } +} diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java new file mode 100644 index 00000000000..2f71a6f1ac5 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.platform.db.migration.version.v76; + +import org.junit.Test; + +import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationCount; +import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber; + +public class DbVersion76Test { + + private DbVersion76 underTest = new DbVersion76(); + + @Test + public void migrationNumber_starts_at_2500() { + verifyMinimumMigrationNumber(underTest, 2500); + } + + @Test + public void verify_migration_count() { + verifyMigrationCount(underTest, 2); + } + +} diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest/user_properties.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest/user_properties.sql new file mode 100644 index 00000000000..11e714df797 --- /dev/null +++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest/user_properties.sql @@ -0,0 +1,10 @@ +CREATE TABLE "USER_PROPERTIES" ( + "UUID" VARCHAR(40) NOT NULL, + "USER_UUID" VARCHAR(255) NOT NULL, + "KEE" VARCHAR(100) NOT NULL, + "TEXT_VALUE" VARCHAR(4000) NOT NULL, + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL, + + CONSTRAINT "PK_USER_PROPERTIES" PRIMARY KEY ("UUID") +); \ No newline at end of file diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java index 137fbcc95df..0ab70526451 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java @@ -27,6 +27,7 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService.NewController; import org.sonar.core.platform.PluginRepository; +import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; @@ -125,7 +126,8 @@ public class CurrentAction implements UsersWsAction { .addAllScmAccounts(user.getScmAccountsAsList()) .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) .setHomepage(buildHomepage(dbSession, user)) - .setShowOnboardingTutorial(!user.isOnboarded()); + .setShowOnboardingTutorial(!user.isOnboarded()) + .addAllSettings(loadUserSettings(dbSession, user)); setNullable(emptyToNull(user.getEmail()), builder::setEmail); setNullable(emptyToNull(user.getEmail()), u -> builder.setAvatar(avatarResolver.create(user))); setNullable(user.getExternalLogin(), builder::setExternalIdentity); @@ -237,6 +239,16 @@ public class CurrentAction implements UsersWsAction { .build(); } + private List loadUserSettings(DbSession dbSession, UserDto user) { + return dbClient.userPropertiesDao().selectByUser(dbSession, user) + .stream() + .map(dto -> CurrentWsResponse.Setting.newBuilder() + .setKey(dto.getKey()) + .setValue(dto.getValue()) + .build()) + .collect(MoreCollectors.toList()); + } + private static boolean noHomepageSet(UserDto user) { return user.getHomepageType() == null; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java index e177bc1f86c..96be0264780 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java @@ -102,6 +102,7 @@ public class DeactivateAction implements UsersWsAction { dbClient.permissionTemplateDao().deleteUserPermissionsByUserId(dbSession, userId); dbClient.qProfileEditUsersDao().deleteByUser(dbSession, user); dbClient.organizationMemberDao().deleteByUserId(dbSession, userId); + dbClient.userPropertiesDao().deleteByUser(dbSession, user); dbClient.userDao().deactivateUser(dbSession, user); userIndexer.commitAndIndex(dbSession, user); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java new file mode 100644 index 00000000000..97d1a3dfe3c --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.user.UserPropertyDto; +import org.sonar.server.user.UserSession; + +import static java.util.Objects.requireNonNull; + +public class SetSettingAction implements UsersWsAction { + + public static final String PARAM_KEY = "key"; + public static final String PARAM_VALUE = "value"; + + private final DbClient dbClient; + private final UserSession userSession; + + public SetSettingAction(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("set_setting") + .setDescription("Update a setting value.
" + + "Requires user to be authenticated") + .setSince("7.6") + .setInternal(true) + .setPost(true) + .setHandler(this); + + action.createParam(PARAM_KEY) + .setRequired(true) + .setMaximumLength(100) + .setDescription("Setting key") + .setPossibleValues("notifications.optOut", "notifications.readDate"); + + action.createParam(PARAM_VALUE) + .setRequired(true) + .setMaximumLength(4000) + .setDescription("Setting value") + .setExampleValue("true"); + } + + @Override + public void handle(Request request, Response response) throws Exception { + userSession.checkLoggedIn(); + String key = request.mandatoryParam(PARAM_KEY); + String value = request.mandatoryParam(PARAM_VALUE); + setUserSetting(key, value); + response.noContent(); + } + + private void setUserSetting(String key, String value) { + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.userPropertiesDao().insertOrUpdate(dbSession, + new UserPropertyDto() + .setUserUuid(requireNonNull(userSession.getUuid(), "Authenticated user uuid cannot be null")) + .setKey(key) + .setValue(value)); + dbSession.commit(); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java index 39d6c3ae5de..9ae8a685287 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java @@ -38,6 +38,7 @@ public class UsersWsModule extends Module { UserJsonWriter.class, SkipOnboardingTutorialAction.class, SetHomepageAction.class, - HomepageTypesImpl.class); + HomepageTypesImpl.class, + SetSettingAction.class); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java index a07acb7b511..1f9c32082b6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java @@ -19,6 +19,7 @@ */ package org.sonar.server.user.ws; +import java.util.Collections; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -45,6 +46,7 @@ import org.sonarqube.ws.Users.CurrentWsResponse; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.mock; import static org.sonar.api.web.UserRole.USER; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; @@ -56,7 +58,7 @@ import static org.sonar.test.JsonAssert.assertJson; public class CurrentActionTest { @Rule - public UserSessionRule userSessionRule = UserSessionRule.standalone(); + public UserSessionRule userSession = UserSessionRule.standalone(); @Rule public DbTester db = DbTester.create(System2.INSTANCE); @Rule @@ -70,7 +72,7 @@ public class CurrentActionTest { ResourceTypeTree.builder().addType(ResourceType.builder(Qualifiers.PROJECT).build()).build()})); private WsActionTester ws = new WsActionTester( - new CurrentAction(userSessionRule, db.getDbClient(), TestDefaultOrganizationProvider.from(db), new AvatarResolverImpl(), homepageTypes, pluginRepository, permissionService)); + new CurrentAction(userSession, db.getDbClient(), TestDefaultOrganizationProvider.from(db), new AvatarResolverImpl(), homepageTypes, pluginRepository, permissionService)); @Test public void return_user_info() { @@ -83,7 +85,7 @@ public class CurrentActionTest { .setExternalIdentityProvider("sonarqube") .setScmAccounts(newArrayList("obiwan:github", "obiwan:bitbucket")) .setOnboarded(false)); - userSessionRule.logIn("obiwan.kenobi"); + userSession.logIn(user); CurrentWsResponse response = call(); @@ -97,7 +99,7 @@ public class CurrentActionTest { @Test public void return_minimal_user_info() { - db.users().insertUser(user -> user + UserDto user = db.users().insertUser(u -> u .setLogin("obiwan.kenobi") .setName("Obiwan Kenobi") .setEmail(null) @@ -105,14 +107,14 @@ public class CurrentActionTest { .setExternalLogin("obiwan") .setExternalIdentityProvider("sonarqube") .setScmAccounts((String) null)); - userSessionRule.logIn("obiwan.kenobi"); + userSession.logIn(user); CurrentWsResponse response = call(); assertThat(response) .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal, - CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::hasPersonalOrganization) - .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false); + CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::hasPersonalOrganization, CurrentWsResponse::getSettingsList) + .containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false, Collections.emptyList()); assertThat(response.hasEmail()).isFalse(); assertThat(response.getScmAccountsList()).isEmpty(); assertThat(response.getGroupsList()).isEmpty(); @@ -121,10 +123,10 @@ public class CurrentActionTest { @Test public void convert_empty_email_to_null() { - db.users().insertUser(user -> user + UserDto user = db.users().insertUser(u -> u .setLogin("obiwan.kenobi") .setEmail("")); - userSessionRule.logIn("obiwan.kenobi"); + userSession.logIn(user); CurrentWsResponse response = call(); @@ -134,7 +136,7 @@ public class CurrentActionTest { @Test public void return_group_membership() { UserDto user = db.users().insertUser(); - userSessionRule.logIn(user.getLogin()); + userSession.logIn(user); db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), user); db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), user); @@ -146,8 +148,8 @@ public class CurrentActionTest { @Test public void return_permissions() { UserDto user = db.users().insertUser(); - userSessionRule - .logIn(user.getLogin()) + userSession + .logIn(user) // permissions on default organization .addPermission(SCAN, db.getDefaultOrganization()) .addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization()) @@ -163,17 +165,38 @@ public class CurrentActionTest { public void return_personal_organization() { OrganizationDto organization = db.organizations().insert(); UserDto user = db.users().insertUser(u -> u.setOrganizationUuid(organization.getUuid())); - userSessionRule.logIn(user); + userSession.logIn(user); CurrentWsResponse response = call(); assertThat(response.getPersonalOrganization()).isEqualTo(organization.getKey()); } + @Test + public void return_user_settings() { + UserDto user = db.users().insertUser(); + db.users().insertUserSetting(user, userSetting -> userSetting + .setKey("notifications.readDate") + .setValue("1234")); + db.users().insertUserSetting(user, userSetting -> userSetting + .setKey("notifications.optOut") + .setValue("true")); + db.commit(); + userSession.logIn(user); + + CurrentWsResponse response = call(); + + assertThat(response.getSettingsList()) + .extracting(CurrentWsResponse.Setting::getKey, CurrentWsResponse.Setting::getValue) + .containsExactly( + tuple("notifications.optOut", "true"), + tuple("notifications.readDate", "1234")); + } + @Test public void fail_with_ISE_when_user_login_in_db_does_not_exist() { db.users().insertUser(usert -> usert.setLogin("another")); - userSessionRule.logIn("obiwan.kenobi"); + userSession.logIn("obiwan.kenobi"); expectedException.expect(IllegalStateException.class); expectedException.expectMessage("User login 'obiwan.kenobi' cannot be found"); @@ -184,7 +207,7 @@ public class CurrentActionTest { @Test public void fail_with_ISE_when_personal_organization_does_not_exist() { UserDto user = db.users().insertUser(u -> u.setOrganizationUuid("Unknown")); - userSessionRule.logIn(user); + userSession.logIn(user); expectedException.expect(IllegalStateException.class); expectedException.expectMessage("Organization uuid 'Unknown' does not exist"); @@ -194,7 +217,7 @@ public class CurrentActionTest { @Test public void anonymous() { - userSessionRule + userSession .anonymous() .addPermission(SCAN, db.getDefaultOrganization()) .addPermission(PROVISION_PROJECTS, db.getDefaultOrganization()); @@ -214,11 +237,6 @@ public class CurrentActionTest { @Test public void json_example() { ComponentDto componentDto = db.components().insertPrivateProject(u -> u.setUuid("UUID-of-the-death-star"), u -> u.setDbKey("death-star-key")); - userSessionRule - .logIn("obiwan.kenobi") - .addPermission(SCAN, db.getDefaultOrganization()) - .addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization()) - .addProjectPermission(USER, componentDto); UserDto obiwan = db.users().insertUser(user -> user .setLogin("obiwan.kenobi") .setName("Obiwan Kenobi") @@ -230,6 +248,11 @@ public class CurrentActionTest { .setOnboarded(true) .setHomepageType("PROJECT") .setHomepageParameter("UUID-of-the-death-star")); + userSession + .logIn(obiwan) + .addPermission(SCAN, db.getDefaultOrganization()) + .addPermission(ADMINISTER_QUALITY_PROFILES, db.getDefaultOrganization()) + .addProjectPermission(USER, componentDto); db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Jedi")), obiwan); db.users().insertMember(db.users().insertGroup(newGroupDto().setName("Rebel")), obiwan); 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 04e4126c093..1e114fa0a34 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,7 +20,6 @@ package org.sonar.server.user.ws; import java.util.Optional; -import java.util.function.Consumer; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -57,7 +56,6 @@ import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.termQuery; import static org.sonar.api.web.UserRole.CODEVIEWER; import static org.sonar.api.web.UserRole.USER; -import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_PROFILES; @@ -93,14 +91,14 @@ public class DeactivateActionTest { @Test public void deactivate_user_and_delete_his_related_data() { - UserDto user = insertUser(u -> u + UserDto user = db.users().insertUser(u -> u .setLogin("ada.lovelace") .setEmail("ada.lovelace@noteg.com") .setName("Ada Lovelace") .setScmAccounts(singletonList("al"))); logInAsSystemAdministrator(); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); verifyThatUserIsDeactivated(user.getLogin()); assertThat(es.client().prepareSearch(UserIndexDefinition.INDEX_TYPE_USER) @@ -113,12 +111,12 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_group_membership() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); GroupDto group1 = db.users().insertGroup(); db.users().insertGroup(); db.users().insertMember(group1, user); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().groupMembershipDao().selectGroupIdsByUserId(dbSession, user.getId())).isEmpty(); } @@ -126,12 +124,12 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_tokens() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); db.users().insertToken(user); db.users().insertToken(user); db.commit(); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().userTokenDao().selectByUser(dbSession, user)).isEmpty(); } @@ -139,13 +137,13 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_properties() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); ComponentDto project = db.components().insertPrivateProject(); db.properties().insertProperty(newUserPropertyDto(user)); db.properties().insertProperty(newUserPropertyDto(user)); db.properties().insertProperty(newUserPropertyDto(user).setResourceId(project.getId())); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().setUserId(user.getId()).build(), dbSession)).isEmpty(); assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().setUserId(user.getId()).setComponentId(project.getId()).build(), dbSession)).isEmpty(); @@ -154,14 +152,14 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_permissions() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); ComponentDto project = db.components().insertPrivateProject(); db.users().insertPermissionOnUser(user, SCAN); db.users().insertPermissionOnUser(user, ADMINISTER_QUALITY_PROFILES); db.users().insertProjectPermissionOnUser(user, USER, project); db.users().insertProjectPermissionOnUser(user, CODEVIEWER, project); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().userPermissionDao().selectGlobalPermissionsOfUser(dbSession, user.getId(), db.getDefaultOrganization().getUuid())).isEmpty(); assertThat(db.getDbClient().userPermissionDao().selectProjectPermissionsOfUser(dbSession, user.getId(), project.getId())).isEmpty(); @@ -170,13 +168,13 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_permission_templates() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); PermissionTemplateDto template = db.permissionTemplates().insertTemplate(); PermissionTemplateDto anotherTemplate = db.permissionTemplates().insertTemplate(); db.permissionTemplates().addUserToTemplate(template.getId(), user.getId(), USER); db.permissionTemplates().addUserToTemplate(anotherTemplate.getId(), user.getId(), CODEVIEWER); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getId())).extracting(PermissionTemplateUserDto::getUserId).isEmpty(); assertThat(db.getDbClient().permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, anotherTemplate.getId())).extracting(PermissionTemplateUserDto::getUserId) @@ -186,11 +184,11 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_qprofiles_permissions() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); QProfileDto profile = db.qualityProfiles().insert(db.getDefaultOrganization()); db.qualityProfiles().addUserPermission(profile, user); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().qProfileEditUsersDao().exists(dbSession, profile, user)).isFalse(); } @@ -198,14 +196,14 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_default_assignee_settings() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); ComponentDto project = db.components().insertPrivateProject(); ComponentDto anotherProject = db.components().insertPrivateProject(); db.properties().insertProperty(new PropertyDto().setKey("sonar.issues.defaultAssigneeLogin").setValue(user.getLogin()).setResourceId(project.getId())); db.properties().insertProperty(new PropertyDto().setKey("sonar.issues.defaultAssigneeLogin").setValue(user.getLogin()).setResourceId(anotherProject.getId())); db.properties().insertProperty(new PropertyDto().setKey("other").setValue(user.getLogin()).setResourceId(anotherProject.getId())); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().setKey("sonar.issues.defaultAssigneeLogin").build(), db.getSession())).isEmpty(); assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().build(), db.getSession())).extracting(PropertyDto::getKey).containsOnly("other"); @@ -214,21 +212,36 @@ public class DeactivateActionTest { @Test public void deactivate_user_deletes_his_organization_membership() { logInAsSystemAdministrator(); - UserDto user = insertUser(); + UserDto user = db.users().insertUser(); OrganizationDto organization = db.organizations().insert(); db.organizations().addMember(organization, user); OrganizationDto anotherOrganization = db.organizations().insert(); db.organizations().addMember(anotherOrganization, user); - deactivate(user.getLogin()).getInput(); + deactivate(user.getLogin()); assertThat(dbClient.organizationMemberDao().select(db.getSession(), organization.getUuid(), user.getId())).isNotPresent(); assertThat(dbClient.organizationMemberDao().select(db.getSession(), anotherOrganization.getUuid(), user.getId())).isNotPresent(); } + @Test + public void deactivate_user_deletes_his_user_settings() { + logInAsSystemAdministrator(); + UserDto user = db.users().insertUser(); + db.users().insertUserSetting(user); + db.users().insertUserSetting(user); + UserDto anotherUser = db.users().insertUser(); + db.users().insertUserSetting(anotherUser); + + deactivate(user.getLogin()); + + assertThat(db.getDbClient().userPropertiesDao().selectByUser(dbSession, user)).isEmpty(); + assertThat(db.getDbClient().userPropertiesDao().selectByUser(dbSession, anotherUser)).hasSize(1); + } + @Test public void cannot_deactivate_self() { - UserDto user = createUser(); + UserDto user = db.users().insertUser(); userSession.logIn(user.getLogin()).setSystemAdministrator(); expectedException.expect(BadRequestException.class); @@ -279,7 +292,7 @@ public class DeactivateActionTest { @Test public void fail_to_deactivate_last_administrator_of_default_organization() { - UserDto admin = createUser(); + UserDto admin = db.users().insertUser(); db.users().insertPermissionOnUser(admin, ADMINISTER); logInAsSystemAdministrator(); @@ -293,15 +306,15 @@ public class DeactivateActionTest { public void fail_to_deactivate_last_administrator_of_organization() { // user1 is the unique administrator of org1 and org2. // user1 and user2 are both administrators of org3 - UserDto user1 = insertUser(u -> u.setLogin("test")); + UserDto user1 = db.users().insertUser(u -> u.setLogin("test")); OrganizationDto org1 = db.organizations().insert(newOrganizationDto().setKey("org1")); OrganizationDto org2 = db.organizations().insert(newOrganizationDto().setKey("org2")); OrganizationDto org3 = db.organizations().insert(newOrganizationDto().setKey("org3")); - db.users().insertPermissionOnUser(org1, user1, SYSTEM_ADMIN); - db.users().insertPermissionOnUser(org2, user1, SYSTEM_ADMIN); - db.users().insertPermissionOnUser(org3, user1, SYSTEM_ADMIN); - UserDto user2 = createUser(); - db.users().insertPermissionOnUser(org3, user2, SYSTEM_ADMIN); + db.users().insertPermissionOnUser(org1, user1, ADMINISTER); + db.users().insertPermissionOnUser(org2, user1, ADMINISTER); + db.users().insertPermissionOnUser(org3, user1, ADMINISTER); + UserDto user2 = db.users().insertUser(); + db.users().insertPermissionOnUser(org3, user2, ADMINISTER); logInAsSystemAdministrator(); expectedException.expect(BadRequestException.class); @@ -312,8 +325,8 @@ public class DeactivateActionTest { @Test public void administrators_can_be_deactivated_if_there_are_still_other_administrators() { - UserDto admin = createUser(); - UserDto anotherAdmin = createUser(); + UserDto admin = db.users().insertUser(); + UserDto anotherAdmin = db.users().insertUser(); db.users().insertPermissionOnUser(admin, ADMINISTER); db.users().insertPermissionOnUser(anotherAdmin, ADMINISTER); db.commit(); @@ -334,7 +347,7 @@ public class DeactivateActionTest { @Test public void test_example() { - UserDto user = insertUser(u -> u + UserDto user = db.users().insertUser(u -> u .setLogin("ada.lovelace") .setEmail("ada.lovelace@noteg.com") .setName("Ada Lovelace") @@ -347,19 +360,6 @@ public class DeactivateActionTest { assertJson(json).isSimilarTo(ws.getDef().responseExampleAsString()); } - private UserDto createUser() { - return insertUser(); - } - - @SafeVarargs - private final UserDto insertUser(Consumer... populators) { - UserDto user = db.users().insertUser(populators); - db.users().insertToken(user); - db.properties().insertProperties(new PropertyDto().setUserId(user.getId()).setKey("foo").setValue("bar")); - userIndexer.commitAndIndex(dbSession, user); - return user; - } - private void logInAsSystemAdministrator() { userSession.logIn().setSystemAdministrator(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java new file mode 100644 index 00000000000..8b9f52672ac --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java @@ -0,0 +1,130 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; +import org.sonar.db.user.UserPropertyDto; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +public class SetSettingActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private WsActionTester ws = new WsActionTester(new SetSettingAction(db.getDbClient(), userSession)); + + @Test + public void set_new_setting() { + UserDto user = db.users().insertUser(); + userSession.logIn(user); + + ws.newRequest() + .setParam("key", "notifications.optOut") + .setParam("value", "true") + .execute(); + + assertThat(db.getDbClient().userPropertiesDao().selectByUser(db.getSession(), user)) + .extracting(UserPropertyDto::getKey, UserPropertyDto::getValue) + .containsExactlyInAnyOrder(tuple("notifications.optOut", "true")); + } + + @Test + public void update_existing_setting() { + UserDto user = db.users().insertUser(); + db.users().insertUserSetting(user, userSetting -> userSetting + .setKey("notifications.optOut") + .setValue("false")); + userSession.logIn(user); + + ws.newRequest() + .setParam("key", "notifications.optOut") + .setParam("value", "true") + .execute(); + + assertThat(db.getDbClient().userPropertiesDao().selectByUser(db.getSession(), user)) + .extracting(UserPropertyDto::getKey, UserPropertyDto::getValue) + .containsExactlyInAnyOrder(tuple("notifications.optOut", "true")); + } + + @Test + public void keep_existing_setting_when_setting_new_one() { + UserDto user = db.users().insertUser(); + db.users().insertUserSetting(user, userSetting -> userSetting + .setKey("notifications.readDate") + .setValue("1234")); + userSession.logIn(user); + + ws.newRequest() + .setParam("key", "notifications.optOut") + .setParam("value", "true") + .execute(); + + assertThat(db.getDbClient().userPropertiesDao().selectByUser(db.getSession(), user)) + .extracting(UserPropertyDto::getKey, UserPropertyDto::getValue) + .containsExactlyInAnyOrder( + tuple("notifications.readDate", "1234"), + tuple("notifications.optOut", "true")); + } + + @Test + public void fail_when_not_authenticated() { + expectedException.expect(UnauthorizedException.class); + + ws.newRequest() + .setParam("key", "notifications.optOut") + .setParam("value", "true") + .execute(); + } + + @Test + public void definition() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.key()).isEqualTo("set_setting"); + assertThat(definition.isPost()).isTrue(); + assertThat(definition.isInternal()).isTrue(); + assertThat(definition.since()).isEqualTo("7.6"); + + assertThat(definition.params()) + .extracting(WebService.Param::key, WebService.Param::isRequired, WebService.Param::maximumLength) + .containsOnly( + tuple("key", true, 100), + tuple("value", true, 4000)); + + assertThat(definition.param("key").possibleValues()).containsExactlyInAnyOrder("notifications.optOut", "notifications.readDate"); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java index ad4e8668dd5..e95268bd442 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java @@ -29,6 +29,6 @@ public class UsersWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new UsersWsModule().configure(container); - assertThat(container.size()).isEqualTo(2 + 14); + assertThat(container.size()).isEqualTo(2 + 15); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetSettingRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetSettingRequest.java new file mode 100644 index 00000000000..f69d272a30e --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetSettingRequest.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program 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. + * + * This program 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.users; + +import javax.annotation.Generated; + +/** + * This is part of the internal API. + * This is a POST request. + * @see Further information about this action online (including a response example) + * @since 7.6 + */ +@Generated("sonar-ws-generator") +public class SetSettingRequest { + + private String key; + private String value; + + /** + * This is a mandatory parameter. + */ + public SetSettingRequest setKey(String key) { + this.key = key; + return this; + } + + public String getKey() { + return key; + } + + /** + * This is a mandatory parameter. + * Possible values: + *
    + *
  • "notifications.optOut"
  • + *
  • "notifications.readDate"
  • + *
+ */ + public SetSettingRequest setValue(String value) { + this.value = value; + return this; + } + + public String getValue() { + return value; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java index 3dcec5eaa39..beb341d56e6 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java @@ -22,15 +22,15 @@ package org.sonarqube.ws.client.users; import java.util.stream.Collectors; import javax.annotation.Generated; import org.sonarqube.ws.MediaTypes; -import org.sonarqube.ws.client.BaseService; -import org.sonarqube.ws.client.GetRequest; -import org.sonarqube.ws.client.PostRequest; -import org.sonarqube.ws.client.WsConnector; import org.sonarqube.ws.Users.CreateWsResponse; import org.sonarqube.ws.Users.CurrentWsResponse; import org.sonarqube.ws.Users.GroupsWsResponse; import org.sonarqube.ws.Users.IdentityProvidersWsResponse; import org.sonarqube.ws.Users.SearchWsResponse; +import org.sonarqube.ws.client.BaseService; +import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsConnector; /** * @see Further information about this web service online @@ -172,6 +172,22 @@ public class UsersService extends BaseService { ).content(); } + /** + * + * This is part of the internal API. + * This is a POST request. + * @see Further information about this action online (including a response example) + * @since 7.6 + */ + public void setSetting(SetSettingRequest request) { + call( + new PostRequest(path("set_setting")) + .setParam("key", request.getKey()) + .setParam("value", request.getValue()) + .setMediaType(MediaTypes.JSON) + ).content(); + } + /** * * This is part of the internal API. diff --git a/sonar-ws/src/main/protobuf/ws-users.proto b/sonar-ws/src/main/protobuf/ws-users.proto index d3ba178ebea..917cd7c2b58 100644 --- a/sonar-ws/src/main/protobuf/ws-users.proto +++ b/sonar-ws/src/main/protobuf/ws-users.proto @@ -110,6 +110,7 @@ message CurrentWsResponse { optional string avatar = 12; optional Homepage homepage = 13; optional string personalOrganization = 14; + repeated Setting settings = 15; message Permissions { repeated string global = 1; @@ -133,4 +134,9 @@ message CurrentWsResponse { optional string organization = 3; optional string branch = 4; } + + message Setting { + optional string key = 1; + optional string value = 2; + } } -- 2.39.5