"snapshots", | "snapshots", | ||||
"users", | "users", | ||||
"user_dismissed_messages", | "user_dismissed_messages", | ||||
"user_properties", | |||||
"user_roles", | "user_roles", | ||||
"user_tokens", | "user_tokens", | ||||
"webhooks", | "webhooks", |
import org.sonar.db.user.UserDao; | import org.sonar.db.user.UserDao; | ||||
import org.sonar.db.user.UserDismissedMessagesDao; | import org.sonar.db.user.UserDismissedMessagesDao; | ||||
import org.sonar.db.user.UserGroupDao; | import org.sonar.db.user.UserGroupDao; | ||||
import org.sonar.db.user.UserPropertiesDao; | |||||
import org.sonar.db.user.UserTokenDao; | import org.sonar.db.user.UserTokenDao; | ||||
import org.sonar.db.webhook.WebhookDao; | import org.sonar.db.webhook.WebhookDao; | ||||
import org.sonar.db.webhook.WebhookDeliveryDao; | import org.sonar.db.webhook.WebhookDeliveryDao; | ||||
UserDismissedMessagesDao.class, | UserDismissedMessagesDao.class, | ||||
UserGroupDao.class, | UserGroupDao.class, | ||||
UserPermissionDao.class, | UserPermissionDao.class, | ||||
UserPropertiesDao.class, | |||||
UserTokenDao.class, | UserTokenDao.class, | ||||
WebhookDao.class, | WebhookDao.class, | ||||
WebhookDeliveryDao.class); | WebhookDeliveryDao.class); |
import org.sonar.db.user.UserDao; | import org.sonar.db.user.UserDao; | ||||
import org.sonar.db.user.UserDismissedMessagesDao; | import org.sonar.db.user.UserDismissedMessagesDao; | ||||
import org.sonar.db.user.UserGroupDao; | import org.sonar.db.user.UserGroupDao; | ||||
import org.sonar.db.user.UserPropertiesDao; | |||||
import org.sonar.db.user.UserTokenDao; | import org.sonar.db.user.UserTokenDao; | ||||
import org.sonar.db.webhook.WebhookDao; | import org.sonar.db.webhook.WebhookDao; | ||||
import org.sonar.db.webhook.WebhookDeliveryDao; | import org.sonar.db.webhook.WebhookDeliveryDao; | ||||
private final UserDao userDao; | private final UserDao userDao; | ||||
private final UserGroupDao userGroupDao; | private final UserGroupDao userGroupDao; | ||||
private final UserTokenDao userTokenDao; | private final UserTokenDao userTokenDao; | ||||
private final UserPropertiesDao userPropertiesDao; | |||||
private final GroupMembershipDao groupMembershipDao; | private final GroupMembershipDao groupMembershipDao; | ||||
private final RoleDao roleDao; | private final RoleDao roleDao; | ||||
private final GroupPermissionDao groupPermissionDao; | private final GroupPermissionDao groupPermissionDao; | ||||
userDao = getDao(map, UserDao.class); | userDao = getDao(map, UserDao.class); | ||||
userGroupDao = getDao(map, UserGroupDao.class); | userGroupDao = getDao(map, UserGroupDao.class); | ||||
userTokenDao = getDao(map, UserTokenDao.class); | userTokenDao = getDao(map, UserTokenDao.class); | ||||
userPropertiesDao = getDao(map, UserPropertiesDao.class); | |||||
groupMembershipDao = getDao(map, GroupMembershipDao.class); | groupMembershipDao = getDao(map, GroupMembershipDao.class); | ||||
roleDao = getDao(map, RoleDao.class); | roleDao = getDao(map, RoleDao.class); | ||||
groupPermissionDao = getDao(map, GroupPermissionDao.class); | groupPermissionDao = getDao(map, GroupPermissionDao.class); | ||||
return userTokenDao; | return userTokenDao; | ||||
} | } | ||||
public UserPropertiesDao userPropertiesDao() { | |||||
return userPropertiesDao; | |||||
} | |||||
public GroupMembershipDao groupMembershipDao() { | public GroupMembershipDao groupMembershipDao() { | ||||
return groupMembershipDao; | return groupMembershipDao; | ||||
} | } |
import org.sonar.db.user.UserGroupDto; | import org.sonar.db.user.UserGroupDto; | ||||
import org.sonar.db.user.UserGroupMapper; | import org.sonar.db.user.UserGroupMapper; | ||||
import org.sonar.db.user.UserMapper; | import org.sonar.db.user.UserMapper; | ||||
import org.sonar.db.user.UserPropertiesMapper; | |||||
import org.sonar.db.user.UserTokenCount; | import org.sonar.db.user.UserTokenCount; | ||||
import org.sonar.db.user.UserTokenDto; | import org.sonar.db.user.UserTokenDto; | ||||
import org.sonar.db.user.UserTokenMapper; | import org.sonar.db.user.UserTokenMapper; | ||||
UserGroupMapper.class, | UserGroupMapper.class, | ||||
UserMapper.class, | UserMapper.class, | ||||
UserPermissionMapper.class, | UserPermissionMapper.class, | ||||
UserPropertiesMapper.class, | |||||
UserTokenMapper.class, | UserTokenMapper.class, | ||||
WebhookMapper.class, | WebhookMapper.class, | ||||
WebhookDeliveryMapper.class | WebhookDeliveryMapper.class |
import javax.annotation.CheckForNull; | import javax.annotation.CheckForNull; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.db.property.PropertyDto; | import org.sonar.db.property.PropertyDto; | ||||
import org.sonar.db.user.UserPropertyDto; | |||||
public class PropertyNewValue extends NewValue { | public class PropertyNewValue extends NewValue { | ||||
private String propertyKey; | private String propertyKey; | ||||
@Nullable | @Nullable | ||||
private String qualifier; | private String qualifier; | ||||
public PropertyNewValue(UserPropertyDto userPropertyDto, @Nullable String login) { | |||||
this.propertyKey = userPropertyDto.getKey(); | |||||
this.userUuid = userPropertyDto.getUserUuid(); | |||||
this.userLogin = login; | |||||
setValue(propertyKey, userPropertyDto.getValue()); | |||||
} | |||||
public PropertyNewValue(PropertyDto propertyDto, @Nullable String userLogin, @Nullable String componentKey, @Nullable String componentName, @Nullable String qualifier) { | public PropertyNewValue(PropertyDto propertyDto, @Nullable String userLogin, @Nullable String componentKey, @Nullable String componentName, @Nullable String qualifier) { | ||||
this.propertyKey = propertyDto.getKey(); | this.propertyKey = propertyDto.getKey(); | ||||
this.userUuid = propertyDto.getUserUuid(); | this.userUuid = propertyDto.getUserUuid(); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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 javax.annotation.Nullable; | |||||
import org.sonar.api.utils.System2; | |||||
import org.sonar.core.util.UuidFactory; | |||||
import org.sonar.db.Dao; | |||||
import org.sonar.db.DbSession; | |||||
import org.sonar.db.audit.AuditPersister; | |||||
import org.sonar.db.audit.model.PropertyNewValue; | |||||
public class UserPropertiesDao implements Dao { | |||||
private final System2 system2; | |||||
private final UuidFactory uuidFactory; | |||||
private final AuditPersister auditPersister; | |||||
public UserPropertiesDao(System2 system2, UuidFactory uuidFactory, AuditPersister auditPersister) { | |||||
this.system2 = system2; | |||||
this.uuidFactory = uuidFactory; | |||||
this.auditPersister = auditPersister; | |||||
} | |||||
public List<UserPropertyDto> selectByUser(DbSession session, UserDto user) { | |||||
return mapper(session).selectByUserUuid(user.getUuid()); | |||||
} | |||||
public UserPropertyDto insertOrUpdate(DbSession session, UserPropertyDto dto, @Nullable String login) { | |||||
long now = system2.now(); | |||||
boolean isUpdate = true; | |||||
if (mapper(session).update(dto, now) == 0) { | |||||
mapper(session).insert(dto.setUuid(uuidFactory.create()), now); | |||||
isUpdate = false; | |||||
} | |||||
if (isUpdate) { | |||||
auditPersister.updateProperty(session, new PropertyNewValue(dto, login), true); | |||||
} else { | |||||
auditPersister.addProperty(session, new PropertyNewValue(dto, login), true); | |||||
} | |||||
return dto; | |||||
} | |||||
public void deleteByUser(DbSession session, UserDto user) { | |||||
List<UserPropertyDto> userProperties = selectByUser(session, user); | |||||
int deletedRows = mapper(session).deleteByUserUuid(user.getUuid()); | |||||
if (deletedRows > 0) { | |||||
userProperties.stream() | |||||
.filter(p -> auditPersister.isTrackedProperty(p.getKey())) | |||||
.forEach(p -> auditPersister.deleteProperty(session, new PropertyNewValue(p, user.getLogin()), true)); | |||||
} | |||||
} | |||||
private static UserPropertiesMapper mapper(DbSession session) { | |||||
return session.getMapper(UserPropertiesMapper.class); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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); | |||||
int deleteByUserUuid(@Param("userUuid") String userUuid); | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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; | |||||
} | |||||
} |
<?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> |
CREATE INDEX "UDM_PROJECT_UUID" ON "USER_DISMISSED_MESSAGES"("PROJECT_UUID" NULLS FIRST); | CREATE INDEX "UDM_PROJECT_UUID" ON "USER_DISMISSED_MESSAGES"("PROJECT_UUID" NULLS FIRST); | ||||
CREATE INDEX "UDM_MESSAGE_TYPE" ON "USER_DISMISSED_MESSAGES"("MESSAGE_TYPE" NULLS FIRST); | CREATE INDEX "UDM_MESSAGE_TYPE" ON "USER_DISMISSED_MESSAGES"("MESSAGE_TYPE" NULLS FIRST); | ||||
CREATE TABLE "USER_PROPERTIES"( | |||||
"UUID" CHARACTER VARYING(40) NOT NULL, | |||||
"USER_UUID" CHARACTER VARYING(255) NOT NULL, | |||||
"KEE" CHARACTER VARYING(100) NOT NULL, | |||||
"TEXT_VALUE" CHARACTER VARYING(4000) NOT NULL, | |||||
"CREATED_AT" BIGINT NOT NULL, | |||||
"UPDATED_AT" BIGINT NOT NULL | |||||
); | |||||
ALTER TABLE "USER_PROPERTIES" ADD CONSTRAINT "PK_USER_PROPERTIES" PRIMARY KEY("UUID"); | |||||
CREATE UNIQUE INDEX "USER_PROPERTIES_USER_UUID_KEE" ON "USER_PROPERTIES"("USER_UUID" NULLS FIRST, "KEE" NULLS FIRST); | |||||
CREATE TABLE "USER_ROLES"( | CREATE TABLE "USER_ROLES"( | ||||
"UUID" CHARACTER VARYING(40) NOT NULL, | "UUID" CHARACTER VARYING(40) NOT NULL, | ||||
"ROLE" CHARACTER VARYING(64) NOT NULL, | "ROLE" CHARACTER VARYING(64) NOT NULL, |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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.impl.utils.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"), | |||||
user.getLogin()); | |||||
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"), | |||||
user.getLogin()); | |||||
system2.setNow(2_000_000_000_000L); | |||||
underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey("a_key") | |||||
.setValue("new_value"), | |||||
user.getLogin()); | |||||
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); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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 org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.mockito.ArgumentCaptor; | |||||
import org.sonar.api.impl.utils.TestSystem2; | |||||
import org.sonar.db.DbTester; | |||||
import org.sonar.db.audit.AuditPersister; | |||||
import org.sonar.db.audit.model.PropertyNewValue; | |||||
import static org.assertj.core.api.Assertions.assertThat; | |||||
import static org.mockito.ArgumentMatchers.any; | |||||
import static org.mockito.ArgumentMatchers.eq; | |||||
import static org.mockito.Mockito.mock; | |||||
import static org.mockito.Mockito.times; | |||||
import static org.mockito.Mockito.verify; | |||||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||||
import static org.mockito.Mockito.when; | |||||
public class UserPropertiesDaoWithPersisterTest { | |||||
private static final long NOW = 1_500_000_000_000L; | |||||
private static final String SECURED_PROPERTY_KEY = "a_key.secured"; | |||||
private static final String PROPERTY_KEY = "a_key"; | |||||
private final AuditPersister auditPersister = mock(AuditPersister.class); | |||||
private ArgumentCaptor<PropertyNewValue> newValueCaptor = ArgumentCaptor.forClass(PropertyNewValue.class); | |||||
private TestSystem2 system2 = new TestSystem2().setNow(NOW); | |||||
@Rule | |||||
public DbTester db = DbTester.create(system2, auditPersister); | |||||
private UserPropertiesDao underTest = db.getDbClient().userPropertiesDao(); | |||||
@Test | |||||
public void insertTrackedUserPropertyIsPersisted() { | |||||
UserDto user = db.users().insertUser(); | |||||
verify(auditPersister).addUser(eq(db.getSession()), any()); | |||||
UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(PROPERTY_KEY) | |||||
.setValue("a_value"), | |||||
user.getLogin()); | |||||
verify(auditPersister).addProperty(eq(db.getSession()), newValueCaptor.capture(), eq(true)); | |||||
assertThat(newValueCaptor.getValue()) | |||||
.extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, | |||||
PropertyNewValue::getUserLogin) | |||||
.containsExactly(userSetting.getKey(), userSetting.getValue(), user.getUuid(), user.getLogin()); | |||||
} | |||||
@Test | |||||
public void insertTrackedAndSecuredUserPropertyIsPersisted() { | |||||
UserDto user = db.users().insertUser(); | |||||
verify(auditPersister).addUser(eq(db.getSession()), any()); | |||||
UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(SECURED_PROPERTY_KEY) | |||||
.setValue("a_value"), | |||||
user.getLogin()); | |||||
verify(auditPersister).addProperty(eq(db.getSession()), newValueCaptor.capture(), eq(true)); | |||||
PropertyNewValue newValue = newValueCaptor.getValue(); | |||||
assertThat(newValue) | |||||
.extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, | |||||
PropertyNewValue::getUserLogin) | |||||
.containsExactly(userSetting.getKey(), null, user.getUuid(), user.getLogin()); | |||||
assertThat(newValue.toString()).doesNotContain("propertyValue"); | |||||
} | |||||
@Test | |||||
public void updateTrackedUserPropertyIsPersisted() { | |||||
UserDto user = db.users().insertUser(); | |||||
underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(PROPERTY_KEY) | |||||
.setValue("old_value"), | |||||
user.getLogin()); | |||||
system2.setNow(2_000_000_000_000L); | |||||
UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(PROPERTY_KEY) | |||||
.setValue("new_value"), | |||||
user.getLogin()); | |||||
verify(auditPersister).addUser(eq(db.getSession()), any()); | |||||
verify(auditPersister).addProperty(eq(db.getSession()), any(), eq(true)); | |||||
verify(auditPersister).updateProperty(eq(db.getSession()), newValueCaptor.capture(), eq(true)); | |||||
assertThat(newValueCaptor.getValue()) | |||||
.extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, | |||||
PropertyNewValue::getUserLogin) | |||||
.containsExactly(userSetting.getKey(), userSetting.getValue(), user.getUuid(), user.getLogin()); | |||||
} | |||||
@Test | |||||
public void deleteTrackedUserPropertyIsPersisted() { | |||||
when(auditPersister.isTrackedProperty(PROPERTY_KEY)).thenReturn(true); | |||||
when(auditPersister.isTrackedProperty(SECURED_PROPERTY_KEY)).thenReturn(false); | |||||
UserDto user = db.users().insertUser(); | |||||
UserPropertyDto userSetting = underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(PROPERTY_KEY) | |||||
.setValue("a_value"), | |||||
user.getLogin()); | |||||
underTest.insertOrUpdate(db.getSession(), new UserPropertyDto() | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(SECURED_PROPERTY_KEY) | |||||
.setValue("another_value"), | |||||
user.getLogin()); | |||||
underTest.deleteByUser(db.getSession(), user); | |||||
verify(auditPersister).addUser(eq(db.getSession()), any()); | |||||
verify(auditPersister, times(2)).addProperty(eq(db.getSession()), any(), eq(true)); | |||||
verify(auditPersister).isTrackedProperty(PROPERTY_KEY); | |||||
verify(auditPersister).isTrackedProperty(SECURED_PROPERTY_KEY); | |||||
verify(auditPersister).deleteProperty(eq(db.getSession()), newValueCaptor.capture(), eq(true)); | |||||
verifyNoMoreInteractions(auditPersister); | |||||
assertThat(newValueCaptor.getValue()) | |||||
.extracting(PropertyNewValue::getPropertyKey, PropertyNewValue::getPropertyValue, PropertyNewValue::getUserUuid, | |||||
PropertyNewValue::getUserLogin) | |||||
.containsExactly(userSetting.getKey(), userSetting.getValue(), user.getUuid(), user.getLogin()); | |||||
} | |||||
@Test | |||||
public void deleteTrackedUserPropertyWithoutAffectedRowsIsNotPersisted() { | |||||
UserDto user = db.users().insertUser(); | |||||
underTest.deleteByUser(db.getSession(), user); | |||||
verify(auditPersister).addUser(eq(db.getSession()), any()); | |||||
verifyNoMoreInteractions(auditPersister); | |||||
} | |||||
} |
return Optional.ofNullable(dbClient.userDao().selectByExternalLoginAndIdentityProvider(db.getSession(), login, identityProvider)); | return Optional.ofNullable(dbClient.userDao().selectByExternalLoginAndIdentityProvider(db.getSession(), login, identityProvider)); | ||||
} | } | ||||
// USER SETTINGS | |||||
@SafeVarargs | |||||
public final UserPropertyDto insertUserSetting(UserDto user, Consumer<UserPropertyDto>... populators) { | |||||
UserPropertyDto dto = UserTesting.newUserSettingDto(user); | |||||
stream(populators).forEach(p -> p.accept(dto)); | |||||
dbClient.userPropertiesDao().insertOrUpdate(db.getSession(), dto, user.getLogin()); | |||||
db.commit(); | |||||
return dto; | |||||
} | |||||
// GROUPS | // GROUPS | ||||
public GroupDto insertGroup(String name) { | public GroupDto insertGroup(String name) { |
package org.sonar.db.user; | package org.sonar.db.user; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.core.util.Uuids; | |||||
import static java.util.Collections.singletonList; | import static java.util.Collections.singletonList; | ||||
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | ||||
.setCryptedPassword(null) | .setCryptedPassword(null) | ||||
.setSalt(null); | .setSalt(null); | ||||
} | } | ||||
public static UserPropertyDto newUserSettingDto(UserDto user) { | |||||
return new UserPropertyDto() | |||||
.setUuid(Uuids.createFast()) | |||||
.setUserUuid(user.getUuid()) | |||||
.setKey(randomAlphanumeric(20)) | |||||
.setValue(randomAlphanumeric(100)); | |||||
} | |||||
} | } |
.add(6303, "Drop unused Issues Column ISSUE_ATTRIBUTES", DropIssuesAttributesIssueColumn.class) | .add(6303, "Drop unused Issues Column ISSUE_ATTRIBUTES", DropIssuesAttributesIssueColumn.class) | ||||
.add(6304, "Create table 'SCANNER_ANALYSIS_CACHE", CreateScannerAnalysisCacheTable.class) | .add(6304, "Create table 'SCANNER_ANALYSIS_CACHE", CreateScannerAnalysisCacheTable.class) | ||||
.add(6305, "Issue warning for users using SHA1 hash method", SelectUsersWithSha1HashMethod.class) | .add(6305, "Issue warning for users using SHA1 hash method", SelectUsersWithSha1HashMethod.class) | ||||
.add(6306, "Drop table 'user_properties'", DropUserPropertiesTable.class) | |||||
; | ; | ||||
} | } | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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.v94; | |||||
import java.sql.SQLException; | |||||
import org.sonar.db.Database; | |||||
import org.sonar.db.DatabaseUtils; | |||||
import org.sonar.server.platform.db.migration.sql.DropTableBuilder; | |||||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||||
public class DropUserPropertiesTable extends DdlChange { | |||||
private static final String TABLE_NAME = "user_properties"; | |||||
public DropUserPropertiesTable(Database db) { | |||||
super(db); | |||||
} | |||||
@Override | |||||
public void execute(Context context) throws SQLException { | |||||
if (tableExists()) { | |||||
context.execute(new DropTableBuilder(getDialect(), TABLE_NAME).build()); | |||||
} | |||||
} | |||||
private boolean tableExists() throws SQLException { | |||||
try (var connection = getDatabase().getDataSource().getConnection()) { | |||||
return DatabaseUtils.tableExists(TABLE_NAME, connection); | |||||
} | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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.v94; | |||||
import java.sql.SQLException; | |||||
import org.junit.Rule; | |||||
import org.junit.Test; | |||||
import org.sonar.db.CoreDbTester; | |||||
public class DropUserPropertiesTableTest { | |||||
private static final String TABLE_NAME = "user_properties"; | |||||
@Rule | |||||
public final CoreDbTester db = CoreDbTester.createForSchema(DropUserPropertiesTableTest.class, "schema.sql"); | |||||
private final DropUserPropertiesTable underTest = new DropUserPropertiesTable(db.database()); | |||||
@Test | |||||
public void migration_should_drop_table() throws SQLException { | |||||
db.assertTableExists(TABLE_NAME); | |||||
underTest.execute(); | |||||
db.assertTableDoesNotExist(TABLE_NAME); | |||||
} | |||||
@Test | |||||
public void migration_should_be_reentrant() throws SQLException { | |||||
db.assertTableExists(TABLE_NAME); | |||||
underTest.execute(); | |||||
// re-entrant | |||||
underTest.execute(); | |||||
db.assertTableDoesNotExist(TABLE_NAME); | |||||
} | |||||
} |
CREATE TABLE "USER_PROPERTIES"( | |||||
"UUID" CHARACTER VARYING(40) NOT NULL, | |||||
"USER_UUID" CHARACTER VARYING(255) NOT NULL, | |||||
"KEE" CHARACTER VARYING(100) NOT NULL, | |||||
"TEXT_VALUE" CHARACTER VARYING(4000) NOT NULL, | |||||
"CREATED_AT" BIGINT NOT NULL, | |||||
"UPDATED_AT" BIGINT NOT NULL | |||||
); | |||||
ALTER TABLE "USER_PROPERTIES" ADD CONSTRAINT "PK_USER_PROPERTIES" PRIMARY KEY("UUID"); |
assertThat(dbClient.userDao().selectByLogin(session, "user").isOnboarded()).isFalse(); | assertThat(dbClient.userDao().selectByLogin(session, "user").isOnboarded()).isFalse(); | ||||
} | } | ||||
@Test | |||||
public void does_not_set_notifications_readDate_setting_when_creating_user_when_not_on() { | |||||
createDefaultGroup(); | |||||
UserDto user = underTest.createAndCommit(db.getSession(), NewUser.builder() | |||||
.setLogin("userLogin") | |||||
.setName("UserName") | |||||
.build(), u -> { | |||||
}); | |||||
assertThat(dbClient.userPropertiesDao().selectByUser(session, user)).isEmpty(); | |||||
} | |||||
@Test | @Test | ||||
public void create_user_and_index_other_user() { | public void create_user_and_index_other_user() { | ||||
createDefaultGroup(); | createDefaultGroup(); |
assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isFalse(); | assertThat(dbClient.userDao().selectByLogin(session, user.getLogin()).isOnboarded()).isFalse(); | ||||
} | } | ||||
@Test | |||||
public void does_not_set_notifications_readDate_setting_when_reactivating_user() { | |||||
createDefaultGroup(); | |||||
UserDto user = db.users().insertDisabledUser(); | |||||
underTest.reactivateAndCommit(db.getSession(), user, NewUser.builder() | |||||
.setLogin(user.getLogin()) | |||||
.setName(user.getName()) | |||||
.build(), u -> { | |||||
}); | |||||
assertThat(dbClient.userPropertiesDao().selectByUser(session, user)).isEmpty(); | |||||
} | |||||
@Test | @Test | ||||
public void fail_to_reactivate_user_when_login_already_exists() { | public void fail_to_reactivate_user_when_login_already_exists() { | ||||
createDefaultGroup(); | createDefaultGroup(); |
import org.sonar.api.server.ws.WebService.NewController; | import org.sonar.api.server.ws.WebService.NewController; | ||||
import org.sonar.core.platform.EditionProvider; | import org.sonar.core.platform.EditionProvider; | ||||
import org.sonar.core.platform.PlatformEditionProvider; | import org.sonar.core.platform.PlatformEditionProvider; | ||||
import org.sonar.core.util.stream.MoreCollectors; | |||||
import org.sonar.db.DbClient; | import org.sonar.db.DbClient; | ||||
import org.sonar.db.DbSession; | import org.sonar.db.DbSession; | ||||
import org.sonar.db.component.BranchDto; | import org.sonar.db.component.BranchDto; | ||||
import static org.apache.commons.lang.StringUtils.EMPTY; | import static org.apache.commons.lang.StringUtils.EMPTY; | ||||
import static org.sonar.api.web.UserRole.USER; | import static org.sonar.api.web.UserRole.USER; | ||||
import static org.sonar.server.ws.WsUtils.writeProtobuf; | import static org.sonar.server.ws.WsUtils.writeProtobuf; | ||||
import static org.sonarqube.ws.Users.CurrentWsResponse.Permissions; | |||||
import static org.sonarqube.ws.Users.CurrentWsResponse.newBuilder; | |||||
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.APPLICATION; | import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.APPLICATION; | ||||
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PORTFOLIO; | import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PORTFOLIO; | ||||
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PROJECT; | import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PROJECT; | ||||
import static org.sonarqube.ws.Users.CurrentWsResponse.Permissions; | |||||
import static org.sonarqube.ws.Users.CurrentWsResponse.newBuilder; | |||||
import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CURRENT; | import static org.sonarqube.ws.client.user.UsersWsParameters.ACTION_CURRENT; | ||||
public class CurrentAction implements UsersWsAction { | public class CurrentAction implements UsersWsAction { | ||||
.setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) | .setPermissions(Permissions.newBuilder().addAllGlobal(getGlobalPermissions()).build()) | ||||
.setHomepage(buildHomepage(dbSession, user)) | .setHomepage(buildHomepage(dbSession, user)) | ||||
.setShowOnboardingTutorial(!user.isOnboarded()) | .setShowOnboardingTutorial(!user.isOnboarded()) | ||||
.addAllSettings(loadUserSettings(dbSession, user)) | |||||
.setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null) | .setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null) | ||||
.setSonarLintAdSeen(user.isSonarlintAdSeen()); | .setSonarLintAdSeen(user.isSonarlintAdSeen()); | ||||
ofNullable(emptyToNull(user.getEmail())).ifPresent(builder::setEmail); | ofNullable(emptyToNull(user.getEmail())).ifPresent(builder::setEmail); | ||||
.build(); | .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) { | private static boolean noHomepageSet(UserDto user) { | ||||
return user.getHomepageType() == null; | return user.getHomepageType() == null; | ||||
} | } |
dbClient.userPermissionDao().deleteByUserUuid(dbSession, user); | dbClient.userPermissionDao().deleteByUserUuid(dbSession, user); | ||||
dbClient.permissionTemplateDao().deleteUserPermissionsByUserUuid(dbSession, userUuid, user.getLogin()); | dbClient.permissionTemplateDao().deleteUserPermissionsByUserUuid(dbSession, userUuid, user.getLogin()); | ||||
dbClient.qProfileEditUsersDao().deleteByUser(dbSession, user); | dbClient.qProfileEditUsersDao().deleteByUser(dbSession, user); | ||||
dbClient.userPropertiesDao().deleteByUser(dbSession, user); | |||||
dbClient.almPatDao().deleteByUser(dbSession, user); | dbClient.almPatDao().deleteByUser(dbSession, user); | ||||
dbClient.sessionTokensDao().deleteByUser(dbSession, user); | dbClient.sessionTokensDao().deleteByUser(dbSession, user); | ||||
dbClient.userDismissedMessagesDao().deleteByUser(dbSession, user); | dbClient.userDismissedMessagesDao().deleteByUser(dbSession, user); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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( | |||||
"tutorials.jenkins.skipBitbucketPreReqs", | |||||
"notifications.optOut"); | |||||
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), | |||||
userSession.getLogin()); | |||||
dbSession.commit(); | |||||
} | |||||
} | |||||
} |
UserJsonWriter.class, | UserJsonWriter.class, | ||||
SetHomepageAction.class, | SetHomepageAction.class, | ||||
HomepageTypesImpl.class, | HomepageTypesImpl.class, | ||||
SetSettingAction.class, | |||||
UpdateIdentityProviderAction.class); | UpdateIdentityProviderAction.class); | ||||
} | } |
*/ | */ | ||||
package org.sonar.server.user.ws; | package org.sonar.server.user.ws; | ||||
import java.util.Collections; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.resources.Qualifiers; | import org.sonar.api.resources.Qualifiers; | ||||
import static com.google.common.collect.Lists.newArrayList; | import static com.google.common.collect.Lists.newArrayList; | ||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||||
import static org.assertj.core.api.Assertions.tuple; | |||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
import static org.sonar.api.web.UserRole.USER; | import static org.sonar.api.web.UserRole.USER; | ||||
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; | import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES; | ||||
assertThat(response) | assertThat(response) | ||||
.extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal, | .extracting(CurrentWsResponse::getIsLoggedIn, CurrentWsResponse::getLogin, CurrentWsResponse::getName, CurrentWsResponse::hasAvatar, CurrentWsResponse::getLocal, | ||||
CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getSettingsList, | |||||
CurrentWsResponse::getUsingSonarLintConnectedMode, CurrentWsResponse::getSonarLintAdSeen) | |||||
.containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", Collections.emptyList(), false, false); | |||||
CurrentWsResponse::getExternalIdentity, CurrentWsResponse::getExternalProvider, CurrentWsResponse::getUsingSonarLintConnectedMode, CurrentWsResponse::getSonarLintAdSeen) | |||||
.containsExactly(true, "obiwan.kenobi", "Obiwan Kenobi", false, true, "obiwan", "sonarqube", false, false); | |||||
assertThat(response.hasEmail()).isFalse(); | assertThat(response.hasEmail()).isFalse(); | ||||
assertThat(response.getScmAccountsList()).isEmpty(); | assertThat(response.getScmAccountsList()).isEmpty(); | ||||
assertThat(response.getGroupsList()).isEmpty(); | assertThat(response.getGroupsList()).isEmpty(); | ||||
assertThat(response.getPermissions().getGlobalList()).containsOnly("profileadmin", "scan"); | assertThat(response.getPermissions().getGlobalList()).containsOnly("profileadmin", "scan"); | ||||
} | } | ||||
@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) | |||||
.containsExactlyInAnyOrder( | |||||
tuple("notifications.optOut", "true"), | |||||
tuple("notifications.readDate", "1234")); | |||||
} | |||||
@Test | @Test | ||||
public void fail_with_ISE_when_user_login_in_db_does_not_exist() { | public void fail_with_ISE_when_user_login_in_db_does_not_exist() { | ||||
db.users().insertUser(usert -> usert.setLogin("another")); | db.users().insertUser(usert -> usert.setLogin("another")); |
assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().build(), db.getSession())).extracting(PropertyDto::getKey).containsOnly("other"); | assertThat(db.getDbClient().propertiesDao().selectByQuery(PropertyQuery.builder().build(), db.getSession())).extracting(PropertyDto::getKey).containsOnly("other"); | ||||
} | } | ||||
@Test | |||||
public void deactivate_user_deletes_their_user_settings() { | |||||
createAdminUser(); | |||||
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 | @Test | ||||
public void deactivate_user_deletes_their_qgate_permissions() { | public void deactivate_user_deletes_their_qgate_permissions() { | ||||
createAdminUser(); | createAdminUser(); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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.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.assertThatThrownBy; | |||||
import static org.assertj.core.api.Assertions.tuple; | |||||
public class SetSettingActionTest { | |||||
@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() { | |||||
assertThatThrownBy(() -> { | |||||
ws.newRequest() | |||||
.setParam("key", "notifications.optOut") | |||||
.setParam("value", "true") | |||||
.execute(); | |||||
}) | |||||
.isInstanceOf(UnauthorizedException.class); | |||||
} | |||||
@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( | |||||
"tutorials.jenkins.skipBitbucketPreReqs", | |||||
"notifications.optOut"); | |||||
} | |||||
} |
public void verify_count_of_added_components() { | public void verify_count_of_added_components() { | ||||
ListContainer container = new ListContainer(); | ListContainer container = new ListContainer(); | ||||
new UsersWsModule().configure(container); | new UsersWsModule().configure(container); | ||||
assertThat(container.getAddedObjects()).hasSize(16); | |||||
assertThat(container.getAddedObjects()).hasSize(15); | |||||
} | } | ||||
} | } |
import org.sonarqube.ws.client.timemachine.TimemachineService; | import org.sonarqube.ws.client.timemachine.TimemachineService; | ||||
import org.sonarqube.ws.client.updatecenter.UpdatecenterService; | import org.sonarqube.ws.client.updatecenter.UpdatecenterService; | ||||
import org.sonarqube.ws.client.usergroups.UserGroupsService; | import org.sonarqube.ws.client.usergroups.UserGroupsService; | ||||
import org.sonarqube.ws.client.userproperties.UserPropertiesService; | |||||
import org.sonarqube.ws.client.users.UsersService; | import org.sonarqube.ws.client.users.UsersService; | ||||
import org.sonarqube.ws.client.usertokens.UserTokensService; | import org.sonarqube.ws.client.usertokens.UserTokensService; | ||||
import org.sonarqube.ws.client.views.ViewsService; | import org.sonarqube.ws.client.views.ViewsService; | ||||
private final TimemachineService timemachineService; | private final TimemachineService timemachineService; | ||||
private final UpdatecenterService updatecenterService; | private final UpdatecenterService updatecenterService; | ||||
private final UserGroupsService userGroupsService; | private final UserGroupsService userGroupsService; | ||||
private final UserPropertiesService userPropertiesService; | |||||
private final UserTokensService userTokensService; | private final UserTokensService userTokensService; | ||||
private final UsersService usersService; | private final UsersService usersService; | ||||
private final ViewsService viewsService; | private final ViewsService viewsService; | ||||
this.timemachineService = new TimemachineService(wsConnector); | this.timemachineService = new TimemachineService(wsConnector); | ||||
this.updatecenterService = new UpdatecenterService(wsConnector); | this.updatecenterService = new UpdatecenterService(wsConnector); | ||||
this.userGroupsService = new UserGroupsService(wsConnector); | this.userGroupsService = new UserGroupsService(wsConnector); | ||||
this.userPropertiesService = new UserPropertiesService(wsConnector); | |||||
this.userTokensService = new UserTokensService(wsConnector); | this.userTokensService = new UserTokensService(wsConnector); | ||||
this.usersService = new UsersService(wsConnector); | this.usersService = new UsersService(wsConnector); | ||||
this.viewsService = new ViewsService(wsConnector); | this.viewsService = new ViewsService(wsConnector); | ||||
return userGroupsService; | return userGroupsService; | ||||
} | } | ||||
@Override | |||||
public UserPropertiesService userProperties() { | |||||
return userPropertiesService; | |||||
} | |||||
@Override | @Override | ||||
public UserTokensService userTokens() { | public UserTokensService userTokens() { | ||||
return userTokensService; | return userTokensService; |
import org.sonarqube.ws.client.timemachine.TimemachineService; | import org.sonarqube.ws.client.timemachine.TimemachineService; | ||||
import org.sonarqube.ws.client.updatecenter.UpdatecenterService; | import org.sonarqube.ws.client.updatecenter.UpdatecenterService; | ||||
import org.sonarqube.ws.client.usergroups.UserGroupsService; | import org.sonarqube.ws.client.usergroups.UserGroupsService; | ||||
import org.sonarqube.ws.client.userproperties.UserPropertiesService; | |||||
import org.sonarqube.ws.client.users.UsersService; | import org.sonarqube.ws.client.users.UsersService; | ||||
import org.sonarqube.ws.client.usertokens.UserTokensService; | import org.sonarqube.ws.client.usertokens.UserTokensService; | ||||
import org.sonarqube.ws.client.views.ViewsService; | import org.sonarqube.ws.client.views.ViewsService; | ||||
UserGroupsService userGroups(); | UserGroupsService userGroups(); | ||||
UserPropertiesService userProperties(); | |||||
UserTokensService userTokens(); | UserTokensService userTokens(); | ||||
UsersService users(); | UsersService users(); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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.userproperties; | |||||
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.WsConnector; | |||||
/** | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/user_properties">Further information about this web service online</a> | |||||
*/ | |||||
@Generated("sonar-ws-generator") | |||||
public class UserPropertiesService extends BaseService { | |||||
public UserPropertiesService(WsConnector wsConnector) { | |||||
super(wsConnector, "api/user_properties"); | |||||
} | |||||
/** | |||||
* | |||||
* This is part of the internal API. | |||||
* This is a GET request. | |||||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/user_properties/index">Further information about this action online (including a response example)</a> | |||||
* @since 2.6 | |||||
* @deprecated since 6.3 | |||||
*/ | |||||
@Deprecated | |||||
public String index() { | |||||
return call( | |||||
new GetRequest(path("index")) | |||||
.setMediaType(MediaTypes.JSON) | |||||
).content(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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 | |||||
@Generated("sonar-ws-generator") | |||||
package org.sonarqube.ws.client.userproperties; | |||||
import javax.annotation.ParametersAreNonnullByDefault; | |||||
import javax.annotation.Generated; | |||||
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2022 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>"tutorials.jenkins.skipBitbucketPreReqs"</li> | |||||
* <li>"notifications.optOut"</li> | |||||
* <li>"notifications.readDate"</li> | |||||
* </ul> | |||||
*/ | |||||
public SetSettingRequest setValue(String value) { | |||||
this.value = value; | |||||
return this; | |||||
} | |||||
public String getValue() { | |||||
return value; | |||||
} | |||||
} |
.setMediaType(MediaTypes.JSON)).content(); | .setMediaType(MediaTypes.JSON)).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. | * This is part of the internal API. |
optional bool showOnboardingTutorial = 11; | optional bool showOnboardingTutorial = 11; | ||||
optional string avatar = 12; | optional string avatar = 12; | ||||
optional Homepage homepage = 13; | optional Homepage homepage = 13; | ||||
repeated Setting settings = 15; | |||||
reserved 15; // settings removed | |||||
optional bool usingSonarLintConnectedMode = 16; | optional bool usingSonarLintConnectedMode = 16; | ||||
optional bool sonarLintAdSeen = 17; | optional bool sonarLintAdSeen = 17; | ||||