Browse Source

2FA: lock account after X unsuccessful verification attempts; refactoring

pull/6235/head
Viacheslav Klimov 5 years ago
parent
commit
20a4f3cc4c
  1. 15
      application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java
  2. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
  3. 38
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java
  4. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFactorAuthSettings.java
  5. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProvider.java
  6. 14
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFactorAuthProvider.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFactorAuthProvider.java
  8. 60
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
  10. 11
      application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  12. 88
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  13. 11
      application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java
  14. 11
      common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java
  15. 15
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

15
application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java

@ -17,24 +17,25 @@ package org.thingsboard.server.controller;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.audit.ActionType;
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.service.security.auth.mfa.TwoFactorAuthService; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
import org.thingsboard.server.service.security.model.JwtTokenPair; import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
/* /*
* TODO [viacheslav]: * TODO [viacheslav]:
* - Tests for 2FA * - Tests for 2FA
* - Swagger documentation * - Swagger documentation
*
* */ * */
// TODO [viacheslav]: maybe get rid of sessionId concept..
/* /*
* *
@ -51,6 +52,7 @@ public class TwoFactorAuthController extends BaseController {
private final TwoFactorAuthService twoFactorAuthService; private final TwoFactorAuthService twoFactorAuthService;
private final JwtTokenFactory tokenFactory; private final JwtTokenFactory tokenFactory;
private final SystemSecurityService systemSecurityService;
@PostMapping("/verification/send") @PostMapping("/verification/send")
@ -62,14 +64,17 @@ public class TwoFactorAuthController extends BaseController {
@PostMapping("/verification/check") @PostMapping("/verification/check")
@PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')") @PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')")
public JwtTokenPair checkTwoFaVerificationCode(@RequestParam String verificationCode) throws Exception { public JwtTokenPair checkTwoFaVerificationCode(@RequestParam String verificationCode, Authentication authentication) throws Exception {
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, verificationCode, true); boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, verificationCode, true);
if (verificationSuccess) { if (verificationSuccess) {
// FIXME [viacheslav]: log login action systemSecurityService.logLoginAction(user, authentication, ActionType.LOGIN, null);
return tokenFactory.createTokenPair(user); return tokenFactory.createTokenPair(user);
} else { } else {
throw new ThingsboardException("Verification code is incorrect", ThingsboardErrorCode.AUTHENTICATION); ThingsboardException error = new ThingsboardException("Verification code is incorrect", ThingsboardErrorCode.AUTHENTICATION);
// FIXME [viacheslav]: log login action: no authentication details
systemSecurityService.logLoginAction(user, authentication, ActionType.LOGIN, error);
throw error;
} }
} }

3
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java

@ -33,11 +33,11 @@ import org.thingsboard.server.common.data.id.EntityId;
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.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken; import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@ -66,7 +66,6 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
} else { } else {
securityUser = authenticateByPublicId(principal.getValue()); securityUser = authenticateByPublicId(principal.getValue());
} }
securityUser.setSessionId(unsafeUser.getSessionId());
if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) { if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
throw new CredentialsExpiredException("Token is outdated"); throw new CredentialsExpiredException("Token is outdated");

38
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java

@ -23,6 +23,7 @@ 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.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager; import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager;
@ -50,30 +51,29 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
private final UserService userService; private final UserService userService;
private final Map<TwoFactorAuthProviderType, TwoFactorAuthProvider<TwoFactorAuthProviderConfig, TwoFactorAuthAccountConfig>> providers = new EnumMap<>(TwoFactorAuthProviderType.class); private final Map<TwoFactorAuthProviderType, TwoFactorAuthProvider<TwoFactorAuthProviderConfig, TwoFactorAuthAccountConfig>> providers = new EnumMap<>(TwoFactorAuthProviderType.class);
// FIXME [viacheslav]: remove from the map
// TODO [viacheslav]: these rate limits are local, and will work bad in the cluster // TODO [viacheslav]: these rate limits are local, and will work bad in the cluster
private final ConcurrentMap<String, TbRateLimits> verificationCodeSendingRateLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<UserId, TbRateLimits> verificationCodeSendingRateLimits = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TbRateLimits> verificationCodeCheckingRateLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<UserId, TbRateLimits> verificationCodeCheckingRateLimits = new ConcurrentHashMap<>();
private static final ThingsboardException ACCOUNT_NOT_CONFIGURED = new ThingsboardException("2FA is not configured for account", ThingsboardErrorCode.BAD_REQUEST_PARAMS); private static final ThingsboardException ACCOUNT_NOT_CONFIGURED_ERROR = new ThingsboardException("2FA is not configured for account", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
private static final ThingsboardException PROVIDER_NOT_CONFIGURED = new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS); private static final ThingsboardException PROVIDER_NOT_CONFIGURED_ERROR = new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
private static final ThingsboardException PROVIDER_NOT_AVAILABLE = new ThingsboardException("2FA provider is not available", ThingsboardErrorCode.GENERAL); private static final ThingsboardException PROVIDER_NOT_AVAILABLE_ERROR = new ThingsboardException("2FA provider is not available", ThingsboardErrorCode.GENERAL);
@Override @Override
public void prepareVerificationCode(SecurityUser securityUser, boolean rateLimit) throws Exception { public void prepareVerificationCode(SecurityUser securityUser, boolean rateLimit) throws Exception {
TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId()) TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId())
.orElseThrow(() -> ACCOUNT_NOT_CONFIGURED); .orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR);
prepareVerificationCode(securityUser, accountConfig, rateLimit); prepareVerificationCode(securityUser, accountConfig, rateLimit);
} }
@Override @Override
public void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean rateLimit) throws ThingsboardException { public void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean rateLimit) throws ThingsboardException {
TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId()) TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
if (rateLimit) { if (rateLimit) {
if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeSendRateLimit())) { if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeSendRateLimit())) {
TbRateLimits rateLimits = verificationCodeSendingRateLimits.computeIfAbsent(securityUser.getSessionId(), sessionId -> { TbRateLimits rateLimits = verificationCodeSendingRateLimits.computeIfAbsent(securityUser.getId(), sessionId -> {
return new TbRateLimits(twoFaSettings.getVerificationCodeSendRateLimit()); return new TbRateLimits(twoFaSettings.getVerificationCodeSendRateLimit());
}); });
if (!rateLimits.tryConsume()) { if (!rateLimits.tryConsume()) {
@ -83,14 +83,14 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
} }
TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
getTwoFaProvider(accountConfig.getProviderType()).prepareVerificationCode(securityUser, providerConfig, accountConfig); getTwoFaProvider(accountConfig.getProviderType()).prepareVerificationCode(securityUser, providerConfig, accountConfig);
} }
@Override @Override
public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean rateLimit) throws ThingsboardException { public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean rateLimit) throws ThingsboardException {
TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId()) TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId())
.orElseThrow(() -> ACCOUNT_NOT_CONFIGURED); .orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR);
return checkVerificationCode(securityUser, verificationCode, accountConfig, rateLimit); return checkVerificationCode(securityUser, verificationCode, accountConfig, rateLimit);
} }
@ -101,10 +101,10 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
} }
TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId()) TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
if (rateLimit) { if (rateLimit) {
if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeCheckRateLimit())) { if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeCheckRateLimit())) {
TbRateLimits rateLimits = verificationCodeCheckingRateLimits.computeIfAbsent(securityUser.getSessionId(), sessionId -> { TbRateLimits rateLimits = verificationCodeCheckingRateLimits.computeIfAbsent(securityUser.getId(), sessionId -> {
return new TbRateLimits(twoFaSettings.getVerificationCodeCheckRateLimit()); return new TbRateLimits(twoFaSettings.getVerificationCodeCheckRateLimit());
}); });
if (!rateLimits.tryConsume()) { if (!rateLimits.tryConsume()) {
@ -114,10 +114,14 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
} }
TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
boolean verificationSuccess = getTwoFaProvider(accountConfig.getProviderType()).checkVerificationCode(securityUser, verificationCode, providerConfig, accountConfig); boolean verificationSuccess = getTwoFaProvider(accountConfig.getProviderType()).checkVerificationCode(securityUser, verificationCode, providerConfig, accountConfig);
if (rateLimit) { if (rateLimit) {
systemSecurityService.validateTwoFaVerification(securityUser.getTenantId(), securityUser.getId(), verificationSuccess, twoFaSettings); systemSecurityService.validateTwoFaVerification(securityUser, verificationSuccess, twoFaSettings);
if (verificationSuccess) {
verificationCodeCheckingRateLimits.remove(securityUser.getId());
verificationCodeSendingRateLimits.remove(securityUser.getId());
}
} }
return verificationSuccess; return verificationSuccess;
} }
@ -132,12 +136,12 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
private TwoFactorAuthProviderConfig getTwoFaProviderConfig(TenantId tenantId, TwoFactorAuthProviderType providerType) throws ThingsboardException { private TwoFactorAuthProviderConfig getTwoFaProviderConfig(TenantId tenantId, TwoFactorAuthProviderType providerType) throws ThingsboardException {
return configManager.getTwoFaSettings(tenantId) return configManager.getTwoFaSettings(tenantId)
.flatMap(twoFaSettings -> twoFaSettings.getProviderConfig(providerType)) .flatMap(twoFaSettings -> twoFaSettings.getProviderConfig(providerType))
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED); .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
} }
private TwoFactorAuthProvider<TwoFactorAuthProviderConfig, TwoFactorAuthAccountConfig> getTwoFaProvider(TwoFactorAuthProviderType providerType) throws ThingsboardException { private TwoFactorAuthProvider<TwoFactorAuthProviderConfig, TwoFactorAuthAccountConfig> getTwoFaProvider(TwoFactorAuthProviderType providerType) throws ThingsboardException {
return Optional.ofNullable(providers.get(providerType)) return Optional.ofNullable(providers.get(providerType))
.orElseThrow(() -> PROVIDER_NOT_AVAILABLE); .orElseThrow(() -> PROVIDER_NOT_AVAILABLE_ERROR);
} }
@Autowired @Autowired

6
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFactorAuthSettings.java

@ -17,14 +17,11 @@ package org.thingsboard.server.service.security.auth.mfa.config;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.checkerframework.checker.index.qual.NonNegative;
import org.thingsboard.server.service.security.auth.mfa.config.provider.TwoFactorAuthProviderConfig; import org.thingsboard.server.service.security.auth.mfa.config.provider.TwoFactorAuthProviderConfig;
import org.thingsboard.server.service.security.auth.mfa.provider.TwoFactorAuthProviderType; import org.thingsboard.server.service.security.auth.mfa.provider.TwoFactorAuthProviderType;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Min; import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -42,9 +39,10 @@ public class TwoFactorAuthSettings {
@ApiModelProperty(example = "3:900 (3 requests per 15 minutes)") @ApiModelProperty(example = "3:900 (3 requests per 15 minutes)")
@Pattern(regexp = "[^0]\\d+:[^0]\\d+", message = "Rate limit configuration is invalid") @Pattern(regexp = "[^0]\\d+:[^0]\\d+", message = "Rate limit configuration is invalid")
private String verificationCodeCheckRateLimit; private String verificationCodeCheckRateLimit;
@ApiModelProperty(example = "10")
@Min(0) @Min(0)
private int maxCodeVerificationFailuresBeforeUserLockout; private int maxCodeVerificationFailuresBeforeUserLockout;
@ApiModelProperty(value = "in seconds") @ApiModelProperty(value = "in minutes", example = "60")
@Min(1) @Min(1)
private int totalAllowedTimeForVerification; private int totalAllowedTimeForVerification;

4
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProvider.java

@ -25,9 +25,9 @@ public interface TwoFactorAuthProvider<C extends TwoFactorAuthProviderConfig, A
A generateNewAccountConfig(User user, C providerConfig); A generateNewAccountConfig(User user, C providerConfig);
default void prepareVerificationCode(SecurityUser user, C providerConfig, A accountConfig) throws ThingsboardException {} default void prepareVerificationCode(SecurityUser securityUser, C providerConfig, A accountConfig) throws ThingsboardException {}
boolean checkVerificationCode(SecurityUser user, String verificationCode, C providerConfig, A accountConfig); boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, C providerConfig, A accountConfig);
TwoFactorAuthProviderType getType(); TwoFactorAuthProviderType getType();

14
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFactorAuthProvider.java

@ -38,27 +38,27 @@ public abstract class OtpBasedTwoFactorAuthProvider<C extends OtpBasedTwoFactorA
@Override @Override
public final void prepareVerificationCode(SecurityUser user, C providerConfig, A accountConfig) throws ThingsboardException { public final void prepareVerificationCode(SecurityUser securityUser, C providerConfig, A accountConfig) throws ThingsboardException {
String verificationCode = RandomStringUtils.randomNumeric(6); String verificationCode = RandomStringUtils.randomNumeric(6);
verificationCodesCache.put(user.getSessionId(), new Otp(System.currentTimeMillis(), verificationCode, accountConfig)); verificationCodesCache.put(securityUser.getId(), new Otp(System.currentTimeMillis(), verificationCode, accountConfig));
sendVerificationCode(user, verificationCode, providerConfig, accountConfig); sendVerificationCode(securityUser, verificationCode, providerConfig, accountConfig);
} }
protected abstract void sendVerificationCode(SecurityUser user, String verificationCode, C providerConfig, A accountConfig) throws ThingsboardException; protected abstract void sendVerificationCode(SecurityUser user, String verificationCode, C providerConfig, A accountConfig) throws ThingsboardException;
@Override @Override
public final boolean checkVerificationCode(SecurityUser user, String verificationCode, C providerConfig, A accountConfig) { public final boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, C providerConfig, A accountConfig) {
Otp correctVerificationCode = verificationCodesCache.get(user.getSessionId(), Otp.class); Otp correctVerificationCode = verificationCodesCache.get(securityUser.getId(), Otp.class);
if (correctVerificationCode != null) { if (correctVerificationCode != null) {
if (System.currentTimeMillis() - correctVerificationCode.getTimestamp() if (System.currentTimeMillis() - correctVerificationCode.getTimestamp()
> TimeUnit.SECONDS.toMillis(providerConfig.getVerificationCodeLifetime())) { > TimeUnit.SECONDS.toMillis(providerConfig.getVerificationCodeLifetime())) {
verificationCodesCache.evict(user.getSessionId()); verificationCodesCache.evict(securityUser.getId());
return false; return false;
} }
if (verificationCode.equals(correctVerificationCode.getValue()) if (verificationCode.equals(correctVerificationCode.getValue())
&& correctVerificationCode.getConfig().equals(accountConfig)) { && correctVerificationCode.getConfig().equals(accountConfig)) {
verificationCodesCache.evict(user.getSessionId()); verificationCodesCache.evict(securityUser.getId());
return true; return true;
} }
} }

2
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/TotpTwoFactorAuthProvider.java

@ -45,7 +45,7 @@ public class TotpTwoFactorAuthProvider implements TwoFactorAuthProvider<TotpTwoF
} }
@Override @Override
public final boolean checkVerificationCode(SecurityUser user, String verificationCode, TotpTwoFactorAuthProviderConfig providerConfig, TotpTwoFactorAuthAccountConfig accountConfig) { public final boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TotpTwoFactorAuthProviderConfig providerConfig, TotpTwoFactorAuthAccountConfig accountConfig) {
String secretKey = UriComponentsBuilder.fromUriString(accountConfig.getAuthUrl()).build().getQueryParams().getFirst("secret"); String secretKey = UriComponentsBuilder.fromUriString(accountConfig.getAuthUrl()).build().getQueryParams().getFirst("secret");
return new Totp(secretKey).verify(verificationCode); return new Totp(secretKey).verify(verificationCode);
} }

60
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java

@ -36,7 +36,6 @@ 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.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken; import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
@ -44,7 +43,6 @@ import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConf
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.service.security.system.SystemSecurityService;
import ua_parser.Client;
import java.util.UUID; import java.util.UUID;
@ -56,19 +54,16 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
private final SystemSecurityService systemSecurityService; private final SystemSecurityService systemSecurityService;
private final UserService userService; private final UserService userService;
private final CustomerService customerService; private final CustomerService customerService;
private final AuditLogService auditLogService;
private final TwoFactorAuthConfigManager twoFactorAuthConfigManager; private final TwoFactorAuthConfigManager twoFactorAuthConfigManager;
@Autowired @Autowired
public RestAuthenticationProvider(final UserService userService, public RestAuthenticationProvider(final UserService userService,
final CustomerService customerService, final CustomerService customerService,
final SystemSecurityService systemSecurityService, final SystemSecurityService systemSecurityService,
final AuditLogService auditLogService,
TwoFactorAuthConfigManager twoFactorAuthConfigManager) { TwoFactorAuthConfigManager twoFactorAuthConfigManager) {
this.userService = userService; this.userService = userService;
this.customerService = customerService; this.customerService = customerService;
this.systemSecurityService = systemSecurityService; this.systemSecurityService = systemSecurityService;
this.auditLogService = auditLogService;
this.twoFactorAuthConfigManager = twoFactorAuthConfigManager; this.twoFactorAuthConfigManager = twoFactorAuthConfigManager;
} }
@ -89,8 +84,9 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
securityUser = authenticateByUsernameAndPassword(authentication, userPrincipal, username, password); securityUser = authenticateByUsernameAndPassword(authentication, userPrincipal, username, password);
if (twoFactorAuthConfigManager.isTwoFaEnabled(securityUser)) { if (twoFactorAuthConfigManager.isTwoFaEnabled(securityUser)) {
return new MfaAuthenticationToken(securityUser); return new MfaAuthenticationToken(securityUser);
} else {
systemSecurityService.logLoginAction((User) authentication.getPrincipal(), authentication, ActionType.LOGIN, null);
} }
logLoginAction((User) authentication.getPrincipal(), authentication, ActionType.LOGIN, null);
} else { } else {
String publicId = userPrincipal.getValue(); String publicId = userPrincipal.getValue();
securityUser = authenticateByPublicId(userPrincipal, publicId); securityUser = authenticateByPublicId(userPrincipal, publicId);
@ -115,7 +111,7 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
try { try {
systemSecurityService.validateUserCredentials(user.getTenantId(), userCredentials, username, password); systemSecurityService.validateUserCredentials(user.getTenantId(), userCredentials, username, password);
} catch (LockedException e) { } catch (LockedException e) {
logLoginAction(user, authentication, ActionType.LOCKOUT, null); systemSecurityService.logLoginAction(user, authentication, ActionType.LOCKOUT, null);
throw e; throw e;
} }
@ -124,7 +120,7 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
return new SecurityUser(user, userCredentials.isEnabled(), userPrincipal); return new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
} catch (Exception e) { } catch (Exception e) {
logLoginAction(user, authentication, ActionType.LOGIN, e); systemSecurityService.logLoginAction(user, authentication, ActionType.LOGIN, e);
throw e; throw e;
} }
} }
@ -159,52 +155,4 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
} }
private 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;
}
}
}
}
auditLogService.logEntityAction(
user.getTenantId(), user.getCustomerId(), user.getId(),
user.getName(), user.getId(), null, actionType, e, clientAddress, browser, os, device);
}
} }

5
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java

@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit;
@Component(value = "defaultAuthenticationSuccessHandler") @Component(value = "defaultAuthenticationSuccessHandler")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -54,8 +55,8 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
JwtTokenPair tokenPair = new JwtTokenPair(); JwtTokenPair tokenPair = new JwtTokenPair();
if (authentication instanceof MfaAuthenticationToken) { if (authentication instanceof MfaAuthenticationToken) {
int preVerificationTokenLifetime = twoFactorAuthConfigManager.getTwoFaSettings(securityUser.getTenantId()) int preVerificationTokenLifetime = (int) TimeUnit.MINUTES.toSeconds(twoFactorAuthConfigManager.getTwoFaSettings(securityUser.getTenantId())
.map(TwoFactorAuthSettings::getTotalAllowedTimeForVerification).orElse(30); .map(TwoFactorAuthSettings::getTotalAllowedTimeForVerification).orElse(30));
tokenPair.setToken(tokenFactory.createTwoFaPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken()); tokenPair.setToken(tokenFactory.createTwoFaPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken());
tokenPair.setRefreshToken(null); tokenPair.setRefreshToken(null);
} else { } else {

11
application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java

@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import java.util.Collection; import java.util.Collection;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -32,7 +31,6 @@ public class SecurityUser extends User {
private Collection<GrantedAuthority> authorities; private Collection<GrantedAuthority> authorities;
private boolean enabled; private boolean enabled;
private UserPrincipal userPrincipal; private UserPrincipal userPrincipal;
private String sessionId;
public SecurityUser() { public SecurityUser() {
super(); super();
@ -46,7 +44,6 @@ public class SecurityUser extends User {
super(user); super(user);
this.enabled = enabled; this.enabled = enabled;
this.userPrincipal = userPrincipal; this.userPrincipal = userPrincipal;
this.sessionId = UUID.randomUUID().toString();
} }
public Collection<GrantedAuthority> getAuthorities() { public Collection<GrantedAuthority> getAuthorities() {
@ -74,12 +71,4 @@ public class SecurityUser extends User {
this.userPrincipal = userPrincipal; this.userPrincipal = userPrincipal;
} }
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
} }

6
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -60,7 +60,6 @@ public class JwtTokenFactory {
private static final String IS_PUBLIC = "isPublic"; private static final String IS_PUBLIC = "isPublic";
private static final String TENANT_ID = "tenantId"; private static final String TENANT_ID = "tenantId";
private static final String CUSTOMER_ID = "customerId"; private static final String CUSTOMER_ID = "customerId";
private static final String SESSION_ID = "sessionId";
private final JwtSettings settings; private final JwtSettings settings;
@ -116,7 +115,6 @@ public class JwtTokenFactory {
} else if (securityUser.getAuthority() == Authority.SYS_ADMIN) { } else if (securityUser.getAuthority() == Authority.SYS_ADMIN) {
securityUser.setTenantId(TenantId.SYS_TENANT_ID); securityUser.setTenantId(TenantId.SYS_TENANT_ID);
} }
securityUser.setSessionId(claims.get(SESSION_ID, String.class));
if (securityUser.getAuthority() != Authority.PRE_VERIFICATION_TOKEN) { if (securityUser.getAuthority() != Authority.PRE_VERIFICATION_TOKEN) {
securityUser.setFirstName(claims.get(FIRST_NAME, String.class)); securityUser.setFirstName(claims.get(FIRST_NAME, String.class));
@ -162,7 +160,6 @@ public class JwtTokenFactory {
UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject); UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject);
SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class)))); SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class))));
securityUser.setUserPrincipal(principal); securityUser.setUserPrincipal(principal);
securityUser.setSessionId(claims.get(SESSION_ID, String.class));
return securityUser; return securityUser;
} }
@ -183,9 +180,6 @@ public class JwtTokenFactory {
Claims claims = Jwts.claims().setSubject(principal.getValue()); Claims claims = Jwts.claims().setSubject(principal.getValue());
claims.put(USER_ID, securityUser.getId().getId().toString()); claims.put(USER_ID, securityUser.getId().getId().toString());
claims.put(SCOPES, scopes); claims.put(SCOPES, scopes);
if (securityUser.getSessionId() != null) {
claims.put(SESSION_ID, securityUser.getSessionId());
}
ZonedDateTime currentTime = ZonedDateTime.now(); ZonedDateTime currentTime = ZonedDateTime.now();

88
application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java

@ -34,6 +34,7 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; 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.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.User; 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.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; 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.UserCredentials;
import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; 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.exception.DataValidationException;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.user.UserServiceImpl; import org.thingsboard.server.dao.user.UserServiceImpl;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthSettings; 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.exception.UserPasswordExpiredException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.utils.MiscUtils; import org.thingsboard.server.utils.MiscUtils;
import ua_parser.Client;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTINGS_CACHE; import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTINGS_CACHE;
@ -82,6 +87,9 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
@Autowired @Autowired
private MailService mailService; private MailService mailService;
@Autowired
private AuditLogService auditLogService;
@Resource @Resource
private SystemSecurityService self; private SystemSecurityService self;
@ -124,7 +132,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
@Override @Override
public void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException { public void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException {
if (!encoder.matches(password, userCredentials.getPassword())) { if (!encoder.matches(password, userCredentials.getPassword())) {
int failedLoginAttempts = userService.onUserLoginIncorrectCredentials(tenantId, userCredentials.getUserId()); int failedLoginAttempts = userService.increaseFailedLoginAttempts(tenantId, userCredentials.getUserId());
SecuritySettings securitySettings = self.getSecuritySettings(tenantId); SecuritySettings securitySettings = self.getSecuritySettings(tenantId);
if (securitySettings.getMaxFailedLoginAttempts() != null && securitySettings.getMaxFailedLoginAttempts() > 0) { if (securitySettings.getMaxFailedLoginAttempts() != null && securitySettings.getMaxFailedLoginAttempts() > 0) {
if (failedLoginAttempts > securitySettings.getMaxFailedLoginAttempts() && userCredentials.isEnabled()) { if (failedLoginAttempts > securitySettings.getMaxFailedLoginAttempts() && userCredentials.isEnabled()) {
@ -139,8 +147,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
throw new DisabledException("User is not active"); 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.resetFailedLoginAttempts(tenantId, userCredentials.getUserId());
userService.onUserLoginSuccessful(tenantId, userCredentials.getUserId());
SecuritySettings securitySettings = self.getSecuritySettings(tenantId); SecuritySettings securitySettings = self.getSecuritySettings(tenantId);
if (isPositiveInteger(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) { if (isPositiveInteger(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) {
@ -154,27 +161,21 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
} }
@Override @Override
public void validateTwoFaVerification(TenantId tenantId, UserId userId, boolean verificationSuccess, TwoFactorAuthSettings twoFaSettings) { public void validateTwoFaVerification(SecurityUser securityUser, boolean verificationSuccess, TwoFactorAuthSettings twoFaSettings) {
User user = userService.findUserById(tenantId, userId); TenantId tenantId = securityUser.getTenantId();
ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(user.getAdditionalInfo()) UserId userId = securityUser.getId();
.filter(jsonNode -> jsonNode instanceof ObjectNode)
.orElseGet(JacksonUtil::newObjectNode);
// TODO [viacheslav]: test !
int failedVerificationAttempts = Optional.ofNullable(additionalInfo.get("failedTwoFaVerificationAttempts"))
.map(JsonNode::asInt).orElse(0);
int failedVerificationAttempts = 0;
if (!verificationSuccess) { if (!verificationSuccess) {
failedVerificationAttempts++; failedVerificationAttempts = userService.increaseFailedLoginAttempts(tenantId, userId);
// TODO [viacheslav]: maybe use userService.onUserLoginIncorrectCredentials()
} else { } else {
failedVerificationAttempts = 0; userService.resetFailedLoginAttempts(tenantId, userId);
// and set last login ts
} }
if (twoFaSettings.getMaxCodeVerificationFailuresBeforeUserLockout() > 0 if (twoFaSettings.getMaxCodeVerificationFailuresBeforeUserLockout() > 0
&& failedVerificationAttempts >= twoFaSettings.getMaxCodeVerificationFailuresBeforeUserLockout()) { && failedVerificationAttempts >= twoFaSettings.getMaxCodeVerificationFailuresBeforeUserLockout()) {
userService.setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false); userService.setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false);
lockAccount(userId, user.getEmail(), self.getSecuritySettings(tenantId)); lockAccount(userId, securityUser.getEmail(), self.getSecuritySettings(tenantId));
throw new LockedException("User account was locked due to exceeded 2FA verification attempts"); throw new LockedException("User account was locked due to exceeded 2FA verification attempts");
} }
} }
@ -255,6 +256,59 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
return baseUrl; 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) { private static boolean isPositiveInteger(Integer val) {
return val != null && val.intValue() > 0; return val != null && val.intValue() > 0;
} }

11
application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java

@ -15,14 +15,17 @@
*/ */
package org.thingsboard.server.service.security.system; package org.thingsboard.server.service.security.system;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
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.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthSettings; import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthSettings;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -34,10 +37,12 @@ public interface SystemSecurityService {
void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException; void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException;
void validateTwoFaVerification(TenantId tenantId, UserId userId, boolean verificationSuccess, TwoFactorAuthSettings twoFaSettings); void validateTwoFaVerification(SecurityUser securityUser, boolean verificationSuccess, TwoFactorAuthSettings twoFaSettings);
void validatePassword(TenantId tenantId, String password, UserCredentials userCredentials) throws DataValidationException; void validatePassword(TenantId tenantId, String password, UserCredentials userCredentials) throws DataValidationException;
String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest); String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest);
void logLoginAction(User user, Authentication authentication, ActionType actionType, Exception e);
} }

