@ -34,6 +34,7 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.authentication.BadCredentialsException ;
import org.springframework.security.authentication.DisabledException ;
import org.springframework.security.authentication.LockedException ;
import org.springframework.security.core.Authentication ;
import org.springframework.security.core.AuthenticationException ;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder ;
import org.springframework.stereotype.Service ;
@ -41,6 +42,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService ;
import org.thingsboard.server.common.data.AdminSettings ;
import org.thingsboard.server.common.data.User ;
import org.thingsboard.server.common.data.audit.ActionType ;
import org.thingsboard.server.common.data.exception.ThingsboardException ;
import org.thingsboard.server.common.data.id.CustomerId ;
import org.thingsboard.server.common.data.id.TenantId ;
@ -48,20 +50,23 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials ;
import org.thingsboard.server.common.data.security.model.SecuritySettings ;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy ;
import org.thingsboard.server.dao.audit.AuditLogService ;
import org.thingsboard.server.dao.exception.DataValidationException ;
import org.thingsboard.server.dao.settings.AdminSettingsService ;
import org.thingsboard.server.dao.user.UserService ;
import org.thingsboard.server.dao.user.UserServiceImpl ;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthSettings ;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails ;
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException ;
import org.thingsboard.server.service.security.model.SecurityUser ;
import org.thingsboard.server.utils.MiscUtils ;
import ua_parser.Client ;
import javax.annotation.Resource ;
import javax.servlet.http.HttpServletRequest ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.Map ;
import java.util.Optional ;
import java.util.concurrent.TimeUnit ;
import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTINGS_CACHE ;
@ -82,6 +87,9 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
@Autowired
private MailService mailService ;
@Autowired
private AuditLogService auditLogService ;
@Resource
private SystemSecurityService self ;
@ -124,7 +132,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
@Override
public void validateUserCredentials ( TenantId tenantId , UserCredentials userCredentials , String username , String password ) throws AuthenticationException {
if ( ! encoder . matches ( password , userCredentials . getPassword ( ) ) ) {
int failedLoginAttempts = userService . onUserLoginIncorrectCredential s( tenantId , userCredentials . getUserId ( ) ) ;
int failedLoginAttempts = userService . increaseFailedLoginAttempt s( tenantId , userCredentials . getUserId ( ) ) ;
SecuritySettings securitySettings = self . getSecuritySettings ( tenantId ) ;
if ( securitySettings . getMaxFailedLoginAttempts ( ) ! = null & & securitySettings . getMaxFailedLoginAttempts ( ) > 0 ) {
if ( failedLoginAttempts > securitySettings . getMaxFailedLoginAttempts ( ) & & userCredentials . isEnabled ( ) ) {
@ -139,8 +147,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
throw new DisabledException ( "User is not active" ) ;
}
// FIXME [viacheslav]: don't do that in case of 2FA. maybe just move underlying setLastLoginTs to logLoginAction ?
userService . onUserLoginSuccessful ( tenantId , userCredentials . getUserId ( ) ) ;
userService . resetFailedLoginAttempts ( tenantId , userCredentials . getUserId ( ) ) ;
SecuritySettings securitySettings = self . getSecuritySettings ( tenantId ) ;
if ( isPositiveInteger ( securitySettings . getPasswordPolicy ( ) . getPasswordExpirationPeriodDays ( ) ) ) {
@ -154,27 +161,21 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
}
@Override
public void validateTwoFaVerification ( TenantId tenantId , UserId userId , boolean verificationSuccess , TwoFactorAuthSettings twoFaSettings ) {
User user = userService . findUserById ( tenantId , userId ) ;
ObjectNode additionalInfo = ( ObjectNode ) Optional . ofNullable ( user . getAdditionalInfo ( ) )
. filter ( jsonNode - > jsonNode instanceof ObjectNode )
. orElseGet ( JacksonUtil : : newObjectNode ) ;
// TODO [viacheslav]: test !
int failedVerificationAttempts = Optional . ofNullable ( additionalInfo . get ( "failedTwoFaVerificationAttempts" ) )
. map ( JsonNode : : asInt ) . orElse ( 0 ) ;
public void validateTwoFaVerification ( SecurityUser securityUser , boolean verificationSuccess , TwoFactorAuthSettings twoFaSettings ) {
TenantId tenantId = securityUser . getTenantId ( ) ;
UserId userId = securityUser . getId ( ) ;
int failedVerificationAttempts = 0 ;
if ( ! verificationSuccess ) {
failedVerificationAttempts + + ;
// TODO [viacheslav]: maybe use userService.onUserLoginIncorrectCredentials()
failedVerificationAttempts = userService . increaseFailedLoginAttempts ( tenantId , userId ) ;
} else {
failedVerificationAttempts = 0 ;
// and set last login ts
userService . resetFailedLoginAttempts ( tenantId , userId ) ;
}
if ( twoFaSettings . getMaxCodeVerificationFailuresBeforeUserLockout ( ) > 0
& & failedVerificationAttempts > = twoFaSettings . getMaxCodeVerificationFailuresBeforeUserLockout ( ) ) {
userService . setUserCredentialsEnabled ( TenantId . SYS_TENANT_ID , userId , false ) ;
lockAccount ( userId , user . getEmail ( ) , self . getSecuritySettings ( tenantId ) ) ;
lockAccount ( userId , sec urityU ser. getEmail ( ) , self . getSecuritySettings ( tenantId ) ) ;
throw new LockedException ( "User account was locked due to exceeded 2FA verification attempts" ) ;
}
}
@ -255,6 +256,59 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
return baseUrl ;
}
@Override
public void logLoginAction ( User user , Authentication authentication , ActionType actionType , Exception e ) {
String clientAddress = "Unknown" ;
String browser = "Unknown" ;
String os = "Unknown" ;
String device = "Unknown" ;
if ( authentication ! = null & & authentication . getDetails ( ) ! = null ) {
if ( authentication . getDetails ( ) instanceof RestAuthenticationDetails ) {
RestAuthenticationDetails details = ( RestAuthenticationDetails ) authentication . getDetails ( ) ;
clientAddress = details . getClientAddress ( ) ;
if ( details . getUserAgent ( ) ! = null ) {
Client userAgent = details . getUserAgent ( ) ;
if ( userAgent . userAgent ! = null ) {
browser = userAgent . userAgent . family ;
if ( userAgent . userAgent . major ! = null ) {
browser + = " " + userAgent . userAgent . major ;
if ( userAgent . userAgent . minor ! = null ) {
browser + = "." + userAgent . userAgent . minor ;
if ( userAgent . userAgent . patch ! = null ) {
browser + = "." + userAgent . userAgent . patch ;
}
}
}
}
if ( userAgent . os ! = null ) {
os = userAgent . os . family ;
if ( userAgent . os . major ! = null ) {
os + = " " + userAgent . os . major ;
if ( userAgent . os . minor ! = null ) {
os + = "." + userAgent . os . minor ;
if ( userAgent . os . patch ! = null ) {
os + = "." + userAgent . os . patch ;
if ( userAgent . os . patchMinor ! = null ) {
os + = "." + userAgent . os . patchMinor ;
}
}
}
}
}
if ( userAgent . device ! = null ) {
device = userAgent . device . family ;
}
}
}
}
if ( actionType = = ActionType . LOGIN & & e = = null ) {
userService . setLastLoginTs ( user . getTenantId ( ) , user . getId ( ) ) ;
}
auditLogService . logEntityAction (
user . getTenantId ( ) , user . getCustomerId ( ) , user . getId ( ) ,
user . getName ( ) , user . getId ( ) , null , actionType , e , clientAddress , browser , os , device ) ;
}
private static boolean isPositiveInteger ( Integer val ) {
return val ! = null & & val . intValue ( ) > 0 ;
}