Improvements for 2FA

This commit is contained in:
Viacheslav Klimov 2022-05-26 16:13:24 +03:00
parent 67e969fd75
commit eeb7dc2338
7 changed files with 22 additions and 11 deletions

View File

@ -258,9 +258,9 @@ public class TwoFaConfigController extends BaseController {
ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PostMapping("/settings") @PostMapping("/settings")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
public void savePlatformTwoFaSettings(@ApiParam(value = "Settings value", required = true) public PlatformTwoFaSettings savePlatformTwoFaSettings(@ApiParam(value = "Settings value", required = true)
@RequestBody PlatformTwoFaSettings twoFaSettings) throws ThingsboardException { @RequestBody PlatformTwoFaSettings twoFaSettings) throws ThingsboardException {
twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings); return twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings);
} }

View File

@ -99,6 +99,9 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
return newSettings; return newSettings;
}); });
Map<TwoFaProviderType, TwoFaAccountConfig> configs = settings.getConfigs(); Map<TwoFaProviderType, TwoFaAccountConfig> configs = settings.getConfigs();
if (configs.isEmpty() && accountConfig.getProviderType() == TwoFaProviderType.BACKUP_CODE) {
throw new IllegalArgumentException("To use 2FA backup codes you first need to configure at least one provider");
}
if (accountConfig.isUseByDefault()) { if (accountConfig.isUseByDefault()) {
configs.values().forEach(config -> config.setUseByDefault(false)); configs.values().forEach(config -> config.setUseByDefault(false));
} }
@ -114,7 +117,11 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, userId) AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, userId)
.orElseThrow(() -> new IllegalArgumentException("2FA not configured")); .orElseThrow(() -> new IllegalArgumentException("2FA not configured"));
settings.getConfigs().remove(providerType); settings.getConfigs().remove(providerType);
if (!settings.getConfigs().isEmpty() && settings.getConfigs().values().stream().noneMatch(TwoFaAccountConfig::isUseByDefault)) { if (settings.getConfigs().size() == 1) {
settings.getConfigs().remove(TwoFaProviderType.BACKUP_CODE);
}
if (!settings.getConfigs().isEmpty() && settings.getConfigs().values().stream()
.noneMatch(TwoFaAccountConfig::isUseByDefault)) {
settings.getConfigs().values().stream() settings.getConfigs().values().stream()
.min(Comparator.comparing(TwoFaAccountConfig::getProviderType)) .min(Comparator.comparing(TwoFaAccountConfig::getProviderType))
.ifPresent(config -> config.setUseByDefault(true)); .ifPresent(config -> config.setUseByDefault(true));
@ -135,7 +142,7 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
} }
@Override @Override
public void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException { public PlatformTwoFaSettings savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException {
ConstraintValidator.validateFields(twoFactorAuthSettings); ConstraintValidator.validateFields(twoFactorAuthSettings);
for (TwoFaProviderConfig providerConfig : twoFactorAuthSettings.getProviders()) { for (TwoFaProviderConfig providerConfig : twoFactorAuthSettings.getProviders()) {
twoFactorAuthService.checkProvider(tenantId, providerConfig.getProviderType()); twoFactorAuthService.checkProvider(tenantId, providerConfig.getProviderType());
@ -149,6 +156,7 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
}); });
settings.setJsonValue(JacksonUtil.valueToTree(twoFactorAuthSettings)); settings.setJsonValue(JacksonUtil.valueToTree(twoFactorAuthSettings));
adminSettingsService.saveAdminSettings(tenantId, settings); adminSettingsService.saveAdminSettings(tenantId, settings);
return twoFactorAuthSettings;
} }
@Override @Override

View File

@ -39,7 +39,7 @@ public interface TwoFaConfigManager {
Optional<PlatformTwoFaSettings> getPlatformTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault); Optional<PlatformTwoFaSettings> getPlatformTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault);
void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException; PlatformTwoFaSettings savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException;
void deletePlatformTwoFaSettings(TenantId tenantId); void deletePlatformTwoFaSettings(TenantId tenantId);

View File

@ -55,7 +55,9 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
if (authentication instanceof MfaAuthenticationToken) { if (authentication instanceof MfaAuthenticationToken) {
int preVerificationTokenLifetime = twoFaConfigManager.getPlatformTwoFaSettings(securityUser.getTenantId(), true) int preVerificationTokenLifetime = twoFaConfigManager.getPlatformTwoFaSettings(securityUser.getTenantId(), true)
.flatMap(settings -> Optional.ofNullable(settings.getTotalAllowedTimeForVerification())).orElse((int) TimeUnit.MINUTES.toSeconds(30)); .flatMap(settings -> Optional.ofNullable(settings.getTotalAllowedTimeForVerification())
.filter(time -> time > 0))
.orElse((int) TimeUnit.MINUTES.toSeconds(30));
tokenPair.setToken(tokenFactory.createPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken()); tokenPair.setToken(tokenFactory.createPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken());
tokenPair.setRefreshToken(null); tokenPair.setRefreshToken(null);
tokenPair.setScope(Authority.PRE_VERIFICATION_TOKEN); tokenPair.setScope(Authority.PRE_VERIFICATION_TOKEN);

View File

@ -172,11 +172,12 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
return; return;
} }
if (twoFaSettings.getMaxVerificationFailuresBeforeUserLockout() > 0 Integer maxVerificationFailures = twoFaSettings.getMaxVerificationFailuresBeforeUserLockout();
&& failedVerificationAttempts >= twoFaSettings.getMaxVerificationFailuresBeforeUserLockout()) { if (maxVerificationFailures != null && maxVerificationFailures > 0
&& failedVerificationAttempts >= maxVerificationFailures) {
userService.setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false); userService.setUserCredentialsEnabled(TenantId.SYS_TENANT_ID, userId, false);
SecuritySettings securitySettings = self.getSecuritySettings(tenantId); SecuritySettings securitySettings = self.getSecuritySettings(tenantId);
lockAccount(userId, securityUser.getEmail(), securitySettings.getUserLockoutNotificationEmail(), twoFaSettings.getMaxVerificationFailuresBeforeUserLockout()); lockAccount(userId, securityUser.getEmail(), securitySettings.getUserLockoutNotificationEmail(), maxVerificationFailures);
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");
} }
} }

View File

@ -37,7 +37,7 @@ public class PlatformTwoFaSettings {
@Pattern(regexp = "[1-9]\\d*:[1-9]\\d*", message = "verification code check rate limit configuration is invalid") @Pattern(regexp = "[1-9]\\d*:[1-9]\\d*", message = "verification code check rate limit configuration is invalid")
private String verificationCodeCheckRateLimit; private String verificationCodeCheckRateLimit;
@Min(value = 0, message = "maximum number of verification failure before user lockout must be positive") @Min(value = 0, message = "maximum number of verification failure before user lockout must be positive")
private int maxVerificationFailuresBeforeUserLockout; private Integer maxVerificationFailuresBeforeUserLockout;
@Min(value = 1, message = "total amount of time allotted for verification must be greater than 0") @Min(value = 1, message = "total amount of time allotted for verification must be greater than 0")
private Integer totalAllowedTimeForVerification; private Integer totalAllowedTimeForVerification;

View File

@ -22,7 +22,7 @@ import javax.validation.constraints.Min;
@Data @Data
public class BackupCodeTwoFaProviderConfig implements TwoFaProviderConfig { public class BackupCodeTwoFaProviderConfig implements TwoFaProviderConfig {
@Min(0) @Min(1)
private int codesQuantity; private int codesQuantity;
@Override @Override