11
common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java

@ -56,16 +56,19 @@ public interface UserService {
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 onUserLoginSuccessful(TenantId tenantId, UserId userId); void resetFailedLoginAttempts(TenantId tenantId, UserId userId);
int increaseFailedLoginAttempts(TenantId tenantId, UserId userId);
void setLastLoginTs(TenantId tenantId, UserId userId);
int onUserLoginIncorrectCredentials(TenantId tenantId, UserId userId);
} }

15
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -298,34 +298,35 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
@Override @Override
public void onUserLoginSuccessful(TenantId tenantId, UserId userId) { public void resetFailedLoginAttempts(TenantId tenantId, UserId userId) {
log.trace("Executing onUserLoginSuccessful [{}]", userId); log.trace("Executing onUserLoginSuccessful [{}]", userId);
User user = findUserById(tenantId, userId); User user = findUserById(tenantId, userId);
setLastLoginTs(user); // FIXME [viacheslav]: move to logLoginAction ?
resetFailedLoginAttempts(user); resetFailedLoginAttempts(user);
saveUser(user); saveUser(user);
} }
private void setLastLoginTs(User user) { private void resetFailedLoginAttempts(User user) {
JsonNode additionalInfo = user.getAdditionalInfo(); JsonNode additionalInfo = user.getAdditionalInfo();
if (!(additionalInfo instanceof ObjectNode)) { if (!(additionalInfo instanceof ObjectNode)) {
additionalInfo = JacksonUtil.newObjectNode(); additionalInfo = JacksonUtil.newObjectNode();
} }
((ObjectNode) additionalInfo).put(LAST_LOGIN_TS, System.currentTimeMillis()); ((ObjectNode) additionalInfo).put(FAILED_LOGIN_ATTEMPTS, 0);
user.setAdditionalInfo(additionalInfo); user.setAdditionalInfo(additionalInfo);
} }
private void resetFailedLoginAttempts(User user) { @Override
public void setLastLoginTs(TenantId tenantId, UserId userId) {
User user = findUserById(tenantId, userId);
JsonNode additionalInfo = user.getAdditionalInfo(); JsonNode additionalInfo = user.getAdditionalInfo();
if (!(additionalInfo instanceof ObjectNode)) { if (!(additionalInfo instanceof ObjectNode)) {
additionalInfo = JacksonUtil.newObjectNode(); additionalInfo = JacksonUtil.newObjectNode();
} }
((ObjectNode) additionalInfo).put(FAILED_LOGIN_ATTEMPTS, 0); ((ObjectNode) additionalInfo).put(LAST_LOGIN_TS, System.currentTimeMillis());
user.setAdditionalInfo(additionalInfo); user.setAdditionalInfo(additionalInfo);
} }
@Override @Override
public int onUserLoginIncorrectCredentials(TenantId tenantId, UserId userId) { public int increaseFailedLoginAttempts(TenantId tenantId, UserId userId) {
log.trace("Executing onUserLoginIncorrectCredentials [{}]", userId); log.trace("Executing onUserLoginIncorrectCredentials [{}]", userId);
User user = findUserById(tenantId, userId); User user = findUserById(tenantId, userId);
int failedLoginAttempts = increaseFailedLoginAttempts(user); int failedLoginAttempts = increaseFailedLoginAttempts(user);

Loading…
Cancel
Save