Browse Source

SONAR-11579 Create WS to store/load user settings

tags/7.6
Julien Lancelot 5 years ago
parent
commit
347f6c6744
37 changed files with 1202 additions and 94 deletions
  1. 2
    2
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  2. 1
    0
      server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java
  3. 12
    0
      server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
  4. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java
  5. 7
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java
  6. 2
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
  7. 58
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java
  8. 35
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesMapper.java
  9. 80
    0
      server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertyDto.java
  10. 51
    0
      server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserPropertiesMapper.xml
  11. 1
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java
  12. 9
    17
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java
  13. 12
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java
  14. 127
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java
  15. 9
    0
      server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java
  16. 2
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
  17. 58
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTable.java
  18. 94
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTable.java
  19. 33
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java
  20. 24
    0
      server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/package-info.java
  21. 1
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java
  22. 0
    1
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java
  23. 55
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest.java
  24. 63
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTableTest.java
  25. 41
    0
      server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java
  26. 10
    0
      server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest/user_properties.sql
  27. 13
    1
      server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java
  28. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java
  29. 88
    0
      server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java
  30. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java
  31. 44
    21
      server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java
  32. 44
    44
      server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java
  33. 130
    0
      server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java
  34. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java
  35. 64
    0
      sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetSettingRequest.java
  36. 20
    4
      sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java
  37. 6
    0
      sonar-ws/src/main/protobuf/ws-users.proto

+ 2
- 2
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java View File

@@ -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

+ 1
- 0
server/sonar-db-core/src/main/java/org/sonar/db/version/SqTables.java View File

@@ -108,6 +108,7 @@ public final class SqTables {
"schema_migrations",
"snapshots",
"users",
"user_properties",
"user_roles",
"user_tokens",
"webhooks",

+ 12
- 0
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl View File

@@ -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");

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/DaoModule.java View File

@@ -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));

+ 7
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/DbClient.java View File

@@ -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;
}

+ 2
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java View File

@@ -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

+ 58
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesDao.java View File

@@ -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<UserPropertyDto> 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);
}

}

+ 35
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertiesMapper.java View File

@@ -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<UserPropertyDto> 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);

}

+ 80
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/user/UserPropertyDto.java View File

@@ -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;
}

}

+ 51
- 0
server/sonar-db-dao/src/main/resources/org/sonar/db/user/UserPropertiesMapper.xml View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd">

<mapper namespace="org.sonar.db.user.UserPropertiesMapper">

<sql id="userPropertiesColumns">
us.uuid as uuid,
us.user_uuid as userUuid,
us.kee as "key",
us.text_value as "value"
</sql>

<select id="selectByUserUuid" parameterType="String" resultType="org.sonar.db.user.UserPropertyDto">
SELECT
<include refid="userPropertiesColumns"/>
FROM user_properties us
WHERE us.user_uuid=#{userUuid}
</select>

<insert id="insert" parameterType="map" useGeneratedKeys="false">
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}
)
</insert>

<update id="update" parameterType="map">
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}
</update>

<update id="deleteByUserUuid" parameterType="String">
DELETE FROM user_properties WHERE user_uuid=#{userUuid,jdbcType=VARCHAR}
</update>

</mapper>

+ 1
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/DaoModuleTest.java View File

@@ -30,6 +30,6 @@ public class DaoModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new DaoModule().configure(container);
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 59);
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 60);
}
}

+ 9
- 17
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDaoTest.java View File

@@ -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());

+ 12
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserDbTester.java View File

@@ -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<UserPropertyDto>... 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) {

+ 127
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserPropertiesDaoTest.java View File

@@ -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<UserPropertyDto> 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<String, Object> 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<String, Object> 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);
}
}

+ 9
- 0
server/sonar-db-dao/src/test/java/org/sonar/db/user/UserTesting.java View File

@@ -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));
}
}

+ 2
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java View File

@@ -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,

+ 58
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTable.java View File

@@ -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());
}

}

+ 94
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTable.java View File

@@ -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);
}
}
}

+ 33
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76.java View File

@@ -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);
}
}

+ 24
- 0
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v76/package-info.java View File

@@ -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;


+ 1
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java View File

@@ -37,7 +37,7 @@ public class MigrationConfigurationModuleTest {
assertThat(container.getPicoContainer().getComponentAdapters())
.hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER
// DbVersion classes
+ 16
+ 17
// Others
+ 3);
}

+ 0
- 1
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v75/CreateOrganizationsAlmBindingsTableTest.java View File

@@ -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");
}

}

+ 55
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest.java View File

@@ -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();
}

}

+ 63
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/CreateUserPropertiesTableTest.java View File

@@ -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);
}
}

+ 41
- 0
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v76/DbVersion76Test.java View File

@@ -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);
}

}

+ 10
- 0
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v76/AddUniqueIndexInUserPropertiesTableTest/user_properties.sql View File

@@ -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")
);

+ 13
- 1
server/sonar-server/src/main/java/org/sonar/server/user/ws/CurrentAction.java View File

@@ -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<CurrentWsResponse.Setting> 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;
}

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/user/ws/DeactivateAction.java View File

@@ -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);
}

+ 88
- 0
server/sonar-server/src/main/java/org/sonar/server/user/ws/SetSettingAction.java View File

@@ -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.<br>" +
"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();
}
}

}

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/user/ws/UsersWsModule.java View File

@@ -38,6 +38,7 @@ public class UsersWsModule extends Module {
UserJsonWriter.class,
SkipOnboardingTutorialAction.class,
SetHomepageAction.class,
HomepageTypesImpl.class);
HomepageTypesImpl.class,
SetSettingAction.class);
}
}

+ 44
- 21
server/sonar-server/src/test/java/org/sonar/server/user/ws/CurrentActionTest.java View File

@@ -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);


+ 44
- 44
server/sonar-server/src/test/java/org/sonar/server/user/ws/DeactivateActionTest.java View File

@@ -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<UserDto>... 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();
}

+ 130
- 0
server/sonar-server/src/test/java/org/sonar/server/user/ws/SetSettingActionTest.java View File

@@ -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");
}

}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/user/ws/UsersWsModuleTest.java View File

@@ -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);
}
}

+ 64
- 0
sonar-ws/src/main/java/org/sonarqube/ws/client/users/SetSettingRequest.java View File

@@ -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 <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/set_setting">Further information about this action online (including a response example)</a>
* @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:
* <ul>
* <li>"notifications.optOut"</li>
* <li>"notifications.readDate"</li>
* </ul>
*/
public SetSettingRequest setValue(String value) {
this.value = value;
return this;
}

public String getValue() {
return value;
}
}

+ 20
- 4
sonar-ws/src/main/java/org/sonarqube/ws/client/users/UsersService.java View File

@@ -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 <a href="https://next.sonarqube.com/sonarqube/web_api/api/users">Further information about this web service online</a>
@@ -172,6 +172,22 @@ public class UsersService extends BaseService {
).content();
}

/**
*
* This is part of the internal API.
* This is a POST request.
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/users/set_setting">Further information about this action online (including a response example)</a>
* @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.

+ 6
- 0
sonar-ws/src/main/protobuf/ws-users.proto View File

@@ -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;
}
}

Loading…
Cancel
Save