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)
@PostMapping("/settings")
@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 {
twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings);
return twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings);
}

View File

@ -99,6 +99,9 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
return newSettings;
});
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()) {
configs.values().forEach(config -> config.setUseByDefault(false));
}
@ -114,7 +117,11 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, userId)
.orElseThrow(() -> new IllegalArgumentException("2FA not configured"));
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()
.min(Comparator.comparing(TwoFaAccountConfig::getProviderType))
.ifPresent(config -> config.setUseByDefault(true));
@ -135,7 +142,7 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
}
@Override
public void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException {
public PlatformTwoFaSettings savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) throws ThingsboardException {
ConstraintValidator.validateFields(twoFactorAuthSettings);
for (TwoFaProviderConfig providerConfig : twoFactorAuthSettings.getProviders()) {
twoFactorAuthService.checkProvider(tenantId, providerConfig.getProviderType());
@ -149,6 +156,7 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
});
settings.setJsonValue(JacksonUtil.valueToTree(twoFactorAuthSettings));
adminSettingsService.saveAdminSettings(tenantId, settings);
return twoFactorAuthSettings;
}
@Override

View File

@ -39,7 +39,7 @@ public interface TwoFaConfigManager {
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);

View File

@ -55,7 +55,9 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
if (authentication instanceof MfaAuthenticationToken) {
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.setRefreshToken(null);
tokenPair.setScope(Authority.PRE_VERIFICATION_TOKEN);

View File

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

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")
private String verificationCodeCheckRateLimit;
@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")
private Integer totalAllowedTimeForVerification;

View File

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