Store 2FA account config is UserCredentials' additionalInfo

This commit is contained in:
Viacheslav Klimov 2022-03-29 11:32:27 +03:00
parent b7db4ed604
commit 95f41810ac
5 changed files with 54 additions and 27 deletions

View File

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.security.auth.mfa.config;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
@ -22,13 +23,13 @@ import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.settings.AdminSettingsDao;
@ -41,6 +42,7 @@ import org.thingsboard.server.service.security.auth.mfa.provider.TwoFactorAuthPr
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
@Service
@RequiredArgsConstructor
@ -62,9 +64,8 @@ public class DefaultTwoFactorAuthConfigManager implements TwoFactorAuthConfigMan
@Override
public Optional<TwoFactorAuthAccountConfig> getTwoFaAccountConfig(TenantId tenantId, UserId userId) {
User user = userService.findUserById(tenantId, userId);
return Optional.ofNullable(user.getAdditionalInfo())
.flatMap(additionalInfo -> Optional.ofNullable(additionalInfo.get(TWO_FACTOR_AUTH_ACCOUNT_CONFIG_KEY)).filter(jsonNode -> !jsonNode.isNull()))
return Optional.ofNullable(getAccountInfo(tenantId, userId).get(TWO_FACTOR_AUTH_ACCOUNT_CONFIG_KEY))
.filter(JsonNode::isObject)
.map(jsonNode -> JacksonUtil.treeToValue(jsonNode, TwoFactorAuthAccountConfig.class))
.filter(twoFactorAuthAccountConfig -> {
return getTwoFaProviderConfig(tenantId, twoFactorAuthAccountConfig.getProviderType()).isPresent();
@ -76,24 +77,33 @@ public class DefaultTwoFactorAuthConfigManager implements TwoFactorAuthConfigMan
getTwoFaProviderConfig(tenantId, accountConfig.getProviderType())
.orElseThrow(() -> new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS));
User user = userService.findUserById(tenantId, userId);
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(user.getAdditionalInfo())
.orElseGet(JacksonUtil::newObjectNode);
additionalInfo.set(TWO_FACTOR_AUTH_ACCOUNT_CONFIG_KEY, JacksonUtil.valueToTree(accountConfig));
user.setAdditionalInfo(additionalInfo);
userService.saveUser(user);
updateAccountInfo(tenantId, userId, accountInfo -> {
accountInfo.set(TWO_FACTOR_AUTH_ACCOUNT_CONFIG_KEY, JacksonUtil.valueToTree(accountConfig));
});
}
@Override
public void deleteTwoFaAccountConfig(TenantId tenantId, UserId userId) {
User user = userService.findUserById(tenantId, userId);
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(user.getAdditionalInfo())
.orElseGet(JacksonUtil::newObjectNode);
additionalInfo.remove(TWO_FACTOR_AUTH_ACCOUNT_CONFIG_KEY);
user.setAdditionalInfo(additionalInfo);
updateAccountInfo(tenantId, userId, accountInfo -> {
accountInfo.remove(TWO_FACTOR_AUTH_ACCOUNT_CONFIG_KEY);
});
}
userService.saveUser(user);
private ObjectNode getAccountInfo(TenantId tenantId, UserId userId) {
return (ObjectNode) Optional.ofNullable(userService.findUserCredentialsByUserId(tenantId, userId).getAdditionalInfo())
.filter(JsonNode::isObject)
.orElseGet(JacksonUtil::newObjectNode);
}
// FIXME [viacheslav]: upgrade script for credentials' additional info
private void updateAccountInfo(TenantId tenantId, UserId userId, Consumer<ObjectNode> updater) {
UserCredentials credentials = userService.findUserCredentialsByUserId(tenantId, userId);
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(credentials.getAdditionalInfo())
.filter(JsonNode::isObject)
.orElseGet(JacksonUtil::newObjectNode);
updater.accept(additionalInfo);
credentials.setAdditionalInfo(additionalInfo);
userService.saveUserCredentials(tenantId, credentials);
}

View File

@ -16,12 +16,12 @@
package org.thingsboard.server.common.data.security;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
@EqualsAndHashCode(callSuper = true)
public class UserCredentials extends BaseData<UserCredentialsId> {
public class UserCredentials extends SearchTextBasedWithAdditionalInfo<UserCredentialsId> {
private static final long serialVersionUID = -2108436378880529163L;
@ -88,6 +88,11 @@ public class UserCredentials extends BaseData<UserCredentialsId> {
this.resetToken = resetToken;
}
@Override
public String getSearchText() {
return null;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();

View File

@ -15,14 +15,18 @@
*/
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.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -31,6 +35,7 @@ import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Entity
@Table(name = ModelConstants.USER_CREDENTIALS_COLUMN_FAMILY_NAME)
public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials> implements BaseEntity<UserCredentials> {
@ -50,6 +55,10 @@ public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials>
@Column(name = ModelConstants.USER_CREDENTIALS_RESET_TOKEN_PROPERTY, unique = true)
private String resetToken;
@Type(type = "json")
@Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
public UserCredentialsEntity() {
super();
}
@ -66,6 +75,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials>
this.password = userCredentials.getPassword();
this.activateToken = userCredentials.getActivateToken();
this.resetToken = userCredentials.getResetToken();
this.additionalInfo = userCredentials.getAdditionalInfo();
}
@Override
@ -79,6 +89,7 @@ public final class UserCredentialsEntity extends BaseSqlEntity<UserCredentials>
userCredentials.setPassword(password);
userCredentials.setActivateToken(activateToken);
userCredentials.setResetToken(resetToken);
userCredentials.setAdditionalInfo(additionalInfo);
return userCredentials;
}

View File

@ -370,7 +370,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 (