diff --git a/application/src/main/data/upgrade/3.7.1/schema_update.sql b/application/src/main/data/upgrade/3.7.1/schema_update.sql new file mode 100644 index 0000000000..240daa18d5 --- /dev/null +++ b/application/src/main/data/upgrade/3.7.1/schema_update.sql @@ -0,0 +1,25 @@ +-- +-- Copyright © 2016-2024 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS last_login_ts BIGINT; +UPDATE user_credentials c SET last_login_ts = (SELECT (additional_info::json ->> 'lastLoginTs')::bigint FROM tb_user u WHERE u.id = c.user_id) + WHERE last_login_ts IS NULL; + +ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS failed_login_attempts INT; +UPDATE user_credentials c SET failed_login_attempts = (SELECT (additional_info::json ->> 'failedLoginAttempts')::int FROM tb_user u WHERE u.id = c.user_id) + WHERE failed_login_attempts IS NULL; + +UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts')::text WHERE additional_info IS NOT NULL AND additional_info != 'null'; diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index a71bd4dd38..df7f47c378 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -109,6 +109,8 @@ import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARA import static org.thingsboard.server.controller.ControllerConstants.USER_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; import static org.thingsboard.server.dao.entity.BaseEntityService.NULL_CUSTOMER_ID; +import static org.thingsboard.server.dao.user.UserServiceImpl.LAST_LOGIN_TS; +import static org.thingsboard.server.dao.user.UserServiceImpl.USER_CREDENTIALS_ENABLED; @RequiredArgsConstructor @RestController @@ -151,9 +153,10 @@ public class UserController extends BaseController { processDashboardIdFromAdditionalInfo(additionalInfo, DEFAULT_DASHBOARD); processDashboardIdFromAdditionalInfo(additionalInfo, HOME_DASHBOARD); UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); - if (userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { - additionalInfo.put("userCredentialsEnabled", true); + if (userCredentials.isEnabled() && !additionalInfo.has(USER_CREDENTIALS_ENABLED)) { + additionalInfo.put(USER_CREDENTIALS_ENABLED, true); } + additionalInfo.put(LAST_LOGIN_TS, userCredentials.getLastLoginTs()); } return user; } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 17ceb17ed1..907872d503 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -50,7 +50,6 @@ import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.RelationActionEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.dao.user.UserServiceImpl; /** * This event listener does not support async event processing because relay on ThreadLocal @@ -231,8 +230,6 @@ public class EdgeEventSourcingListener { user.setAdditionalInfo(null); } if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) { - additionalInfo.remove(UserServiceImpl.FAILED_LOGIN_ATTEMPTS); - additionalInfo.remove(UserServiceImpl.LAST_LOGIN_TS); if (additionalInfo.isEmpty()) { user.setAdditionalInfo(null); } else { 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 6516bd7ce6..c969fa71ed 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 @@ -265,7 +265,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { } } if (actionType == ActionType.LOGIN && e == null) { - userService.setLastLoginTs(user.getTenantId(), user.getId()); + userService.updateLastLoginTs(user.getTenantId(), user.getId()); } auditLogService.logEntityAction( user.getTenantId(), user.getCustomerId(), user.getId(), 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 8f22812cfc..586ae50f5d 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 @@ -97,7 +97,7 @@ public interface UserService extends EntityDaoService { int increaseFailedLoginAttempts(TenantId tenantId, UserId userId); - void setLastLoginTs(TenantId tenantId, UserId userId); + void updateLastLoginTs(TenantId tenantId, UserId userId); void saveMobileSession(TenantId tenantId, UserId userId, String mobileToken, MobileSessionInfo sessionInfo); 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 1104ae2949..4a882795db 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 @@ -40,6 +40,8 @@ public class UserCredentials extends BaseDataWithAdditionalInfo private Long resetTokenExpTime; @Convert(converter = JsonConverter.class) - @Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY) + @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.USER_CREDENTIALS_LAST_LOGIN_TS_PROPERTY) + private Long lastLoginTs; + + @Column(name = ModelConstants.USER_CREDENTIALS_FAILED_LOGIN_ATTEMPTS_PROPERTY) + private Integer failedLoginAttempts; + public UserCredentialsEntity() { super(); } public UserCredentialsEntity(UserCredentials userCredentials) { - if (userCredentials.getId() != null) { - this.setUuid(userCredentials.getId().getId()); - } - this.setCreatedTime(userCredentials.getCreatedTime()); + super(userCredentials); if (userCredentials.getUserId() != null) { this.userId = userCredentials.getUserId().getId(); } @@ -82,6 +85,8 @@ public final class UserCredentialsEntity extends BaseSqlEntity this.resetToken = userCredentials.getResetToken(); this.resetTokenExpTime = userCredentials.getResetTokenExpTime(); this.additionalInfo = userCredentials.getAdditionalInfo(); + this.lastLoginTs = userCredentials.getLastLoginTs(); + this.failedLoginAttempts = userCredentials.getFailedLoginAttempts(); } @Override @@ -98,6 +103,8 @@ public final class UserCredentialsEntity extends BaseSqlEntity userCredentials.setResetToken(resetToken); userCredentials.setResetTokenExpTime(resetTokenExpTime); userCredentials.setAdditionalInfo(additionalInfo); + userCredentials.setLastLoginTs(lastLoginTs); + userCredentials.setFailedLoginAttempts(failedLoginAttempts); return userCredentials; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java index 1f502db792..f277475896 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java @@ -69,4 +69,19 @@ public class JpaUserCredentialsDao extends JpaAbstractDao { void removeByUserId(TenantId tenantId, UserId userId); + void setLastLoginTs(TenantId tenantId, UserId userId, long lastLoginTs); + + int incrementFailedLoginAttempts(TenantId tenantId, UserId userId); + + void setFailedLoginAttempts(TenantId tenantId, UserId userId, int failedLoginAttempts); + } 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 ba0d3d69f3..c927c3e465 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 @@ -18,8 +18,6 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.BooleanNode; -import com.fasterxml.jackson.databind.node.IntNode; -import com.fasterxml.jackson.databind.node.LongNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; @@ -86,16 +84,13 @@ import static org.thingsboard.server.dao.service.Validator.validateString; public class UserServiceImpl extends AbstractCachedEntityService implements UserService { public static final String USER_PASSWORD_HISTORY = "userPasswordHistory"; - + public static final String USER_CREDENTIALS_ENABLED = "userCredentialsEnabled"; public static final String LAST_LOGIN_TS = "lastLoginTs"; - public static final String FAILED_LOGIN_ATTEMPTS = "failedLoginAttempts"; private static final int DEFAULT_TOKEN_LENGTH = 30; public static final String INCORRECT_USER_ID = "Incorrect userId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - private static final String USER_CREDENTIALS_ENABLED = "userCredentialsEnabled"; - @Value("${security.user_login_case_sensitive:true}") private boolean userLoginCaseSensitive; @@ -428,6 +423,7 @@ public class UserServiceImpl extends AbstractCachedEntityService