Add validation for 2FA account settings when enforcement is enabled
This commit is contained in:
parent
85804837db
commit
c4c3d255b0
@ -69,7 +69,7 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
public AccountTwoFaSettings getAccountTwoFaSettings() throws ThingsboardException {
|
||||
SecurityUser user = getCurrentUser();
|
||||
return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user.getId()).orElse(null);
|
||||
return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user).orElse(null);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Generate 2FA account config (generateTwoFaAccountConfig)",
|
||||
@ -141,7 +141,7 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
public AccountTwoFaSettings verifyAndSaveTwoFaAccountConfig(@Valid @RequestBody TwoFaAccountConfig accountConfig,
|
||||
@RequestParam(required = false) String verificationCode) throws Exception {
|
||||
SecurityUser user = getCurrentUser();
|
||||
if (twoFaConfigManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig.getProviderType()).isPresent()) {
|
||||
if (twoFaConfigManager.getTwoFaAccountConfig(user.getTenantId(), user, accountConfig.getProviderType()).isPresent()) {
|
||||
throw new IllegalArgumentException("2FA provider is already configured");
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
verificationSuccess = true;
|
||||
}
|
||||
if (verificationSuccess) {
|
||||
return twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig);
|
||||
return twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user, accountConfig);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Verification code is incorrect");
|
||||
}
|
||||
@ -160,38 +160,38 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
|
||||
@ApiOperation(value = "Update 2FA account config (updateTwoFaAccountConfig)", notes =
|
||||
"Update config for a given provider type. \n" +
|
||||
"Update request example:\n" +
|
||||
"```\n{\n \"useByDefault\": true\n}\n```\n" +
|
||||
"Returns whole account's 2FA settings object.\n" +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
"Update request example:\n" +
|
||||
"```\n{\n \"useByDefault\": true\n}\n```\n" +
|
||||
"Returns whole account's 2FA settings object.\n" +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
@PutMapping("/account/config")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
public AccountTwoFaSettings updateTwoFaAccountConfig(@RequestParam TwoFaProviderType providerType,
|
||||
@RequestBody TwoFaAccountConfigUpdateRequest updateRequest) throws ThingsboardException {
|
||||
SecurityUser user = getCurrentUser();
|
||||
|
||||
TwoFaAccountConfig accountConfig = twoFaConfigManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType)
|
||||
TwoFaAccountConfig accountConfig = twoFaConfigManager.getTwoFaAccountConfig(user.getTenantId(), user, providerType)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Config for " + providerType + " 2FA provider not found"));
|
||||
accountConfig.setUseByDefault(updateRequest.isUseByDefault());
|
||||
return twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig);
|
||||
return twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user, accountConfig);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Delete 2FA account config (deleteTwoFaAccountConfig)", notes =
|
||||
"Delete 2FA config for a given 2FA provider type. \n" +
|
||||
"Returns whole account's 2FA settings object.\n" +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
"Returns whole account's 2FA settings object.\n" +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
@DeleteMapping("/account/config")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
public AccountTwoFaSettings deleteTwoFaAccountConfig(@RequestParam TwoFaProviderType providerType) throws ThingsboardException {
|
||||
SecurityUser user = getCurrentUser();
|
||||
return twoFaConfigManager.deleteTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType);
|
||||
return twoFaConfigManager.deleteTwoFaAccountConfig(user.getTenantId(), user, providerType);
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Get available 2FA providers (getAvailableTwoFaProviders)", notes =
|
||||
"Get the list of provider types available for user to use (the ones configured by tenant or sysadmin).\n" +
|
||||
"Example of response:\n" +
|
||||
"```\n[\n \"TOTP\",\n \"EMAIL\",\n \"SMS\"\n]\n```" +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER
|
||||
"Example of response:\n" +
|
||||
"```\n[\n \"TOTP\",\n \"EMAIL\",\n \"SMS\"\n]\n```" +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER
|
||||
)
|
||||
@GetMapping("/providers")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER', 'MFA_CONFIGURATION_TOKEN')")
|
||||
|
||||
@ -101,17 +101,17 @@ public class TwoFactorAuthController extends BaseController {
|
||||
|
||||
@ApiOperation(value = "Get available 2FA providers (getAvailableTwoFaProviders)", notes =
|
||||
"Get the list of 2FA provider infos available for user to use. Example:\n" +
|
||||
"```\n[\n" +
|
||||
" {\n \"type\": \"EMAIL\",\n \"default\": true,\n \"contact\": \"ab*****ko@gmail.com\"\n },\n" +
|
||||
" {\n \"type\": \"TOTP\",\n \"default\": false,\n \"contact\": null\n },\n" +
|
||||
" {\n \"type\": \"SMS\",\n \"default\": false,\n \"contact\": \"+38********12\"\n }\n" +
|
||||
"]\n```")
|
||||
"```\n[\n" +
|
||||
" {\n \"type\": \"EMAIL\",\n \"default\": true,\n \"contact\": \"ab*****ko@gmail.com\"\n },\n" +
|
||||
" {\n \"type\": \"TOTP\",\n \"default\": false,\n \"contact\": null\n },\n" +
|
||||
" {\n \"type\": \"SMS\",\n \"default\": false,\n \"contact\": \"+38********12\"\n }\n" +
|
||||
"]\n```")
|
||||
@GetMapping("/providers")
|
||||
@PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')")
|
||||
public List<TwoFaProviderInfo> getAvailableTwoFaProviders() throws ThingsboardException {
|
||||
SecurityUser user = getCurrentUser();
|
||||
Optional<PlatformTwoFaSettings> platformTwoFaSettings = twoFaConfigManager.getPlatformTwoFaSettings(user.getTenantId(), true);
|
||||
return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user.getId())
|
||||
return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user)
|
||||
.map(settings -> settings.getConfigs().values()).orElse(Collections.emptyList())
|
||||
.stream().map(config -> {
|
||||
String contact = null;
|
||||
@ -141,7 +141,7 @@ public class TwoFactorAuthController extends BaseController {
|
||||
@PreAuthorize("hasAuthority('MFA_CONFIGURATION_TOKEN')")
|
||||
public JwtPair authenticateByTwoFaConfigurationToken(HttpServletRequest servletRequest) throws ThingsboardException {
|
||||
SecurityUser user = getCurrentUser();
|
||||
if (twoFactorAuthService.isTwoFaEnabled(user.getTenantId(), user.getId())) {
|
||||
if (twoFactorAuthService.isTwoFaEnabled(user.getTenantId(), user)) {
|
||||
logLogInAction(servletRequest, user, null);
|
||||
return createTokenPair(user);
|
||||
} else {
|
||||
|
||||
@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
@ -32,6 +34,10 @@ public class AuthExceptionHandler extends OncePerRequestFilter {
|
||||
|
||||
private final ThingsboardErrorResponseHandler errorResponseHandler;
|
||||
|
||||
@Value("${server.log_controller_error_stack_trace}")
|
||||
@Getter
|
||||
private boolean logControllerErrorStackTrace;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
|
||||
try {
|
||||
@ -39,8 +45,15 @@ public class AuthExceptionHandler extends OncePerRequestFilter {
|
||||
} catch (AuthenticationException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
log(e);
|
||||
errorResponseHandler.handle(e, response);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(Exception e) {
|
||||
if (logControllerErrorStackTrace) {
|
||||
log.error("Auth error", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -62,8 +62,8 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
|
||||
private static final ThingsboardException TOO_MANY_REQUESTS_ERROR = new ThingsboardException("Too many requests", ThingsboardErrorCode.TOO_MANY_REQUESTS);
|
||||
|
||||
@Override
|
||||
public boolean isTwoFaEnabled(TenantId tenantId, UserId userId) {
|
||||
return configManager.getAccountTwoFaSettings(tenantId, userId)
|
||||
public boolean isTwoFaEnabled(TenantId tenantId, User user) {
|
||||
return configManager.getAccountTwoFaSettings(tenantId, user)
|
||||
.map(settings -> !settings.getConfigs().isEmpty())
|
||||
.orElse(false);
|
||||
}
|
||||
@ -89,7 +89,7 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
|
||||
|
||||
@Override
|
||||
public void prepareVerificationCode(SecurityUser user, TwoFaProviderType providerType, boolean checkLimits) throws Exception {
|
||||
TwoFaAccountConfig accountConfig = configManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType)
|
||||
TwoFaAccountConfig accountConfig = configManager.getTwoFaAccountConfig(user.getTenantId(), user, providerType)
|
||||
.orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR);
|
||||
prepareVerificationCode(user, accountConfig, checkLimits);
|
||||
}
|
||||
@ -118,7 +118,7 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
|
||||
|
||||
@Override
|
||||
public boolean checkVerificationCode(SecurityUser user, TwoFaProviderType providerType, String verificationCode, boolean checkLimits) throws ThingsboardException {
|
||||
TwoFaAccountConfig accountConfig = configManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType)
|
||||
TwoFaAccountConfig accountConfig = configManager.getTwoFaAccountConfig(user.getTenantId(), user, providerType)
|
||||
.orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR);
|
||||
return checkVerificationCode(user, verificationCode, accountConfig, checkLimits);
|
||||
}
|
||||
|
||||
@ -18,14 +18,13 @@ package org.thingsboard.server.service.security.auth.mfa;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
|
||||
public interface TwoFactorAuthService {
|
||||
|
||||
boolean isTwoFaEnabled(TenantId tenantId, UserId userId);
|
||||
boolean isTwoFaEnabled(TenantId tenantId, User user);
|
||||
|
||||
boolean isEnforceTwoFaEnabled(TenantId tenantId, User user);
|
||||
|
||||
|
||||
@ -21,9 +21,9 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.AdminSettings;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.security.UserAuthSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings;
|
||||
@ -56,9 +56,9 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
|
||||
|
||||
@Override
|
||||
public Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, UserId userId) {
|
||||
public Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, User user) {
|
||||
PlatformTwoFaSettings platformTwoFaSettings = getPlatformTwoFaSettings(tenantId, true).orElse(null);
|
||||
return Optional.ofNullable(userAuthSettingsDao.findByUserId(userId))
|
||||
return Optional.ofNullable(userAuthSettingsDao.findByUserId(user.getId()))
|
||||
.map(userAuthSettings -> {
|
||||
AccountTwoFaSettings twoFaSettings = userAuthSettings.getTwoFaSettings();
|
||||
if (twoFaSettings == null) return null;
|
||||
@ -80,17 +80,22 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
}
|
||||
|
||||
if (updateNeeded) {
|
||||
twoFaSettings = saveAccountTwoFaSettings(tenantId, userId, twoFaSettings);
|
||||
twoFaSettings = saveAccountTwoFaSettings(tenantId, user, twoFaSettings);
|
||||
}
|
||||
return twoFaSettings;
|
||||
});
|
||||
}
|
||||
|
||||
protected AccountTwoFaSettings saveAccountTwoFaSettings(TenantId tenantId, UserId userId, AccountTwoFaSettings settings) {
|
||||
UserAuthSettings userAuthSettings = Optional.ofNullable(userAuthSettingsDao.findByUserId(userId))
|
||||
protected AccountTwoFaSettings saveAccountTwoFaSettings(TenantId tenantId, User user, AccountTwoFaSettings settings) {
|
||||
if (settings.getConfigs().isEmpty()) {
|
||||
if (twoFactorAuthService.isEnforceTwoFaEnabled(tenantId, user)) {
|
||||
throw new DataValidationException("At least one 2FA provider is required");
|
||||
}
|
||||
}
|
||||
UserAuthSettings userAuthSettings = Optional.ofNullable(userAuthSettingsDao.findByUserId(user.getId()))
|
||||
.orElseGet(() -> {
|
||||
UserAuthSettings newUserAuthSettings = new UserAuthSettings();
|
||||
newUserAuthSettings.setUserId(userId);
|
||||
newUserAuthSettings.setUserId(user.getId());
|
||||
return newUserAuthSettings;
|
||||
});
|
||||
userAuthSettings.setTwoFaSettings(settings);
|
||||
@ -102,18 +107,18 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
|
||||
|
||||
@Override
|
||||
public Optional<TwoFaAccountConfig> getTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType) {
|
||||
return getAccountTwoFaSettings(tenantId, userId)
|
||||
public Optional<TwoFaAccountConfig> getTwoFaAccountConfig(TenantId tenantId, User user, TwoFaProviderType providerType) {
|
||||
return getAccountTwoFaSettings(tenantId, user)
|
||||
.map(AccountTwoFaSettings::getConfigs)
|
||||
.flatMap(configs -> Optional.ofNullable(configs.get(providerType)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountTwoFaSettings saveTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaAccountConfig accountConfig) {
|
||||
public AccountTwoFaSettings saveTwoFaAccountConfig(TenantId tenantId, User user, TwoFaAccountConfig accountConfig) {
|
||||
getTwoFaProviderConfig(tenantId, accountConfig.getProviderType())
|
||||
.orElseThrow(() -> new IllegalArgumentException("2FA provider is not configured"));
|
||||
|
||||
AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, userId).orElseGet(() -> {
|
||||
AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, user).orElseGet(() -> {
|
||||
AccountTwoFaSettings newSettings = new AccountTwoFaSettings();
|
||||
newSettings.setConfigs(new LinkedHashMap<>());
|
||||
return newSettings;
|
||||
@ -129,12 +134,12 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
if (configs.values().stream().noneMatch(TwoFaAccountConfig::isUseByDefault)) {
|
||||
configs.values().stream().findFirst().ifPresent(config -> config.setUseByDefault(true));
|
||||
}
|
||||
return saveAccountTwoFaSettings(tenantId, userId, settings);
|
||||
return saveAccountTwoFaSettings(tenantId, user, settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountTwoFaSettings deleteTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType) {
|
||||
AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, userId)
|
||||
public AccountTwoFaSettings deleteTwoFaAccountConfig(TenantId tenantId, User user, TwoFaProviderType providerType) {
|
||||
AccountTwoFaSettings settings = getAccountTwoFaSettings(tenantId, user)
|
||||
.orElseThrow(() -> new IllegalArgumentException("2FA not configured"));
|
||||
settings.getConfigs().remove(providerType);
|
||||
if (settings.getConfigs().size() == 1) {
|
||||
@ -146,7 +151,7 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
.min(Comparator.comparing(TwoFaAccountConfig::getProviderType))
|
||||
.ifPresent(config -> config.setUseByDefault(true));
|
||||
}
|
||||
return saveAccountTwoFaSettings(tenantId, userId, settings);
|
||||
return saveAccountTwoFaSettings(tenantId, user, settings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.thingsboard.server.service.security.auth.mfa.config;
|
||||
|
||||
import org.thingsboard.server.common.data.User;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig;
|
||||
@ -27,14 +27,14 @@ import java.util.Optional;
|
||||
|
||||
public interface TwoFaConfigManager {
|
||||
|
||||
Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, UserId userId);
|
||||
Optional<AccountTwoFaSettings> getAccountTwoFaSettings(TenantId tenantId, User user);
|
||||
|
||||
|
||||
Optional<TwoFaAccountConfig> getTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType);
|
||||
Optional<TwoFaAccountConfig> getTwoFaAccountConfig(TenantId tenantId, User user, TwoFaProviderType providerType);
|
||||
|
||||
AccountTwoFaSettings saveTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaAccountConfig accountConfig);
|
||||
AccountTwoFaSettings saveTwoFaAccountConfig(TenantId tenantId, User user, TwoFaAccountConfig accountConfig);
|
||||
|
||||
AccountTwoFaSettings deleteTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType);
|
||||
AccountTwoFaSettings deleteTwoFaAccountConfig(TenantId tenantId, User user, TwoFaProviderType providerType);
|
||||
|
||||
|
||||
Optional<PlatformTwoFaSettings> getPlatformTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault);
|
||||
|
||||
@ -58,7 +58,7 @@ public class BackupCodeTwoFaProvider implements TwoFaProvider<BackupCodeTwoFaPro
|
||||
public boolean checkVerificationCode(SecurityUser user, String code, BackupCodeTwoFaProviderConfig providerConfig, BackupCodeTwoFaAccountConfig accountConfig) {
|
||||
if (CollectionsUtil.contains(accountConfig.getCodes(), code)) {
|
||||
accountConfig.getCodes().remove(code);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user, accountConfig);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@ -43,8 +43,8 @@ import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.settings.SecuritySettingsService;
|
||||
import org.thingsboard.server.dao.user.UserService;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.security.auth.MfaConfigurationToken;
|
||||
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
|
||||
import org.thingsboard.server.service.security.auth.MfaConfigurationToken;
|
||||
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
|
||||
import org.thingsboard.server.service.security.exception.UserPasswordNotValidException;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
@ -103,7 +103,7 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
|
||||
}
|
||||
|
||||
securityUser = authenticateByUsernameAndPassword(authentication, userPrincipal, username, password);
|
||||
if (twoFactorAuthService.isTwoFaEnabled(securityUser.getTenantId(), securityUser.getId())) {
|
||||
if (twoFactorAuthService.isTwoFaEnabled(securityUser.getTenantId(), securityUser)) {
|
||||
return new MfaAuthenticationToken(securityUser);
|
||||
} else if (twoFactorAuthService.isEnforceTwoFaEnabled(securityUser.getTenantId(), securityUser)) {
|
||||
return new MfaConfigurationToken(securityUser);
|
||||
|
||||
@ -30,6 +30,8 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.thingsboard.rule.engine.api.SmsService;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
|
||||
import org.thingsboard.server.common.data.notification.targets.platform.TenantAdministratorsFilter;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings;
|
||||
import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig;
|
||||
@ -48,6 +50,7 @@ import org.thingsboard.server.service.security.auth.mfa.provider.impl.TotpTwoFaP
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -102,10 +105,11 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(3600);
|
||||
twoFaSettings.setEnforceTwoFa(true);
|
||||
twoFaSettings.setEnforcedUsersFilter(new AllUsersFilter());
|
||||
|
||||
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk());
|
||||
saveTwoFaSettings(twoFaSettings);
|
||||
|
||||
PlatformTwoFaSettings savedTwoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), PlatformTwoFaSettings.class);
|
||||
PlatformTwoFaSettings savedTwoFaSettings = findTwoFaSettings();
|
||||
|
||||
assertThat(savedTwoFaSettings.getProviders()).hasSize(2);
|
||||
assertThat(savedTwoFaSettings.getProviders()).contains(totpTwoFaProviderConfig, smsTwoFaProviderConfig);
|
||||
@ -172,17 +176,6 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
assertThat(errorResponse).containsIgnoringCase("verificationCodeLifetime is required");
|
||||
}
|
||||
|
||||
private String savePlatformTwoFaSettingsAndGetError(TwoFaProviderConfig invalidTwoFaProviderConfig) throws Exception {
|
||||
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
|
||||
twoFaSettings.setProviders(Collections.singletonList(invalidTwoFaProviderConfig));
|
||||
twoFaSettings.setMinVerificationCodeSendPeriod(5);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(100);
|
||||
|
||||
return getErrorMessage(doPost("/api/2fa/settings", twoFaSettings)
|
||||
.andExpect(status().isBadRequest()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSaveTwoFaAccountConfig_providerNotConfigured() throws Exception {
|
||||
configureSmsTwoFaProvider("${code}");
|
||||
@ -283,24 +276,6 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
assertThat(errorMessage).containsIgnoringCase("verification code is incorrect");
|
||||
}
|
||||
|
||||
private TotpTwoFaAccountConfig generateTotpTwoFaAccountConfig(TotpTwoFaProviderConfig totpTwoFaProviderConfig) throws Exception {
|
||||
TwoFaAccountConfig generatedTwoFaAccountConfig = readResponse(doPost("/api/2fa/account/config/generate?providerType=TOTP")
|
||||
.andExpect(status().isOk()), TwoFaAccountConfig.class);
|
||||
assertThat(generatedTwoFaAccountConfig).isInstanceOf(TotpTwoFaAccountConfig.class);
|
||||
|
||||
assertThat(((TotpTwoFaAccountConfig) generatedTwoFaAccountConfig)).satisfies(accountConfig -> {
|
||||
UriComponents otpAuthUrl = UriComponentsBuilder.fromUriString(accountConfig.getAuthUrl()).build();
|
||||
assertThat(otpAuthUrl.getScheme()).isEqualTo("otpauth");
|
||||
assertThat(otpAuthUrl.getHost()).isEqualTo("totp");
|
||||
assertThat(otpAuthUrl.getQueryParams().getFirst("issuer")).isEqualTo(totpTwoFaProviderConfig.getIssuerName());
|
||||
assertThat(otpAuthUrl.getPath()).isEqualTo("/%s:%s", totpTwoFaProviderConfig.getIssuerName(), TENANT_ADMIN_EMAIL);
|
||||
assertThat(otpAuthUrl.getQueryParams().getFirst("secret")).satisfies(secretKey -> {
|
||||
assertDoesNotThrow(() -> Base32.decode(secretKey));
|
||||
});
|
||||
});
|
||||
return (TotpTwoFaAccountConfig) generatedTwoFaAccountConfig;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTwoFaAccountConfig_whenProviderNotConfigured() throws Exception {
|
||||
testVerifyAndSaveTotpTwoFaAccountConfig();
|
||||
@ -434,6 +409,56 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
assertThat(accountConfig).isEqualTo(initialSmsTwoFaAccountConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsTwoFaEnabled() throws Exception {
|
||||
configureSmsTwoFaProvider("${code}");
|
||||
SmsTwoFaAccountConfig accountConfig = new SmsTwoFaAccountConfig();
|
||||
accountConfig.setPhoneNumber("+38050505050");
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUser, accountConfig);
|
||||
|
||||
assertThat(twoFactorAuthService.isTwoFaEnabled(tenantId, tenantAdminUser)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteTwoFaAccountConfig() throws Exception {
|
||||
configureSmsTwoFaProvider("${code}");
|
||||
loginTenantAdmin();
|
||||
SmsTwoFaAccountConfig accountConfig = new SmsTwoFaAccountConfig();
|
||||
accountConfig.setPhoneNumber("+38050505050");
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUser, accountConfig);
|
||||
|
||||
AccountTwoFaSettings accountTwoFaSettings = readResponse(doGet("/api/2fa/account/settings").andExpect(status().isOk()), AccountTwoFaSettings.class);
|
||||
TwoFaAccountConfig savedAccountConfig = accountTwoFaSettings.getConfigs().get(TwoFaProviderType.SMS);
|
||||
assertThat(savedAccountConfig).isEqualTo(accountConfig);
|
||||
|
||||
PlatformTwoFaSettings twoFaSettings = twoFaConfigManager.getPlatformTwoFaSettings(TenantId.SYS_TENANT_ID, true).get();
|
||||
twoFaSettings.setEnforceTwoFa(true);
|
||||
TenantAdministratorsFilter enforcedUsersFilter = new TenantAdministratorsFilter();
|
||||
enforcedUsersFilter.setTenantsIds(Set.of(tenantId.getId()));
|
||||
twoFaSettings.setEnforcedUsersFilter(enforcedUsersFilter);
|
||||
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
|
||||
|
||||
String errorMessage = getErrorMessage(doDelete("/api/2fa/account/config?providerType=SMS")
|
||||
.andExpect(status().isBadRequest()));
|
||||
assertThat(errorMessage).isEqualTo("At least one 2FA provider is required");
|
||||
|
||||
twoFaSettings.setEnforceTwoFa(false);
|
||||
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
|
||||
|
||||
doDelete("/api/2fa/account/config?providerType=SMS").andExpect(status().isOk());
|
||||
|
||||
assertThat(readResponse(doGet("/api/2fa/account/settings").andExpect(status().isOk()), AccountTwoFaSettings.class).getConfigs())
|
||||
.doesNotContainKey(TwoFaProviderType.SMS);
|
||||
}
|
||||
|
||||
private PlatformTwoFaSettings findTwoFaSettings() throws Exception {
|
||||
return doGet("/api/2fa/settings", PlatformTwoFaSettings.class);
|
||||
}
|
||||
|
||||
private void saveTwoFaSettings(PlatformTwoFaSettings twoFaSettings) throws Exception {
|
||||
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
private TotpTwoFaProviderConfig configureTotpTwoFaProvider() throws Exception {
|
||||
TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig();
|
||||
totpTwoFaProviderConfig.setIssuerName("tb");
|
||||
@ -456,37 +481,35 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
twoFaSettings.setProviders(Arrays.stream(providerConfigs).collect(Collectors.toList()));
|
||||
twoFaSettings.setMinVerificationCodeSendPeriod(5);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(100);
|
||||
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk());
|
||||
saveTwoFaSettings(twoFaSettings);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsTwoFaEnabled() throws Exception {
|
||||
configureSmsTwoFaProvider("${code}");
|
||||
SmsTwoFaAccountConfig accountConfig = new SmsTwoFaAccountConfig();
|
||||
accountConfig.setPhoneNumber("+38050505050");
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUserId, accountConfig);
|
||||
private TotpTwoFaAccountConfig generateTotpTwoFaAccountConfig(TotpTwoFaProviderConfig totpTwoFaProviderConfig) throws Exception {
|
||||
TwoFaAccountConfig generatedTwoFaAccountConfig = readResponse(doPost("/api/2fa/account/config/generate?providerType=TOTP")
|
||||
.andExpect(status().isOk()), TwoFaAccountConfig.class);
|
||||
assertThat(generatedTwoFaAccountConfig).isInstanceOf(TotpTwoFaAccountConfig.class);
|
||||
|
||||
assertThat(twoFactorAuthService.isTwoFaEnabled(tenantId, tenantAdminUserId)).isTrue();
|
||||
assertThat(((TotpTwoFaAccountConfig) generatedTwoFaAccountConfig)).satisfies(accountConfig -> {
|
||||
UriComponents otpAuthUrl = UriComponentsBuilder.fromUriString(accountConfig.getAuthUrl()).build();
|
||||
assertThat(otpAuthUrl.getScheme()).isEqualTo("otpauth");
|
||||
assertThat(otpAuthUrl.getHost()).isEqualTo("totp");
|
||||
assertThat(otpAuthUrl.getQueryParams().getFirst("issuer")).isEqualTo(totpTwoFaProviderConfig.getIssuerName());
|
||||
assertThat(otpAuthUrl.getPath()).isEqualTo("/%s:%s", totpTwoFaProviderConfig.getIssuerName(), TENANT_ADMIN_EMAIL);
|
||||
assertThat(otpAuthUrl.getQueryParams().getFirst("secret")).satisfies(secretKey -> {
|
||||
assertDoesNotThrow(() -> Base32.decode(secretKey));
|
||||
});
|
||||
});
|
||||
return (TotpTwoFaAccountConfig) generatedTwoFaAccountConfig;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteTwoFaAccountConfig() throws Exception {
|
||||
configureSmsTwoFaProvider("${code}");
|
||||
SmsTwoFaAccountConfig accountConfig = new SmsTwoFaAccountConfig();
|
||||
accountConfig.setPhoneNumber("+38050505050");
|
||||
private String savePlatformTwoFaSettingsAndGetError(TwoFaProviderConfig invalidTwoFaProviderConfig) throws Exception {
|
||||
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
|
||||
twoFaSettings.setProviders(Collections.singletonList(invalidTwoFaProviderConfig));
|
||||
twoFaSettings.setMinVerificationCodeSendPeriod(5);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(100);
|
||||
|
||||
loginTenantAdmin();
|
||||
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUserId, accountConfig);
|
||||
|
||||
AccountTwoFaSettings accountTwoFaSettings = readResponse(doGet("/api/2fa/account/settings").andExpect(status().isOk()), AccountTwoFaSettings.class);
|
||||
TwoFaAccountConfig savedAccountConfig = accountTwoFaSettings.getConfigs().get(TwoFaProviderType.SMS);
|
||||
assertThat(savedAccountConfig).isEqualTo(accountConfig);
|
||||
|
||||
doDelete("/api/2fa/account/config?providerType=SMS").andExpect(status().isOk());
|
||||
|
||||
assertThat(readResponse(doGet("/api/2fa/account/settings").andExpect(status().isOk()), AccountTwoFaSettings.class).getConfigs())
|
||||
.doesNotContainKey(TwoFaProviderType.SMS);
|
||||
return getErrorMessage(doPost("/api/2fa/settings", twoFaSettings)
|
||||
.andExpect(status().isBadRequest()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -337,7 +337,7 @@ public class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
@Test
|
||||
public void testAuthWithoutTwoFaAccountConfig() throws ThingsboardException {
|
||||
configureTotpTwoFa();
|
||||
twoFaConfigManager.deleteTwoFaAccountConfig(tenantId, user.getId(), TwoFaProviderType.TOTP);
|
||||
twoFaConfigManager.deleteTwoFaAccountConfig(tenantId, user, TwoFaProviderType.TOTP);
|
||||
|
||||
assertDoesNotThrow(() -> {
|
||||
login(username, password);
|
||||
@ -371,15 +371,15 @@ public class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
|
||||
TotpTwoFaAccountConfig totpTwoFaAccountConfig = (TotpTwoFaAccountConfig) twoFactorAuthService.generateNewAccountConfig(twoFaUser, TwoFaProviderType.TOTP);
|
||||
totpTwoFaAccountConfig.setUseByDefault(true);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, twoFaUser.getId(), totpTwoFaAccountConfig);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, twoFaUser, totpTwoFaAccountConfig);
|
||||
|
||||
SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig();
|
||||
smsTwoFaAccountConfig.setPhoneNumber("+38012312322");
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, twoFaUser.getId(), smsTwoFaAccountConfig);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, twoFaUser, smsTwoFaAccountConfig);
|
||||
|
||||
EmailTwoFaAccountConfig emailTwoFaAccountConfig = new EmailTwoFaAccountConfig();
|
||||
emailTwoFaAccountConfig.setEmail(twoFaUser.getEmail());
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, twoFaUser.getId(), emailTwoFaAccountConfig);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, twoFaUser, emailTwoFaAccountConfig);
|
||||
|
||||
logInWithMfaToken(twoFaUser.getEmail(), "12345678", Authority.PRE_VERIFICATION_TOKEN);
|
||||
|
||||
@ -431,9 +431,6 @@ public class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
// verifying enforced users filter
|
||||
createDifferentTenant();
|
||||
doGet("/api/user/" + savedDifferentTenantUser.getId()).andExpect(status().isOk());
|
||||
|
||||
twoFaSettings.setEnforceTwoFa(false);
|
||||
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
|
||||
}
|
||||
|
||||
private void logInWithMfaToken(String username, String password, Authority expectedScope) throws Exception {
|
||||
@ -459,7 +456,7 @@ public class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
|
||||
|
||||
TotpTwoFaAccountConfig totpTwoFaAccountConfig = (TotpTwoFaAccountConfig) twoFactorAuthService.generateNewAccountConfig(user, TwoFaProviderType.TOTP);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), totpTwoFaAccountConfig);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user, totpTwoFaAccountConfig);
|
||||
return totpTwoFaAccountConfig;
|
||||
}
|
||||
|
||||
@ -477,7 +474,7 @@ public class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
|
||||
SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig();
|
||||
smsTwoFaAccountConfig.setPhoneNumber("+38050505050");
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), smsTwoFaAccountConfig);
|
||||
twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user, smsTwoFaAccountConfig);
|
||||
return smsTwoFaAccountConfig;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user