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 f8c133f352..e54c6a9e7e 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 @@ -30,3 +30,12 @@ CREATE TABLE IF NOT EXISTS user_settings ( settings varchar(100000), CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES tb_user(id) ON DELETE CASCADE ); + +ALTER TABLE user_credentials + ADD COLUMN IF NOT EXISTS additional_info varchar NOT NULL DEFAULT '{}'; + +UPDATE user_credentials + SET additional_info = json_build_object('userPasswordHistory', (u.additional_info::json -> 'userPasswordHistory')) + FROM tb_user u WHERE user_credentials.user_id = u.id AND u.additional_info::jsonb ? 'userPasswordHistory'; + +UPDATE tb_user SET additional_info = tb_user.additional_info::jsonb - 'userPasswordHistory'; \ No newline at end of file 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/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 900fd966e8..c9bd9fbad2 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 @@ -15,10 +15,20 @@ */ package org.thingsboard.server.common.data.security; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.UserCredentialsId; 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 { @@ -30,6 +40,20 @@ public class UserCredentials extends BaseData { private String password; private String activateToken; private String resetToken; + + @NoXss + private transient JsonNode additionalInfo; + + @JsonIgnore + private byte[] additionalInfoBytes; + + public JsonNode getAdditionalInfo() { + return getJson(() -> additionalInfo, () -> additionalInfoBytes); + } + + public void setAdditionalInfo(JsonNode settings) { + setJson(settings, json -> this.additionalInfo = json, bytes -> this.additionalInfoBytes = bytes); + } public UserCredentials() { super(); @@ -46,6 +70,7 @@ public class UserCredentials extends BaseData { this.enabled = userCredentials.isEnabled(); this.activateToken = userCredentials.getActivateToken(); this.resetToken = userCredentials.getResetToken(); + setAdditionalInfo(userCredentials.getAdditionalInfo()); } public UserId getUserId() { 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/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index b4ad62c0fc..b99883c52f 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 @@ -127,7 +127,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userCredentials.setEnabled(false); userCredentials.setActivateToken(generateSafeToken(DEFAULT_TOKEN_LENGTH)); userCredentials.setUserId(new UserId(savedUser.getUuidId())); - saveUserCredentialsAndPasswordHistory(user.getTenantId(), userCredentials); + userCredentials.setAdditionalInfo(JacksonUtil.newObjectNode()); + userCredentialsDao.save(user.getTenantId(), userCredentials); } return savedUser; } @@ -157,7 +158,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 @@ -175,7 +176,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); } @@ -211,7 +214,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 @@ -342,17 +348,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(); } @@ -373,8 +370,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..6cdf431ce0 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 DEFAULT '{}' ); CREATE TABLE IF NOT EXISTS widget_type (