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

@ -51,24 +51,24 @@ public interface UserService {
UserCredentials replaceUserCredentials(TenantId tenantId, UserCredentials userCredentials); UserCredentials replaceUserCredentials(TenantId tenantId, UserCredentials userCredentials);
void deleteUser(TenantId tenantId, UserId userId); void deleteUser(TenantId tenantId, UserId userId);
PageData<User> findUsersByTenantId(TenantId tenantId, PageLink pageLink); PageData<User> findUsersByTenantId(TenantId tenantId, PageLink pageLink);
PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink); PageData<User> findTenantAdmins(TenantId tenantId, PageLink pageLink);
void deleteTenantAdmins(TenantId tenantId); void deleteTenantAdmins(TenantId tenantId);
PageData<User> findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); PageData<User> findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink);
void deleteCustomerUsers(TenantId tenantId, CustomerId customerId); void deleteCustomerUsers(TenantId tenantId, CustomerId customerId);
void setUserCredentialsEnabled(TenantId tenantId, UserId userId, boolean enabled); void setUserCredentialsEnabled(TenantId tenantId, UserId userId, boolean enabled);
void resetFailedLoginAttempts(TenantId tenantId, UserId userId); void resetFailedLoginAttempts(TenantId tenantId, UserId userId);
int increaseFailedLoginAttempts(TenantId tenantId, UserId userId); int increaseFailedLoginAttempts(TenantId tenantId, UserId userId);
void setLastLoginTs(TenantId tenantId, UserId userId); void setLastLoginTs(TenantId tenantId, UserId userId);
} }

View File

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

View File

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

View File

@ -370,7 +370,8 @@ CREATE TABLE IF NOT EXISTS user_credentials (
enabled boolean, enabled boolean,
password varchar(255), password varchar(255),
reset_token varchar(255) UNIQUE, reset_token varchar(255) UNIQUE,
user_id uuid UNIQUE user_id uuid UNIQUE,
additional_info varchar
); );
CREATE TABLE IF NOT EXISTS widget_type ( CREATE TABLE IF NOT EXISTS widget_type (