From f5b029d43effae31d62eec60327652c4f511e40a Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 20 Feb 2023 19:18:53 +0200 Subject: [PATCH] moved password history from tb_user to user_credential --- .../main/data/upgrade/3.4.4/schema_update.sql | 9 +---- .../install/SqlDatabaseUpgradeService.java | 31 ++++++++++++++ .../system/DefaultSystemSecurityService.java | 3 +- .../server/dao/user/UserService.java | 2 + .../common/data/security/UserCredentials.java | 23 ++++++++++- .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/UserCredentialsEntity.java | 8 ++++ .../server/dao/sql/user/JpaUserDao.java | 7 ++++ .../thingsboard/server/dao/user/UserDao.java | 8 ++++ .../server/dao/user/UserServiceImpl.java | 40 ++++++++++--------- .../main/resources/sql/schema-entities.sql | 3 +- 11 files changed, 105 insertions(+), 30 deletions(-) diff --git a/application/src/main/data/upgrade/3.4.4/schema_update.sql b/application/src/main/data/upgrade/3.4.4/schema_update.sql index 9ecc68e47b..418e4066fc 100644 --- a/application/src/main/data/upgrade/3.4.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.4/schema_update.sql @@ -32,11 +32,4 @@ CREATE TABLE IF NOT EXISTS user_settings ( ); ALTER TABLE user_credentials - ADD COLUMN IF NOT EXISTS additional_info varchar; - -UPDATE user_credentials AS c - SET additional_info = u.additional_info FROM tb_user AS u - WHERE u.id = c.user_id AND u.additional_info is not null; - -UPDATE tb_user - SET additional_info = null WHERE id is not null; \ No newline at end of file + ADD COLUMN IF NOT EXISTS additional_info varchar NULL; \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index b9ff5383b5..e91bad3e18 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.install; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -24,8 +26,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -34,6 +38,7 @@ import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.common.data.queue.SubmitStrategyType; +import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.dashboard.DashboardService; @@ -44,6 +49,7 @@ import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.sql.tenant.TenantRepository; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; +import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.service.install.sql.SqlDbHelper; @@ -110,6 +116,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @Autowired private TenantService tenantService; + @Autowired + private UserService userService; + @Autowired private TenantRepository tenantRepository; @@ -701,6 +710,28 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception e) { } + PageLink pageLink = new PageLink(1000); + PageData users; + do { + List> futures = new ArrayList<>(); + users = userService.findAllUsers(pageLink); + for (User user : users.getData()) { + futures.add(dbUpgradeExecutor.submit(() -> { + JsonNode additionalInfo = user.getAdditionalInfo(); + if (additionalInfo.isObject() && additionalInfo.has("userPasswordHistory")){ + UserCredentials creds = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); + if (creds != null) { + creds.setAdditionalInfo(JacksonUtil.newObjectNode().set("userPasswordHistory", additionalInfo.get("userPasswordHistory"))); + userService.saveUserCredentials(user.getTenantId(), creds); + } + ((ObjectNode) additionalInfo).remove("userPasswordHistory"); + userService.saveUser(user); + }})); + } + Futures.allAsList(futures).get(); + pageLink = pageLink.nextPageLink(); + } while (users.hasNext()); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005000;"); } log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 1a81c6617f..726a5d98f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -228,8 +228,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { if (userCredentials != null && isPositiveInteger(passwordPolicy.getPasswordReuseFrequencyDays())) { long passwordReuseFrequencyTs = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(passwordPolicy.getPasswordReuseFrequencyDays()); - User user = userService.findUserById(tenantId, userCredentials.getUserId()); - JsonNode additionalInfo = user.getAdditionalInfo(); + JsonNode additionalInfo = userCredentials.getAdditionalInfo(); if (additionalInfo instanceof ObjectNode && additionalInfo.has(UserServiceImpl.USER_PASSWORD_HISTORY)) { JsonNode userPasswordHistoryJson = additionalInfo.get(UserServiceImpl.USER_PASSWORD_HISTORY); Map userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {}); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index bb40507772..a981292d82 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -58,6 +58,8 @@ public interface UserService extends EntityDaoService { PageData findUsersByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAllUsers(PageLink pageLink); + PageData findTenantAdmins(TenantId tenantId, PageLink pageLink); void deleteTenantAdmins(TenantId tenantId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java index 16cf5f090d..5719e46495 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java @@ -24,10 +24,12 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; +import java.util.Arrays; +import java.util.Objects; + import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.getJson; import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.setJson; -@EqualsAndHashCode(callSuper = true) public class UserCredentials extends BaseData { private static final long serialVersionUID = -2108436378880529163L; @@ -67,6 +69,7 @@ public class UserCredentials extends BaseData { this.enabled = userCredentials.isEnabled(); this.activateToken = userCredentials.getActivateToken(); this.resetToken = userCredentials.getResetToken(); + setAdditionalInfo(userCredentials.getAdditionalInfo()); } public UserId getUserId() { @@ -109,6 +112,24 @@ public class UserCredentials extends BaseData { this.resetToken = resetToken; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + UserCredentials that = (UserCredentials) o; + return enabled == that.enabled && userId.equals(that.userId) && Objects.equals(password, that.password) + && Objects.equals(activateToken, that.activateToken) && Objects.equals(resetToken, that.resetToken) + && Arrays.equals(additionalInfoBytes, that.additionalInfoBytes); + } + + @Override + public int hashCode() { + int result = Objects.hash(super.hashCode(), userId, enabled, password, activateToken, resetToken); + result = 31 * result + Arrays.hashCode(additionalInfoBytes); + return result; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index cf953583d1..526876adba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -81,6 +81,7 @@ public class ModelConstants { public static final String USER_CREDENTIALS_PASSWORD_PROPERTY = "password"; //NOSONAR, the constant used to identify password column name (not password value itself) public static final String USER_CREDENTIALS_ACTIVATE_TOKEN_PROPERTY = "activate_token"; public static final String USER_CREDENTIALS_RESET_TOKEN_PROPERTY = "reset_token"; + public static final String USER_CREDENTIALS_ADDITIONAL_PROPERTY = "additional_info"; public static final String USER_CREDENTIALS_BY_USER_COLUMN_FAMILY_NAME = "user_credentials_by_user"; public static final String USER_CREDENTIALS_BY_ACTIVATE_TOKEN_COLUMN_FAMILY_NAME = "user_credentials_by_activate_token"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java index 97868a1e07..5229c2cad6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserCredentialsEntity.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; import org.thingsboard.server.common.data.id.UserCredentialsId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; @@ -50,6 +52,10 @@ public final class UserCredentialsEntity extends BaseSqlEntity @Column(name = ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, unique = true) private String resetToken; + @Type(type = "json") + @Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY) + private JsonNode additionalInfo; + public UserCredentialsEntity() { super(); } @@ -66,6 +72,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity this.password = userCredentials.getPassword(); this.activateToken = userCredentials.getActivateToken(); this.resetToken = userCredentials.getResetToken(); + this.additionalInfo = userCredentials.getAdditionalInfo(); } @Override @@ -79,6 +86,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity userCredentials.setPassword(password); userCredentials.setActivateToken(activateToken); userCredentials.setResetToken(resetToken); + userCredentials.setAdditionalInfo(additionalInfo); return userCredentials; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index 473c145b5d..34386132da 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -75,6 +75,13 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple DaoUtil.toPageable(pageLink))); } + @Override + public PageData findAll(PageLink pageLink) { + return DaoUtil.toPageData( + userRepository + .findAll(DaoUtil.toPageable(pageLink))); + } + @Override public PageData findTenantAdmins(UUID tenantId, PageLink pageLink) { return DaoUtil.toPageData( diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index c2551c84d3..456676664e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -60,6 +60,14 @@ public interface UserDao extends Dao, TenantEntityDao { */ PageData findByTenantId(UUID tenantId, PageLink pageLink); + /** + * Find users by page link. + * + * @param pageLink the page link + * @return the list of user entities + */ + PageData findAll(PageLink pageLink); + /** * Find tenant admin users by tenantId and page link. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index c8c5175e5f..560105a85e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -40,7 +40,6 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -128,7 +127,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userCredentials.setEnabled(false); userCredentials.setActivateToken(StringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); userCredentials.setUserId(new UserId(savedUser.getUuidId())); - saveUserCredentialsAndPasswordHistory(user.getTenantId(), userCredentials); + if (userCredentials.getPassword() != null) { + updatePasswordHistory(userCredentials); + } + userCredentialsDao.save(user.getTenantId(), userCredentials); } return savedUser; } @@ -158,7 +160,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic public UserCredentials saveUserCredentials(TenantId tenantId, UserCredentials userCredentials) { log.trace("Executing saveUserCredentials [{}]", userCredentials); userCredentialsValidator.validate(userCredentials, data -> tenantId); - return saveUserCredentialsAndPasswordHistory(tenantId, userCredentials); + return userCredentialsDao.save(tenantId, userCredentials); } @Override @@ -176,7 +178,9 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userCredentials.setEnabled(true); userCredentials.setActivateToken(null); userCredentials.setPassword(password); - + if (userCredentials.getPassword() != null) { + updatePasswordHistory(userCredentials); + } return saveUserCredentials(tenantId, userCredentials); } @@ -212,7 +216,10 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userCredentialsValidator.validate(userCredentials, data -> tenantId); userCredentialsDao.removeById(tenantId, userCredentials.getUuidId()); userCredentials.setId(null); - return saveUserCredentialsAndPasswordHistory(tenantId, userCredentials); + if (userCredentials.getPassword() != null) { + updatePasswordHistory(userCredentials); + } + return userCredentialsDao.save(tenantId, userCredentials); } @Override @@ -236,6 +243,13 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic return userDao.findByTenantId(tenantId.getId(), pageLink); } + @Override + public PageData findAllUsers(PageLink pageLink) { + log.trace("Executing findAllUsers, pageLink [{}]", pageLink); + validatePageLink(pageLink); + return userDao.findAll(pageLink); + } + @Override public PageData findTenantAdmins(TenantId tenantId, PageLink pageLink) { log.trace("Executing findTenantAdmins, tenantId [{}], pageLink [{}]", tenantId, pageLink); @@ -343,17 +357,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic return failedLoginAttempts; } - private UserCredentials saveUserCredentialsAndPasswordHistory(TenantId tenantId, UserCredentials userCredentials) { - UserCredentials result = userCredentialsDao.save(tenantId, userCredentials); - User user = findUserById(tenantId, userCredentials.getUserId()); - if (userCredentials.getPassword() != null) { - updatePasswordHistory(user, userCredentials); - } - return result; - } - - private void updatePasswordHistory(User user, UserCredentials userCredentials) { - JsonNode additionalInfo = user.getAdditionalInfo(); + private void updatePasswordHistory(UserCredentials userCredentials) { + JsonNode additionalInfo = userCredentials.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { additionalInfo = JacksonUtil.newObjectNode(); } @@ -374,8 +379,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userPasswordHistoryJson = JacksonUtil.valueToTree(userPasswordHistoryMap); ((ObjectNode) additionalInfo).set(USER_PASSWORD_HISTORY, userPasswordHistoryJson); } - user.setAdditionalInfo(additionalInfo); - saveUser(user); + userCredentials.setAdditionalInfo(additionalInfo); } private final PaginatedRemover tenantAdminsRemover = new PaginatedRemover<>() { diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index b222cf4b96..8c47b46aff 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -487,7 +487,8 @@ CREATE TABLE IF NOT EXISTS user_credentials ( enabled boolean, password varchar(255), reset_token varchar(255) UNIQUE, - user_id uuid UNIQUE + user_id uuid UNIQUE, + additional_info varchar ); CREATE TABLE IF NOT EXISTS widget_type (