From bcc736991ed9bf789053dc0b0a051691946723e7 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 4 May 2022 14:29:36 +0300 Subject: [PATCH] Multiple used 2FA providers for account --- .../main/data/upgrade/3.3.4/schema_update.sql | 2 +- ...roller.java => TwoFaConfigController.java} | 107 +-- .../controller/TwoFactorAuthController.java | 44 +- .../auth/mfa/DefaultTwoFactorAuthService.java | 120 ++-- .../auth/mfa/TwoFactorAuthService.java | 21 +- ...er.java => DefaultTwoFaConfigManager.java} | 89 ++- .../auth/mfa/config/TwoFaConfigManager.java | 45 ++ .../config/TwoFactorAuthConfigManager.java | 43 -- ...orAuthProvider.java => TwoFaProvider.java} | 10 +- ...ovider.java => OtpBasedTwoFaProvider.java} | 12 +- ...uthProvider.java => SmsTwoFaProvider.java} | 20 +- ...thProvider.java => TotpTwoFaProvider.java} | 22 +- .../auth/rest/RestAuthenticationProvider.java | 10 +- ...RestAwareAuthenticationSuccessHandler.java | 6 +- .../system/DefaultSystemSecurityService.java | 4 +- .../system/SystemSecurityService.java | 4 +- .../controller/TwoFactorAuthConfigTest.java | 141 ++-- .../server/controller/TwoFactorAuthTest.java | 644 +++++++++--------- .../data/security/UserAuthSettings.java | 4 +- ...ttings.java => PlatformTwoFaSettings.java} | 10 +- .../mfa/account/AccountTwoFaSettings.java | 26 + ...g.java => OtpBasedTwoFaAccountConfig.java} | 4 +- ...Config.java => SmsTwoFaAccountConfig.java} | 8 +- ...onfig.java => TotpTwoFaAccountConfig.java} | 13 +- ...untConfig.java => TwoFaAccountConfig.java} | 14 +- ....java => OtpBasedTwoFaProviderConfig.java} | 2 +- ...onfig.java => SmsTwoFaProviderConfig.java} | 8 +- ...nfig.java => TotpTwoFaProviderConfig.java} | 6 +- ...erConfig.java => TwoFaProviderConfig.java} | 8 +- ...oviderType.java => TwoFaProviderType.java} | 2 +- .../server/common/msg/tools/TbRateLimits.java | 6 + .../server/dao/model/ModelConstants.java | 2 +- .../dao/model/sql/UserAuthSettingsEntity.java | 15 +- .../main/resources/sql/schema-entities.sql | 2 +- 34 files changed, 799 insertions(+), 675 deletions(-) rename application/src/main/java/org/thingsboard/server/controller/{TwoFactorAuthConfigController.java => TwoFaConfigController.java} (70%) rename application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/{DefaultTwoFactorAuthConfigManager.java => DefaultTwoFaConfigManager.java} (62%) create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFactorAuthConfigManager.java rename application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/{TwoFactorAuthProvider.java => TwoFaProvider.java} (84%) rename application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/{OtpBasedTwoFactorAuthProvider.java => OtpBasedTwoFaProvider.java} (87%) rename application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/{SmsTwoFactorAuthProvider.java => SmsTwoFaProvider.java} (73%) rename application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/{TotpTwoFactorAuthProvider.java => TotpTwoFaProvider.java} (76%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/{TwoFactorAuthSettings.java => PlatformTwoFaSettings.java} (92%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/AccountTwoFaSettings.java rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/{OtpBasedTwoFactorAuthAccountConfig.java => OtpBasedTwoFaAccountConfig.java} (82%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/{SmsTwoFactorAuthAccountConfig.java => SmsTwoFaAccountConfig.java} (86%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/{TotpTwoFactorAuthAccountConfig.java => TotpTwoFaAccountConfig.java} (84%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/{TwoFactorAuthAccountConfig.java => TwoFaAccountConfig.java} (79%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/{OtpBasedTwoFactorAuthProviderConfig.java => OtpBasedTwoFaProviderConfig.java} (91%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/{SmsTwoFactorAuthProviderConfig.java => SmsTwoFaProviderConfig.java} (85%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/{TotpTwoFactorAuthProviderConfig.java => TotpTwoFaProviderConfig.java} (85%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/{TwoFactorAuthProviderConfig.java => TwoFaProviderConfig.java} (82%) rename common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/{TwoFactorAuthProviderType.java => TwoFaProviderType.java} (94%) diff --git a/application/src/main/data/upgrade/3.3.4/schema_update.sql b/application/src/main/data/upgrade/3.3.4/schema_update.sql index d2134bdc48..2b1524fcb3 100644 --- a/application/src/main/data/upgrade/3.3.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.3.4/schema_update.sql @@ -18,5 +18,5 @@ CREATE TABLE IF NOT EXISTS user_auth_settings ( id uuid NOT NULL CONSTRAINT user_auth_settings_pkey PRIMARY KEY, created_time bigint NOT NULL, user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id), - mfa_account_config varchar + two_fa_settings varchar ); diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFaConfigController.java similarity index 70% rename from application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java rename to application/src/main/java/org/thingsboard/server/controller/TwoFaConfigController.java index f35f7a231a..6eb192b7e2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFaConfigController.java @@ -21,11 +21,13 @@ import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -33,20 +35,20 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; +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.TotpTwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; -import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.model.SecurityUser; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; - import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -57,9 +59,9 @@ import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; @RequestMapping("/api/2fa") @TbCoreComponent @RequiredArgsConstructor -public class TwoFactorAuthConfigController extends BaseController { +public class TwoFaConfigController extends BaseController { - private final TwoFactorAuthConfigManager twoFactorAuthConfigManager; + private final TwoFaConfigManager twoFaConfigManager; private final TwoFactorAuthService twoFactorAuthService; @@ -71,27 +73,17 @@ public class TwoFactorAuthConfigController extends BaseController { "```\n{\n" + " \"providerType\": \"TOTP\",\n" + " \"authUrl\": \"otpauth://totp/ThingsBoard:tenant@thingsboard.org?issuer=ThingsBoard&secret=FUNBIM3CXFNNGQR6ZIPVWHP65PPFWDII\"\n" + - "}\n```" + NEW_LINE + + "}\n```" + NEW_LINE + "Response example for SMS 2FA: " + NEW_LINE + "```\n{\n" + " \"providerType\": \"SMS\",\n" + " \"phoneNumber\": \"+380505005050\"\n" + "}\n```") - @GetMapping("/account/config") + @GetMapping("/account/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public TwoFactorAuthAccountConfig getTwoFaAccountConfig() throws ThingsboardException { + public AccountTwoFaSettings getAccountTwoFaSettings() throws ThingsboardException { SecurityUser user = getCurrentUser(); - return twoFactorAuthConfigManager.getTwoFaAccountConfig(user.getTenantId(), user.getId()).orElse(null); - } - - - @GetMapping("/providers") - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public List getAvailableTwoFaProviders() throws ThingsboardException { - return twoFactorAuthConfigManager.getTwoFaSettings(getTenantId(), true) - .map(TwoFactorAuthSettings::getProviders).orElse(Collections.emptyList()).stream() - .map(TwoFactorAuthProviderConfig::getProviderType) - .collect(Collectors.toList()); + return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user.getId()).orElse(null); } @@ -116,19 +108,19 @@ public class TwoFactorAuthConfigController extends BaseController { "}\n```") @PostMapping("/account/config/generate") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public TwoFactorAuthAccountConfig generateTwoFaAccountConfig(@ApiParam(value = "2FA provider type to generate new account config for", defaultValue = "TOTP", required = true) - @RequestParam TwoFactorAuthProviderType providerType) throws Exception { + public TwoFaAccountConfig generateTwoFaAccountConfig(@ApiParam(value = "2FA provider type to generate new account config for", defaultValue = "TOTP", required = true) + @RequestParam TwoFaProviderType providerType) throws Exception { SecurityUser user = getCurrentUser(); return twoFactorAuthService.generateNewAccountConfig(user, providerType); } /* TMP */ - @PostMapping("/account/config/generate/qr") + @PostMapping("/account/config/tmp/generate/qr") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public void generateTwoFaAccountConfigWithQr(@RequestParam TwoFactorAuthProviderType providerType, HttpServletResponse response) throws Exception { - TwoFactorAuthAccountConfig config = generateTwoFaAccountConfig(providerType); - if (providerType == TwoFactorAuthProviderType.TOTP) { - BitMatrix qr = new QRCodeWriter().encode(((TotpTwoFactorAuthAccountConfig) config).getAuthUrl(), BarcodeFormat.QR_CODE, 200, 200); + public void generateTwoFaAccountConfigWithQr(@RequestParam TwoFaProviderType providerType, HttpServletResponse response) throws Exception { + TwoFaAccountConfig config = generateTwoFaAccountConfig(providerType); + if (providerType == TwoFaProviderType.TOTP) { + BitMatrix qr = new QRCodeWriter().encode(((TotpTwoFaAccountConfig) config).getAuthUrl(), BarcodeFormat.QR_CODE, 200, 200); try (ServletOutputStream outputStream = response.getOutputStream()) { MatrixToImageWriter.writeToStream(qr, "PNG", outputStream); } @@ -148,7 +140,7 @@ public class TwoFactorAuthConfigController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") public void submitTwoFaAccountConfig(@ApiParam(value = "2FA account config value. For TOTP 2FA config, authUrl value must not be blank and must match specific pattern. " + "For SMS 2FA, phoneNumber property must not be blank and must be of E.164 phone number format.", required = true) - @Valid @RequestBody TwoFactorAuthAccountConfig accountConfig) throws Exception { + @Valid @RequestBody TwoFaAccountConfig accountConfig) throws Exception { SecurityUser user = getCurrentUser(); twoFactorAuthService.prepareVerificationCode(user, accountConfig, false); } @@ -160,36 +152,57 @@ public class TwoFactorAuthConfigController extends BaseController { ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER) @PostMapping("/account/config") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public void verifyAndSaveTwoFaAccountConfig(@ApiParam(value = "2FA account config to save. Validation rules are the same as in submitTwoFaAccountConfig API method", required = true) - @Valid @RequestBody TwoFactorAuthAccountConfig accountConfig, - @ApiParam(value = "6-digit code from an authenticator app in case of TOTP 2FA, or the one sent via an SMS message in case of SMS 2FA", required = true) - @RequestParam String verificationCode) throws Exception { + public AccountTwoFaSettings verifyAndSaveTwoFaAccountConfig(@ApiParam(value = "2FA account config to save. Validation rules are the same as in submitTwoFaAccountConfig API method", required = true) + @Valid @RequestBody TwoFaAccountConfig accountConfig, + @ApiParam(value = "6-digit code from an authenticator app in case of TOTP 2FA, or the one sent via an SMS message in case of SMS 2FA", required = true) + @RequestParam String verificationCode) throws Exception { SecurityUser user = getCurrentUser(); boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, verificationCode, accountConfig, false); if (verificationSuccess) { - twoFactorAuthConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig); + return twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig); } else { throw new ThingsboardException("Verification code is incorrect", ThingsboardErrorCode.INVALID_ARGUMENTS); } } + @PutMapping("/account/config") + public AccountTwoFaSettings updateTwoFaAccountConfig(@RequestParam TwoFaProviderType providerType, + @RequestBody TwoFaAccountConfigUpdateRequest updateRequest) throws ThingsboardException { + SecurityUser user = getCurrentUser(); + TwoFaAccountConfig accountConfig = twoFaConfigManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType) + .orElseThrow(() -> new IllegalArgumentException("No 2FA config for provider " + providerType)); + + accountConfig.setUseByDefault(updateRequest.isUseByDefault()); + return twoFaConfigManager.saveTwoFaAccountConfig(user.getTenantId(), user.getId(), accountConfig); + } + @ApiOperation(value = "Delete 2FA account config (deleteTwoFaAccountConfig)", notes = "Delete user's 2FA config. " + ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER) @DeleteMapping("/account/config") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - public void deleteTwoFaAccountConfig() throws ThingsboardException { + public AccountTwoFaSettings deleteTwoFaAccountConfig(@RequestParam TwoFaProviderType providerType) throws ThingsboardException { SecurityUser user = getCurrentUser(); - twoFactorAuthConfigManager.deleteTwoFaAccountConfig(user.getTenantId(), user.getId()); + return twoFaConfigManager.deleteTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType); + } + + + @GetMapping("/providers") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + public List getAvailableTwoFaProviders() throws ThingsboardException { + return twoFaConfigManager.getPlatformTwoFaSettings(getTenantId(), true) + .map(PlatformTwoFaSettings::getProviders).orElse(Collections.emptyList()).stream() + .map(TwoFaProviderConfig::getProviderType) + .collect(Collectors.toList()); } - @ApiOperation(value = "Get 2FA settings (getTwoFaSettings)", + @ApiOperation(value = "Get 2FA settings (getTwoFaSettings)", // FIXME [viacheslav] notes = "Get settings for 2FA. If 2FA is not configured, then an empty response will be returned." + ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @GetMapping("/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public TwoFactorAuthSettings getTwoFaSettings() throws ThingsboardException { - return twoFactorAuthConfigManager.getTwoFaSettings(getTenantId(), false).orElse(null); + public PlatformTwoFaSettings getPlatformTwoFaSettings() throws ThingsboardException { + return twoFaConfigManager.getPlatformTwoFaSettings(getTenantId(), false).orElse(null); } @ApiOperation(value = "Save 2FA settings (saveTwoFaSettings)", @@ -198,9 +211,15 @@ public class TwoFactorAuthConfigController extends BaseController { ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PostMapping("/settings") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") - public void saveTwoFaSettings(@ApiParam(value = "Settings value", required = true) - @RequestBody TwoFactorAuthSettings twoFaSettings) throws ThingsboardException { - twoFactorAuthConfigManager.saveTwoFaSettings(getTenantId(), twoFaSettings); + public void savePlatformTwoFaSettings(@ApiParam(value = "Settings value", required = true) + @RequestBody PlatformTwoFaSettings twoFaSettings) throws ThingsboardException { + twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings); + } + + + @Data + public static class TwoFaAccountConfigUpdateRequest { + private boolean useByDefault; } } diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java index 2a74d565b5..a1fe5a7cfc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java @@ -17,6 +17,9 @@ package org.thingsboard.server.controller; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -30,9 +33,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; -import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.JwtTokenPair; import org.thingsboard.server.service.security.model.SecurityUser; @@ -41,6 +43,10 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; @RestController @@ -50,7 +56,7 @@ import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; public class TwoFactorAuthController extends BaseController { private final TwoFactorAuthService twoFactorAuthService; - private final TwoFactorAuthConfigManager twoFactorAuthConfigManager; + private final TwoFaConfigManager twoFaConfigManager; private final JwtTokenFactory tokenFactory; private final SystemSecurityService systemSecurityService; private final UserService userService; @@ -65,9 +71,9 @@ public class TwoFactorAuthController extends BaseController { "and Too Many Requests error if rate limits are exceeded.") @PostMapping("/verification/send") @PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')") - public void requestTwoFaVerificationCode() throws Exception { + public void requestTwoFaVerificationCode(@RequestParam TwoFaProviderType providerType) throws Exception { SecurityUser user = getCurrentUser(); - twoFactorAuthService.prepareVerificationCode(user, true); + twoFactorAuthService.prepareVerificationCode(user, providerType, true); } @ApiOperation(value = "Check 2FA verification code (checkTwoFaVerificationCode)", @@ -79,9 +85,10 @@ public class TwoFactorAuthController extends BaseController { @PostMapping("/verification/check") @PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')") public JwtTokenPair checkTwoFaVerificationCode(@ApiParam(value = "6-digit verification code", required = true) + @RequestParam TwoFaProviderType providerType, @RequestParam String verificationCode, HttpServletRequest servletRequest) throws Exception { SecurityUser user = getCurrentUser(); - boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, verificationCode, true); + boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, providerType, verificationCode, true); if (verificationSuccess) { systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(servletRequest), ActionType.LOGIN, null); user = new SecurityUser(userService.findUserById(user.getTenantId(), user.getId()), true, user.getUserPrincipal()); @@ -93,13 +100,26 @@ public class TwoFactorAuthController extends BaseController { } } - @ApiOperation(value = "Get currently used 2FA provider type (getCurrentlyUsedTwoFaProviderType)") - @GetMapping("/provider/type") + + @GetMapping("/providers") @PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')") - public TwoFactorAuthProviderType getCurrentlyUsedTwoFaProviderType() throws ThingsboardException { + public List getAvailableTwoFaProviders() throws ThingsboardException { SecurityUser user = getCurrentUser(); - return twoFactorAuthConfigManager.getTwoFaAccountConfig(user.getTenantId(), user.getId()) - .map(TwoFactorAuthAccountConfig::getProviderType).orElse(null); + return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user.getId()) + .map(settings -> settings.getConfigs().values()).orElse(Collections.emptyList()) + .stream().map(config -> TwoFaProviderInfo.builder() + .type(config.getProviderType()) + .isDefault(config.isUseByDefault()) + .build()) + .collect(Collectors.toList()); + } + + @Data + @AllArgsConstructor + @Builder + public static class TwoFaProviderInfo { + private TwoFaProviderType type; + private boolean isDefault; } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java index 9b2b0b90bd..fd4f16ccdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java @@ -25,15 +25,15 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 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.provider.TwoFactorAuthProviderType; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; -import org.thingsboard.server.service.security.auth.mfa.provider.TwoFactorAuthProvider; +import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; +import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; +import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; +import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.system.SystemSecurityService; @@ -49,117 +49,129 @@ import java.util.concurrent.ConcurrentMap; @TbCoreComponent public class DefaultTwoFactorAuthService implements TwoFactorAuthService { - private final TwoFactorAuthConfigManager configManager; + private final TwoFaConfigManager configManager; private final SystemSecurityService systemSecurityService; private final UserService userService; - private final Map> providers = new EnumMap<>(TwoFactorAuthProviderType.class); - - // TODO [viacheslav]: these rate limits are local, and will work bad in the cluster - private final ConcurrentMap verificationCodeSendingRateLimits = new ConcurrentHashMap<>(); - private final ConcurrentMap verificationCodeCheckingRateLimits = new ConcurrentHashMap<>(); + private final Map> providers = new EnumMap<>(TwoFaProviderType.class); 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_ERROR = new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS); private static final ThingsboardException PROVIDER_NOT_AVAILABLE_ERROR = new ThingsboardException("2FA provider is not available", ThingsboardErrorCode.GENERAL); + private final ConcurrentMap> verificationCodeSendingRateLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap> verificationCodeCheckingRateLimits = new ConcurrentHashMap<>(); + + @Override + public boolean isTwoFaEnabled(TenantId tenantId, UserId userId) { + return configManager.getAccountTwoFaSettings(tenantId, userId) + .map(settings -> !settings.getConfigs().isEmpty()) + .orElse(false); + } + @Override - public void prepareVerificationCode(SecurityUser securityUser, boolean checkLimits) throws Exception { - TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId()) + public void prepareVerificationCode(SecurityUser user, TwoFaProviderType providerType, boolean checkLimits) throws Exception { + TwoFaAccountConfig accountConfig = configManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType) .orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR); - prepareVerificationCode(securityUser, accountConfig, checkLimits); + prepareVerificationCode(user, accountConfig, checkLimits); } @Override - public void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException { - TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId(), true) + public void prepareVerificationCode(SecurityUser user, TwoFaAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException { + PlatformTwoFaSettings twoFaSettings = configManager.getPlatformTwoFaSettings(user.getTenantId(), true) .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); if (checkLimits) { - if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeSendRateLimit())) { - TbRateLimits rateLimits = verificationCodeSendingRateLimits.computeIfAbsent(securityUser.getId(), sessionId -> { - return new TbRateLimits(twoFaSettings.getVerificationCodeSendRateLimit(), true); - }); - if (!rateLimits.tryConsume()) { - throw new ThingsboardException("Too many verification code sending requests", ThingsboardErrorCode.TOO_MANY_REQUESTS); - } - } + checkRateLimits(user.getId(), accountConfig.getProviderType(), twoFaSettings.getVerificationCodeSendRateLimit(), verificationCodeSendingRateLimits); } - TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) + TwoFaProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); - getTwoFaProvider(accountConfig.getProviderType()).prepareVerificationCode(securityUser, providerConfig, accountConfig); + getTwoFaProvider(accountConfig.getProviderType()).prepareVerificationCode(user, providerConfig, accountConfig); } + @Override - public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean checkLimits) throws ThingsboardException { - TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId()) + public boolean checkVerificationCode(SecurityUser user, TwoFaProviderType providerType, String verificationCode, boolean checkLimits) throws ThingsboardException { + TwoFaAccountConfig accountConfig = configManager.getTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType) .orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR); - return checkVerificationCode(securityUser, verificationCode, accountConfig, checkLimits); + return checkVerificationCode(user, verificationCode, accountConfig, checkLimits); } @Override - public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException { - if (!userService.findUserCredentialsByUserId(securityUser.getTenantId(), securityUser.getId()).isEnabled()) { + public boolean checkVerificationCode(SecurityUser user, String verificationCode, TwoFaAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException { + if (!userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()).isEnabled()) { throw new ThingsboardException("User is disabled", ThingsboardErrorCode.AUTHENTICATION); } - TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId(), true) + PlatformTwoFaSettings twoFaSettings = configManager.getPlatformTwoFaSettings(user.getTenantId(), true) .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); if (checkLimits) { - if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeCheckRateLimit())) { - TbRateLimits rateLimits = verificationCodeCheckingRateLimits.computeIfAbsent(securityUser.getId(), sessionId -> { - return new TbRateLimits(twoFaSettings.getVerificationCodeCheckRateLimit(), true); - }); - if (!rateLimits.tryConsume()) { - throw new ThingsboardException("Too many verification code checking requests", ThingsboardErrorCode.TOO_MANY_REQUESTS); - } - } + checkRateLimits(user.getId(), accountConfig.getProviderType(), twoFaSettings.getVerificationCodeCheckRateLimit(), verificationCodeCheckingRateLimits); } - TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) + TwoFaProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType()) .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); boolean verificationSuccess; if (StringUtils.isNumeric(verificationCode) && verificationCode.length() == 6) { - verificationSuccess = getTwoFaProvider(accountConfig.getProviderType()).checkVerificationCode(securityUser, verificationCode, providerConfig, accountConfig); + verificationSuccess = getTwoFaProvider(accountConfig.getProviderType()).checkVerificationCode(user, verificationCode, providerConfig, accountConfig); } else { verificationSuccess = false; } if (checkLimits) { try { - systemSecurityService.validateTwoFaVerification(securityUser, verificationSuccess, twoFaSettings); + systemSecurityService.validateTwoFaVerification(user, verificationSuccess, twoFaSettings); } catch (LockedException e) { - verificationCodeCheckingRateLimits.remove(securityUser.getId()); - verificationCodeSendingRateLimits.remove(securityUser.getId()); + verificationCodeCheckingRateLimits.remove(user.getId()); + verificationCodeSendingRateLimits.remove(user.getId()); throw new ThingsboardException(e.getMessage(), ThingsboardErrorCode.AUTHENTICATION); } if (verificationSuccess) { - verificationCodeCheckingRateLimits.remove(securityUser.getId()); - verificationCodeSendingRateLimits.remove(securityUser.getId()); + verificationCodeCheckingRateLimits.remove(user.getId()); + verificationCodeSendingRateLimits.remove(user.getId()); } } return verificationSuccess; } + private void checkRateLimits(UserId userId, TwoFaProviderType providerType, String rateLimitConfig, + ConcurrentMap> rateLimits) throws ThingsboardException { + if (StringUtils.isNotEmpty(rateLimitConfig)) { + ConcurrentMap providersRateLimits = rateLimits.computeIfAbsent(userId, i -> new ConcurrentHashMap<>()); + + TbRateLimits rateLimit = providersRateLimits.get(providerType); + if (rateLimit == null || !rateLimit.getConfig().equals(rateLimitConfig)) { + rateLimit = new TbRateLimits(rateLimitConfig, true); + providersRateLimits.put(providerType, rateLimit); + } + if (!rateLimit.tryConsume()) { + throw new ThingsboardException("Too many requests", ThingsboardErrorCode.TOO_MANY_REQUESTS); + } + } else { + rateLimits.remove(userId); + } + } + + @Override - public TwoFactorAuthAccountConfig generateNewAccountConfig(User user, TwoFactorAuthProviderType providerType) throws ThingsboardException { - TwoFactorAuthProviderConfig providerConfig = getTwoFaProviderConfig(user.getTenantId(), providerType); + public TwoFaAccountConfig generateNewAccountConfig(User user, TwoFaProviderType providerType) throws ThingsboardException { + TwoFaProviderConfig providerConfig = getTwoFaProviderConfig(user.getTenantId(), providerType); return getTwoFaProvider(providerType).generateNewAccountConfig(user, providerConfig); } - private TwoFactorAuthProviderConfig getTwoFaProviderConfig(TenantId tenantId, TwoFactorAuthProviderType providerType) throws ThingsboardException { - return configManager.getTwoFaSettings(tenantId, true) + private TwoFaProviderConfig getTwoFaProviderConfig(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException { + return configManager.getPlatformTwoFaSettings(tenantId, true) .flatMap(twoFaSettings -> twoFaSettings.getProviderConfig(providerType)) .orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR); } - private TwoFactorAuthProvider getTwoFaProvider(TwoFactorAuthProviderType providerType) throws ThingsboardException { + private TwoFaProvider getTwoFaProvider(TwoFaProviderType providerType) throws ThingsboardException { return Optional.ofNullable(providers.get(providerType)) .orElseThrow(() -> PROVIDER_NOT_AVAILABLE_ERROR); } @Autowired - private void setProviders(Collection providers) { + private void setProviders(Collection providers) { providers.forEach(provider -> { this.providers.put(provider.getType(), provider); }); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java index d84236cfb5..b959f94acb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java @@ -17,20 +17,27 @@ 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.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +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 { - void prepareVerificationCode(SecurityUser securityUser, boolean checkLimits) throws Exception; + boolean isTwoFaEnabled(TenantId tenantId, UserId userId); - void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException; - boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean checkLimits) throws ThingsboardException; + void prepareVerificationCode(SecurityUser user, TwoFaProviderType providerType, boolean checkLimits) throws Exception; - boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException; + void prepareVerificationCode(SecurityUser user, TwoFaAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException; - TwoFactorAuthAccountConfig generateNewAccountConfig(User user, TwoFactorAuthProviderType providerType) throws ThingsboardException; + + boolean checkVerificationCode(SecurityUser user, TwoFaProviderType providerType, String verificationCode, boolean checkLimits) throws ThingsboardException; + + boolean checkVerificationCode(SecurityUser user, String verificationCode, TwoFaAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException; + + + TwoFaAccountConfig generateNewAccountConfig(User user, TwoFaProviderType providerType) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFactorAuthConfigManager.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java similarity index 62% rename from application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFactorAuthConfigManager.java rename to application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java index a74b8374af..9f9b4e4b0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFactorAuthConfigManager.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/DefaultTwoFaConfigManager.java @@ -21,17 +21,16 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -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.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.security.UserAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +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; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.settings.AdminSettingsDao; @@ -39,12 +38,15 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserAuthSettingsDao; import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; @Service @RequiredArgsConstructor -public class DefaultTwoFactorAuthConfigManager implements TwoFactorAuthConfigManager { +public class DefaultTwoFaConfigManager implements TwoFaConfigManager { private final UserAuthSettingsDao userAuthSettingsDao; private final AdminSettingsService adminSettingsService; @@ -55,60 +57,77 @@ public class DefaultTwoFactorAuthConfigManager implements TwoFactorAuthConfigMan @Override - public boolean isTwoFaEnabled(TenantId tenantId, UserId userId) { - return getTwoFaAccountConfig(tenantId, userId).isPresent(); + public Optional getAccountTwoFaSettings(TenantId tenantId, UserId userId) { + return Optional.ofNullable(userAuthSettingsDao.findByUserId(userId)) + .flatMap(userAuthSettings -> Optional.ofNullable(userAuthSettings.getTwoFaSettings())) + .map(twoFaSettings -> { + twoFaSettings.getConfigs().keySet().removeIf(providerType -> { + return getTwoFaProviderConfig(tenantId, providerType).isEmpty(); + }); + return twoFaSettings; + }); } @Override - public Optional getTwoFaAccountConfig(TenantId tenantId, UserId userId) { - return Optional.ofNullable(userAuthSettingsDao.findByUserId(userId)) - .flatMap(userAuthSettings -> Optional.ofNullable(userAuthSettings.getTwoFaAccountConfig())) - .filter(twoFaAccountConfig -> getTwoFaProviderConfig(tenantId, twoFaAccountConfig.getProviderType()).isPresent()); + public Optional getTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType) { + return getAccountTwoFaSettings(tenantId, userId) + .map(AccountTwoFaSettings::getConfigs) + .flatMap(configs -> Optional.ofNullable(configs.get(providerType))); } @Override - public void saveTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFactorAuthAccountConfig accountConfig) throws ThingsboardException { + public AccountTwoFaSettings saveTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaAccountConfig accountConfig) { getTwoFaProviderConfig(tenantId, accountConfig.getProviderType()) - .orElseThrow(() -> new ThingsboardException("2FA provider is not configured", ThingsboardErrorCode.BAD_REQUEST_PARAMS)); + .orElseThrow(() -> new IllegalArgumentException("2FA provider is not configured")); + + return createOrUpdateAccountTwoFaSettings(tenantId, userId, accountTwoFaSettings -> { + Map configs = accountTwoFaSettings.getConfigs(); + configs.put(accountConfig.getProviderType(), accountConfig); + }); + } + + @Override + public AccountTwoFaSettings deleteTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType) { + return createOrUpdateAccountTwoFaSettings(tenantId, userId, accountTwoFaSettings -> { + accountTwoFaSettings.getConfigs().keySet().removeIf(providerType::equals); + }); + } + private AccountTwoFaSettings createOrUpdateAccountTwoFaSettings(TenantId tenantId, UserId userId, Consumer updater) { UserAuthSettings userAuthSettings = Optional.ofNullable(userAuthSettingsDao.findByUserId(userId)) .orElseGet(() -> { UserAuthSettings newUserAuthSettings = new UserAuthSettings(); newUserAuthSettings.setUserId(userId); + + AccountTwoFaSettings newAccountTwoFaSettings = new AccountTwoFaSettings(); + newAccountTwoFaSettings.setConfigs(new LinkedHashMap<>()); + newUserAuthSettings.setTwoFaSettings(newAccountTwoFaSettings); return newUserAuthSettings; }); - userAuthSettings.setTwoFaAccountConfig(accountConfig); + updater.accept(userAuthSettings.getTwoFaSettings()); userAuthSettingsDao.save(tenantId, userAuthSettings); - } - - @Override - public void deleteTwoFaAccountConfig(TenantId tenantId, UserId userId) { - Optional.ofNullable(userAuthSettingsDao.findByUserId(userId)) - .ifPresent(userAuthSettings -> { - userAuthSettings.setTwoFaAccountConfig(null); - userAuthSettingsDao.save(tenantId, userAuthSettings); - }); + return userAuthSettings.getTwoFaSettings(); } - private Optional getTwoFaProviderConfig(TenantId tenantId, TwoFactorAuthProviderType providerType) { - return getTwoFaSettings(tenantId, true) + private Optional getTwoFaProviderConfig(TenantId tenantId, TwoFaProviderType providerType) { + return getPlatformTwoFaSettings(tenantId, true) .flatMap(twoFaSettings -> twoFaSettings.getProviderConfig(providerType)); } @SneakyThrows({InterruptedException.class, ExecutionException.class}) @Override - public Optional getTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault) { + public Optional getPlatformTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault) { if (tenantId.equals(TenantId.SYS_TENANT_ID)) { return Optional.ofNullable(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, TWO_FACTOR_AUTH_SETTINGS_KEY)) - .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), TwoFactorAuthSettings.class)); + .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), PlatformTwoFaSettings.class)); } else { - Optional tenantTwoFaSettings = attributesService.find(TenantId.SYS_TENANT_ID, tenantId, - DataConstants.SERVER_SCOPE, TWO_FACTOR_AUTH_SETTINGS_KEY).get() - .map(adminSettingsAttribute -> JacksonUtil.fromString(adminSettingsAttribute.getJsonValue().get(), TwoFactorAuthSettings.class)); + Optional tenantTwoFaSettings = attributesService.find(TenantId.SYS_TENANT_ID, tenantId, + DataConstants.SERVER_SCOPE, TWO_FACTOR_AUTH_SETTINGS_KEY).get() + .map(adminSettingsAttribute -> JacksonUtil.fromString(adminSettingsAttribute.getJsonValue().get(), PlatformTwoFaSettings.class)); if (sysadminSettingsAsDefault) { if (tenantTwoFaSettings.isEmpty() || tenantTwoFaSettings.get().isUseSystemTwoFactorAuthSettings()) { - return getTwoFaSettings(TenantId.SYS_TENANT_ID, false); + return getPlatformTwoFaSettings(TenantId.SYS_TENANT_ID, false); } } return tenantTwoFaSettings; @@ -117,7 +136,7 @@ public class DefaultTwoFactorAuthConfigManager implements TwoFactorAuthConfigMan @SneakyThrows({InterruptedException.class, ExecutionException.class}) @Override - public void saveTwoFaSettings(TenantId tenantId, TwoFactorAuthSettings twoFactorAuthSettings) { + public void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings) { if (tenantId.equals(TenantId.SYS_TENANT_ID) || !twoFactorAuthSettings.isUseSystemTwoFactorAuthSettings()) { ConstraintValidator.validateFields(twoFactorAuthSettings); } @@ -139,7 +158,7 @@ public class DefaultTwoFactorAuthConfigManager implements TwoFactorAuthConfigMan @SneakyThrows({InterruptedException.class, ExecutionException.class}) @Override - public void deleteTwoFaSettings(TenantId tenantId) { + public void deletePlatformTwoFaSettings(TenantId tenantId) { if (tenantId.equals(TenantId.SYS_TENANT_ID)) { Optional.ofNullable(adminSettingsService.findAdminSettingsByKey(tenantId, TWO_FACTOR_AUTH_SETTINGS_KEY)) .ifPresent(adminSettings -> adminSettingsDao.removeById(tenantId, adminSettings.getId().getId())); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java new file mode 100644 index 0000000000..b0073338b7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFaConfigManager.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.auth.mfa.config; + +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; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; + +import java.util.Optional; + +public interface TwoFaConfigManager { + + + Optional getAccountTwoFaSettings(TenantId tenantId, UserId userId); + + Optional getTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType); + + AccountTwoFaSettings saveTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaAccountConfig accountConfig); + + AccountTwoFaSettings deleteTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFaProviderType providerType); + + + Optional getPlatformTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault); + + void savePlatformTwoFaSettings(TenantId tenantId, PlatformTwoFaSettings twoFactorAuthSettings); + + void deletePlatformTwoFaSettings(TenantId tenantId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFactorAuthConfigManager.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFactorAuthConfigManager.java deleted file mode 100644 index d1c5999e7e..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/config/TwoFactorAuthConfigManager.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.security.auth.mfa.config; - -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.TwoFactorAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; - -import java.util.Optional; - -public interface TwoFactorAuthConfigManager { - - boolean isTwoFaEnabled(TenantId tenantId, UserId userId); - - Optional getTwoFaAccountConfig(TenantId tenantId, UserId userId); - - void saveTwoFaAccountConfig(TenantId tenantId, UserId userId, TwoFactorAuthAccountConfig accountConfig) throws ThingsboardException; - - void deleteTwoFaAccountConfig(TenantId tenantId, UserId userId); - - - Optional getTwoFaSettings(TenantId tenantId, boolean sysadminSettingsAsDefault); - - void saveTwoFaSettings(TenantId tenantId, TwoFactorAuthSettings twoFactorAuthSettings); - - void deleteTwoFaSettings(TenantId tenantId); - -} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java similarity index 84% rename from application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProvider.java rename to application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java index 6961aeeaec..4d4db92af1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFaProvider.java @@ -17,12 +17,12 @@ package org.thingsboard.server.service.security.auth.mfa.provider; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.service.security.model.SecurityUser; -public interface TwoFactorAuthProvider { +public interface TwoFaProvider { A generateNewAccountConfig(User user, C providerConfig); @@ -31,6 +31,6 @@ public interface TwoFactorAuthProvider implements TwoFactorAuthProvider { +public abstract class OtpBasedTwoFaProvider implements TwoFaProvider { private final Cache verificationCodesCache; - protected OtpBasedTwoFactorAuthProvider(CacheManager cacheManager) { + protected OtpBasedTwoFaProvider(CacheManager cacheManager) { this.verificationCodesCache = cacheManager.getCache(CacheConstants.TWO_FA_VERIFICATION_CODES_CACHE); } @@ -70,7 +70,7 @@ public abstract class OtpBasedTwoFactorAuthProvider { +public class SmsTwoFaProvider extends OtpBasedTwoFaProvider { private final SmsService smsService; - public SmsTwoFactorAuthProvider(CacheManager cacheManager, SmsService smsService) { + public SmsTwoFaProvider(CacheManager cacheManager, SmsService smsService) { super(cacheManager); this.smsService = smsService; } @Override - public SmsTwoFactorAuthAccountConfig generateNewAccountConfig(User user, SmsTwoFactorAuthProviderConfig providerConfig) { - return new SmsTwoFactorAuthAccountConfig(); + public SmsTwoFaAccountConfig generateNewAccountConfig(User user, SmsTwoFaProviderConfig providerConfig) { + return new SmsTwoFaAccountConfig(); } @Override - protected void sendVerificationCode(SecurityUser user, String verificationCode, SmsTwoFactorAuthProviderConfig providerConfig, SmsTwoFactorAuthAccountConfig accountConfig) throws ThingsboardException { + protected void sendVerificationCode(SecurityUser user, String verificationCode, SmsTwoFaProviderConfig providerConfig, SmsTwoFaAccountConfig accountConfig) throws ThingsboardException { Map messageData = Map.of( "verificationCode", verificationCode, "userEmail", user.getEmail() @@ -60,8 +60,8 @@ public class SmsTwoFactorAuthProvider extends OtpBasedTwoFactorAuthProvider { +public class TotpTwoFaProvider implements TwoFaProvider { @Override - public final TotpTwoFactorAuthAccountConfig generateNewAccountConfig(User user, TotpTwoFactorAuthProviderConfig providerConfig) { - TotpTwoFactorAuthAccountConfig config = new TotpTwoFactorAuthAccountConfig(); + public final TotpTwoFaAccountConfig generateNewAccountConfig(User user, TotpTwoFaProviderConfig providerConfig) { + TotpTwoFaAccountConfig config = new TotpTwoFaAccountConfig(); String secretKey = generateSecretKey(); config.setAuthUrl(getTotpAuthUrl(user, secretKey, providerConfig)); return config; } @Override - public final boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TotpTwoFactorAuthProviderConfig providerConfig, TotpTwoFactorAuthAccountConfig accountConfig) { + public final boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TotpTwoFaProviderConfig providerConfig, TotpTwoFaAccountConfig accountConfig) { String secretKey = UriComponentsBuilder.fromUriString(accountConfig.getAuthUrl()).build().getQueryParams().getFirst("secret"); return new Totp(secretKey).verify(verificationCode); } @SneakyThrows - private String getTotpAuthUrl(User user, String secretKey, TotpTwoFactorAuthProviderConfig providerConfig) { + private String getTotpAuthUrl(User user, String secretKey, TotpTwoFaProviderConfig providerConfig) { URIBuilder uri = new URIBuilder() .setScheme("otpauth") .setHost("totp") @@ -67,8 +67,8 @@ public class TotpTwoFactorAuthProvider implements TwoFactorAuthProvider Optional.ofNullable(settings.getTotalAllowedTimeForVerification())).orElse((int) TimeUnit.MINUTES.toSeconds(30)); tokenPair.setToken(tokenFactory.createPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken()); tokenPair.setRefreshToken(null); diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 82607cf70b..57d009070c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -54,7 +54,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserServiceImpl; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; +import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; import org.thingsboard.server.service.security.model.SecurityUser; @@ -160,7 +160,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { } @Override - public void validateTwoFaVerification(SecurityUser securityUser, boolean verificationSuccess, TwoFactorAuthSettings twoFaSettings) { + public void validateTwoFaVerification(SecurityUser securityUser, boolean verificationSuccess, PlatformTwoFaSettings twoFaSettings) { TenantId tenantId = securityUser.getTenantId(); UserId userId = securityUser.getId(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index 90241f723b..6173d408c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; +import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; import org.thingsboard.server.service.security.model.SecurityUser; import javax.servlet.http.HttpServletRequest; @@ -36,7 +36,7 @@ public interface SystemSecurityService { void validateUserCredentials(TenantId tenantId, UserCredentials userCredentials, String username, String password) throws AuthenticationException; - void validateTwoFaVerification(SecurityUser securityUser, boolean verificationSuccess, TwoFactorAuthSettings twoFaSettings); + void validateTwoFaVerification(SecurityUser securityUser, boolean verificationSuccess, PlatformTwoFaSettings twoFaSettings); void validatePassword(TenantId tenantId, String password, UserCredentials userCredentials) throws DataValidationException; diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthConfigTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthConfigTest.java index 6e3f11160f..954d313480 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthConfigTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthConfigTest.java @@ -30,17 +30,18 @@ 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.service.security.auth.mfa.config.TwoFactorAuthConfigManager; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; -import org.thingsboard.server.service.security.auth.mfa.provider.impl.OtpBasedTwoFactorAuthProvider; -import org.thingsboard.server.service.security.auth.mfa.provider.impl.TotpTwoFactorAuthProvider; +import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; +import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; +import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; +import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; +import org.thingsboard.server.service.security.auth.mfa.provider.impl.OtpBasedTwoFaProvider; +import org.thingsboard.server.service.security.auth.mfa.provider.impl.TotpTwoFaProvider; import java.util.Arrays; import java.util.Collections; @@ -58,13 +59,15 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @SpyBean - private TotpTwoFactorAuthProvider totpTwoFactorAuthProvider; + private TotpTwoFaProvider totpTwoFactorAuthProvider; @MockBean private SmsService smsService; @Autowired private CacheManager cacheManager; @Autowired - private TwoFactorAuthConfigManager twoFactorAuthConfigManager; + private TwoFaConfigManager twoFaConfigManager; + @Autowired + private TwoFactorAuthService twoFactorAuthService; @Before public void beforeEach() throws Exception { @@ -73,8 +76,8 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @After public void afterEach() { - twoFactorAuthConfigManager.deleteTwoFaSettings(TenantId.SYS_TENANT_ID); - twoFactorAuthConfigManager.deleteTwoFaSettings(tenantId); + twoFaConfigManager.deletePlatformTwoFaSettings(TenantId.SYS_TENANT_ID); + twoFaConfigManager.deletePlatformTwoFaSettings(tenantId); } @@ -88,13 +91,13 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { } private void testSaveTestTwoFaSettings() throws Exception { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = new TotpTwoFactorAuthProviderConfig(); + TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig(); totpTwoFaProviderConfig.setIssuerName("tb"); - SmsTwoFactorAuthProviderConfig smsTwoFaProviderConfig = new SmsTwoFactorAuthProviderConfig(); + SmsTwoFaProviderConfig smsTwoFaProviderConfig = new SmsTwoFaProviderConfig(); smsTwoFaProviderConfig.setSmsVerificationMessageTemplate("${verificationCode}"); smsTwoFaProviderConfig.setVerificationCodeLifetime(60); - TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings(); + PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); twoFaSettings.setProviders(List.of(totpTwoFaProviderConfig, smsTwoFaProviderConfig)); twoFaSettings.setVerificationCodeSendRateLimit("1:60"); twoFaSettings.setVerificationCodeCheckRateLimit("3:900"); @@ -103,7 +106,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk()); - TwoFactorAuthSettings savedTwoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), TwoFactorAuthSettings.class); + PlatformTwoFaSettings savedTwoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), PlatformTwoFaSettings.class); assertThat(savedTwoFaSettings.getProviders()).hasSize(2); assertThat(savedTwoFaSettings.getProviders()).contains(totpTwoFaProviderConfig, smsTwoFaProviderConfig); @@ -113,7 +116,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { public void testSaveTwoFaSettings_validationError() throws Exception { loginTenantAdmin(); - TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings(); + PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); twoFaSettings.setProviders(Collections.emptyList()); twoFaSettings.setVerificationCodeSendRateLimit("ab:aba"); twoFaSettings.setVerificationCodeCheckRateLimit("0:12"); @@ -146,19 +149,19 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @Test public void testGetTwoFaSettings_useSysadminSettingsAsDefault() throws Exception { loginSysAdmin(); - TwoFactorAuthSettings sysadminTwoFaSettings = new TwoFactorAuthSettings(); - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = new TotpTwoFactorAuthProviderConfig(); + PlatformTwoFaSettings sysadminTwoFaSettings = new PlatformTwoFaSettings(); + TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig(); totpTwoFaProviderConfig.setIssuerName("tb"); sysadminTwoFaSettings.setProviders(Collections.singletonList(totpTwoFaProviderConfig)); sysadminTwoFaSettings.setMaxVerificationFailuresBeforeUserLockout(25); doPost("/api/2fa/settings", sysadminTwoFaSettings).andExpect(status().isOk()); loginTenantAdmin(); - TwoFactorAuthSettings tenantTwoFaSettings = new TwoFactorAuthSettings(); + PlatformTwoFaSettings tenantTwoFaSettings = new PlatformTwoFaSettings(); tenantTwoFaSettings.setUseSystemTwoFactorAuthSettings(true); tenantTwoFaSettings.setProviders(Collections.emptyList()); doPost("/api/2fa/settings", tenantTwoFaSettings).andExpect(status().isOk()); - TwoFactorAuthSettings twoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), TwoFactorAuthSettings.class); + PlatformTwoFaSettings twoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), PlatformTwoFaSettings.class); assertThat(twoFaSettings).isEqualTo(tenantTwoFaSettings); doPost("/api/2fa/account/config/generate?providerType=TOTP") @@ -168,7 +171,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { tenantTwoFaSettings.setProviders(Collections.emptyList()); tenantTwoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10); doPost("/api/2fa/settings", tenantTwoFaSettings).andExpect(status().isOk()); - twoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), TwoFactorAuthSettings.class); + twoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), PlatformTwoFaSettings.class); assertThat(twoFaSettings).isEqualTo(tenantTwoFaSettings); assertThat(getErrorMessage(doPost("/api/2fa/account/config/generate?providerType=TOTP") @@ -192,13 +195,13 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { .andExpect(status().isOk()); loginSysAdmin(); - twoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), TwoFactorAuthSettings.class); + twoFaSettings = readResponse(doGet("/api/2fa/settings").andExpect(status().isOk()), PlatformTwoFaSettings.class); assertThat(twoFaSettings).isEqualTo(sysadminTwoFaSettings); } @Test public void testSaveTotpTwoFaProviderConfig_validationError() throws Exception { - TotpTwoFactorAuthProviderConfig invalidTotpTwoFaProviderConfig = new TotpTwoFactorAuthProviderConfig(); + TotpTwoFaProviderConfig invalidTotpTwoFaProviderConfig = new TotpTwoFaProviderConfig(); invalidTotpTwoFaProviderConfig.setIssuerName(" "); String errorResponse = saveTwoFaSettingsAndGetError(invalidTotpTwoFaProviderConfig); @@ -207,7 +210,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @Test public void testSaveSmsTwoFaProviderConfig_validationError() throws Exception { - SmsTwoFactorAuthProviderConfig invalidSmsTwoFaProviderConfig = new SmsTwoFactorAuthProviderConfig(); + SmsTwoFaProviderConfig invalidSmsTwoFaProviderConfig = new SmsTwoFaProviderConfig(); invalidSmsTwoFaProviderConfig.setSmsVerificationMessageTemplate("does not contain verification code"); invalidSmsTwoFaProviderConfig.setVerificationCodeLifetime(60); @@ -221,8 +224,8 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { assertThat(errorResponse).containsIgnoringCase("verification code lifetime is required"); } - private String saveTwoFaSettingsAndGetError(TwoFactorAuthProviderConfig invalidTwoFaProviderConfig) throws Exception { - TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings(); + private String saveTwoFaSettingsAndGetError(TwoFaProviderConfig invalidTwoFaProviderConfig) throws Exception { + PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); twoFaSettings.setProviders(Collections.singletonList(invalidTwoFaProviderConfig)); return getErrorMessage(doPost("/api/2fa/settings", twoFaSettings) @@ -235,12 +238,12 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { loginTenantAdmin(); - TwoFactorAuthProviderType notConfiguredProviderType = TwoFactorAuthProviderType.TOTP; + TwoFaProviderType notConfiguredProviderType = TwoFaProviderType.TOTP; String errorMessage = getErrorMessage(doPost("/api/2fa/account/config/generate?providerType=" + notConfiguredProviderType) .andExpect(status().isBadRequest())); assertThat(errorMessage).containsIgnoringCase("provider is not configured"); - TotpTwoFactorAuthAccountConfig notConfiguredProviderAccountConfig = new TotpTwoFactorAuthAccountConfig(); + TotpTwoFaAccountConfig notConfiguredProviderAccountConfig = new TotpTwoFaAccountConfig(); notConfiguredProviderAccountConfig.setAuthUrl("otpauth://totp/aba:aba?issuer=aba&secret=ABA"); errorMessage = getErrorMessage(doPost("/api/2fa/account/config/submit", notConfiguredProviderAccountConfig)); assertThat(errorMessage).containsIgnoringCase("provider is not configured"); @@ -248,7 +251,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @Test public void testGenerateTotpTwoFaAccountConfig() throws Exception { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); + TotpTwoFaProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); loginTenantAdmin(); @@ -258,11 +261,11 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @Test public void testSubmitTotpTwoFaAccountConfig() throws Exception { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); + TotpTwoFaProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); loginTenantAdmin(); - TotpTwoFactorAuthAccountConfig generatedTotpTwoFaAccountConfig = generateTotpTwoFaAccountConfig(totpTwoFaProviderConfig); + TotpTwoFaAccountConfig generatedTotpTwoFaAccountConfig = generateTotpTwoFaAccountConfig(totpTwoFaProviderConfig); doPost("/api/2fa/account/config/submit", generatedTotpTwoFaAccountConfig).andExpect(status().isOk()); verify(totpTwoFactorAuthProvider).prepareVerificationCode(argThat(user -> user.getEmail().equals(TENANT_ADMIN_EMAIL)), eq(totpTwoFaProviderConfig), eq(generatedTotpTwoFaAccountConfig)); @@ -274,7 +277,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { loginTenantAdmin(); - TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = new TotpTwoFactorAuthAccountConfig(); + TotpTwoFaAccountConfig totpTwoFaAccountConfig = new TotpTwoFaAccountConfig(); totpTwoFaAccountConfig.setAuthUrl(null); String errorMessage = getErrorMessage(doPost("/api/2fa/account/config/submit", totpTwoFaAccountConfig) @@ -293,11 +296,11 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @Test public void testVerifyAndSaveTotpTwoFaAccountConfig() throws Exception { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); + TotpTwoFaProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); loginTenantAdmin(); - TotpTwoFactorAuthAccountConfig generatedTotpTwoFaAccountConfig = generateTotpTwoFaAccountConfig(totpTwoFaProviderConfig); + TotpTwoFaAccountConfig generatedTotpTwoFaAccountConfig = generateTotpTwoFaAccountConfig(totpTwoFaProviderConfig); String secret = UriComponentsBuilder.fromUriString(generatedTotpTwoFaAccountConfig.getAuthUrl()).build() .getQueryParams().getFirst("secret"); @@ -306,17 +309,17 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { doPost("/api/2fa/account/config?verificationCode=" + correctVerificationCode, generatedTotpTwoFaAccountConfig) .andExpect(status().isOk()); - TwoFactorAuthAccountConfig twoFaAccountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFactorAuthAccountConfig.class); + TwoFaAccountConfig twoFaAccountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFaAccountConfig.class); assertThat(twoFaAccountConfig).isEqualTo(generatedTotpTwoFaAccountConfig); } @Test public void testVerifyAndSaveTotpTwoFaAccountConfig_incorrectVerificationCode() throws Exception { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); + TotpTwoFaProviderConfig totpTwoFaProviderConfig = configureTotpTwoFaProvider(); loginTenantAdmin(); - TotpTwoFactorAuthAccountConfig generatedTotpTwoFaAccountConfig = generateTotpTwoFaAccountConfig(totpTwoFaProviderConfig); + TotpTwoFaAccountConfig generatedTotpTwoFaAccountConfig = generateTotpTwoFaAccountConfig(totpTwoFaProviderConfig); String incorrectVerificationCode = "100000"; String errorMessage = getErrorMessage(doPost("/api/2fa/account/config?verificationCode=" + incorrectVerificationCode, generatedTotpTwoFaAccountConfig) @@ -325,12 +328,12 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { assertThat(errorMessage).containsIgnoringCase("verification code is incorrect"); } - private TotpTwoFactorAuthAccountConfig generateTotpTwoFaAccountConfig(TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig) throws Exception { - TwoFactorAuthAccountConfig generatedTwoFaAccountConfig = readResponse(doPost("/api/2fa/account/config/generate?providerType=TOTP") - .andExpect(status().isOk()), TwoFactorAuthAccountConfig.class); - assertThat(generatedTwoFaAccountConfig).isInstanceOf(TotpTwoFactorAuthAccountConfig.class); + 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(((TotpTwoFactorAuthAccountConfig) generatedTwoFaAccountConfig)).satisfies(accountConfig -> { + assertThat(((TotpTwoFaAccountConfig) generatedTwoFaAccountConfig)).satisfies(accountConfig -> { UriComponents otpAuthUrl = UriComponentsBuilder.fromUriString(accountConfig.getAuthUrl()).build(); assertThat(otpAuthUrl.getScheme()).isEqualTo("otpauth"); assertThat(otpAuthUrl.getHost()).isEqualTo("totp"); @@ -340,14 +343,14 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { assertDoesNotThrow(() -> Base32.decode(secretKey)); }); }); - return (TotpTwoFactorAuthAccountConfig) generatedTwoFaAccountConfig; + return (TotpTwoFaAccountConfig) generatedTwoFaAccountConfig; } @Test public void testGetTwoFaAccountConfig_whenProviderNotConfigured() throws Exception { testVerifyAndSaveTotpTwoFaAccountConfig(); assertThat(readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), - TotpTwoFactorAuthAccountConfig.class)).isNotNull(); + TotpTwoFaAccountConfig.class)).isNotNull(); loginSysAdmin(); @@ -371,13 +374,13 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { loginTenantAdmin(); - SmsTwoFactorAuthAccountConfig smsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); smsTwoFaAccountConfig.setPhoneNumber("+38054159785"); doPost("/api/2fa/account/config/submit", smsTwoFaAccountConfig).andExpect(status().isOk()); String verificationCode = cacheManager.getCache(CacheConstants.TWO_FA_VERIFICATION_CODES_CACHE) - .get(tenantAdminUserId, OtpBasedTwoFactorAuthProvider.Otp.class).getValue(); + .get(tenantAdminUserId, OtpBasedTwoFaProvider.Otp.class).getValue(); verify(smsService).sendSms(eq(tenantId), any(), argThat(phoneNumbers -> { return phoneNumbers[0].equals(smsTwoFaAccountConfig.getPhoneNumber()); @@ -388,7 +391,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { public void testSubmitSmsTwoFaAccountConfig_validationError() throws Exception { configureSmsTwoFaProvider("${verificationCode}"); - SmsTwoFactorAuthAccountConfig smsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); String blankPhoneNumber = ""; smsTwoFaAccountConfig.setPhoneNumber(blankPhoneNumber); @@ -410,7 +413,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { loginTenantAdmin(); - SmsTwoFactorAuthAccountConfig smsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); smsTwoFaAccountConfig.setPhoneNumber("+38051889445"); ArgumentCaptor verificationCodeCaptor = ArgumentCaptor.forClass(String.class); @@ -424,7 +427,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { doPost("/api/2fa/account/config?verificationCode=" + correctVerificationCode, smsTwoFaAccountConfig) .andExpect(status().isOk()); - TwoFactorAuthAccountConfig accountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFactorAuthAccountConfig.class); + TwoFaAccountConfig accountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFaAccountConfig.class); assertThat(accountConfig).isEqualTo(smsTwoFaAccountConfig); } @@ -434,7 +437,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { loginTenantAdmin(); - SmsTwoFactorAuthAccountConfig smsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); smsTwoFaAccountConfig.setPhoneNumber("+38051889445"); String errorMessage = getErrorMessage(doPost("/api/2fa/account/config?verificationCode=100000", smsTwoFaAccountConfig) @@ -447,7 +450,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { configureSmsTwoFaProvider("${verificationCode}"); loginTenantAdmin(); - SmsTwoFactorAuthAccountConfig initialSmsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig initialSmsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); initialSmsTwoFaAccountConfig.setPhoneNumber("+38051889445"); ArgumentCaptor verificationCodeCaptor = ArgumentCaptor.forClass(String.class); @@ -459,7 +462,7 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { String correctVerificationCode = verificationCodeCaptor.getValue(); - SmsTwoFactorAuthAccountConfig anotherSmsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig anotherSmsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); anotherSmsTwoFaAccountConfig.setPhoneNumber("+38111111111"); String errorMessage = getErrorMessage(doPost("/api/2fa/account/config?verificationCode=" + correctVerificationCode, anotherSmsTwoFaAccountConfig) .andExpect(status().isBadRequest())); @@ -467,20 +470,20 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { doPost("/api/2fa/account/config?verificationCode=" + correctVerificationCode, initialSmsTwoFaAccountConfig) .andExpect(status().isOk()); - TwoFactorAuthAccountConfig accountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFactorAuthAccountConfig.class); + TwoFaAccountConfig accountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFaAccountConfig.class); assertThat(accountConfig).isEqualTo(initialSmsTwoFaAccountConfig); } - private TotpTwoFactorAuthProviderConfig configureTotpTwoFaProvider() throws Exception { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = new TotpTwoFactorAuthProviderConfig(); + private TotpTwoFaProviderConfig configureTotpTwoFaProvider() throws Exception { + TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig(); totpTwoFaProviderConfig.setIssuerName("tb"); saveProvidersConfigs(totpTwoFaProviderConfig); return totpTwoFaProviderConfig; } - private SmsTwoFactorAuthProviderConfig configureSmsTwoFaProvider(String verificationMessageTemplate) throws Exception { - SmsTwoFactorAuthProviderConfig smsTwoFaProviderConfig = new SmsTwoFactorAuthProviderConfig(); + private SmsTwoFaProviderConfig configureSmsTwoFaProvider(String verificationMessageTemplate) throws Exception { + SmsTwoFaProviderConfig smsTwoFaProviderConfig = new SmsTwoFaProviderConfig(); smsTwoFaProviderConfig.setSmsVerificationMessageTemplate(verificationMessageTemplate); smsTwoFaProviderConfig.setVerificationCodeLifetime(60); @@ -488,8 +491,8 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { return smsTwoFaProviderConfig; } - private void saveProvidersConfigs(TwoFactorAuthProviderConfig... providerConfigs) throws Exception { - TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings(); + private void saveProvidersConfigs(TwoFaProviderConfig... providerConfigs) throws Exception { + PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); twoFaSettings.setProviders(Arrays.stream(providerConfigs).collect(Collectors.toList())); doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk()); @@ -498,24 +501,24 @@ public abstract class TwoFactorAuthConfigTest extends AbstractControllerTest { @Test public void testIsTwoFaEnabled() throws Exception { configureSmsTwoFaProvider("${verificationCode}"); - SmsTwoFactorAuthAccountConfig accountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig accountConfig = new SmsTwoFaAccountConfig(); accountConfig.setPhoneNumber("+38050505050"); - twoFactorAuthConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUserId, accountConfig); + twoFaConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUserId, accountConfig); - assertThat(twoFactorAuthConfigManager.isTwoFaEnabled(tenantId, tenantAdminUserId)).isTrue(); + assertThat(twoFactorAuthService.isTwoFaEnabled(tenantId, tenantAdminUserId)).isTrue(); } @Test public void testDeleteTwoFaAccountConfig() throws Exception { configureSmsTwoFaProvider("${verificationCode}"); - SmsTwoFactorAuthAccountConfig accountConfig = new SmsTwoFactorAuthAccountConfig(); + SmsTwoFaAccountConfig accountConfig = new SmsTwoFaAccountConfig(); accountConfig.setPhoneNumber("+38050505050"); loginTenantAdmin(); - twoFactorAuthConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUserId, accountConfig); + twoFaConfigManager.saveTwoFaAccountConfig(tenantId, tenantAdminUserId, accountConfig); - TwoFactorAuthAccountConfig savedAccountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFactorAuthAccountConfig.class); + TwoFaAccountConfig savedAccountConfig = readResponse(doGet("/api/2fa/account/config").andExpect(status().isOk()), TwoFaAccountConfig.class); assertThat(savedAccountConfig).isEqualTo(accountConfig); doDelete("/api/2fa/account/config").andExpect(status().isOk()); diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java index 41ddd05c1b..17910c4f16 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java @@ -39,14 +39,14 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; -import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager; -import org.thingsboard.server.common.data.security.model.mfa.TwoFactorAuthSettings; -import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFactorAuthAccountConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; +import org.thingsboard.server.common.data.security.model.mfa.PlatformTwoFaSettings; +import org.thingsboard.server.common.data.security.model.mfa.account.SmsTwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.account.TotpTwoFaAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.SmsTwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TotpTwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.model.JwtTokenPair; @@ -68,319 +68,319 @@ import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class TwoFactorAuthTest extends AbstractControllerTest { - - @Autowired - private TwoFactorAuthConfigManager twoFactorAuthConfigManager; - @Autowired - private TwoFactorAuthService twoFactorAuthService; - @MockBean - private SmsService smsService; - @Autowired - private AuditLogService auditLogService; - @Autowired - private UserService userService; - - private User user; - private String username; - private String password; - - @Before - public void beforeEach() throws Exception { - username = "mfa@tb.io"; - password = "psswrd"; - - user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setEmail(username); - user.setTenantId(tenantId); - - loginSysAdmin(); - user = createUser(user, password); - } - - @After - public void afterEach() { - twoFactorAuthConfigManager.deleteTwoFaSettings(tenantId); - twoFactorAuthConfigManager.deleteTwoFaSettings(TenantId.SYS_TENANT_ID); - } - - @Test - public void testTwoFa_totp() throws Exception { - TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(); - - logInWithPreVerificationToken(); - - doPost("/api/auth/2fa/verification/send") - .andExpect(status().isOk()); - - String correctVerificationCode = getCorrectTotp(totpTwoFaAccountConfig); - - JsonNode tokenPair = readResponse(doPost("/api/auth/2fa/verification/check?verificationCode=" + correctVerificationCode) - .andExpect(status().isOk()), JsonNode.class); - validateAndSetJwtToken(tokenPair, username); - - User currentUser = readResponse(doGet("/api/auth/user") - .andExpect(status().isOk()), User.class); - assertThat(currentUser.getId()).isEqualTo(user.getId()); - } - - @Test - public void testTwoFa_sms() throws Exception { - configureSmsTwoFa(); - - logInWithPreVerificationToken(); - - doPost("/api/auth/2fa/verification/send") - .andExpect(status().isOk()); - - ArgumentCaptor verificationCodeCaptor = ArgumentCaptor.forClass(String.class); - verify(smsService).sendSms(eq(tenantId), any(), any(), verificationCodeCaptor.capture()); - String correctVerificationCode = verificationCodeCaptor.getValue(); - - JsonNode tokenPair = readResponse(doPost("/api/auth/2fa/verification/check?verificationCode=" + correctVerificationCode) - .andExpect(status().isOk()), JsonNode.class); - validateAndSetJwtToken(tokenPair, username); - - User currentUser = readResponse(doGet("/api/auth/user") - .andExpect(status().isOk()), User.class); - assertThat(currentUser.getId()).isEqualTo(user.getId()); - } - - @Test - public void testTwoFaPreVerificationTokenLifetime() throws Exception { - configureTotpTwoFa(twoFaSettings -> { - twoFaSettings.setTotalAllowedTimeForVerification(5); - }); - - logInWithPreVerificationToken(); - - await("expiration of the pre-verification token") - .atLeast(Duration.ofSeconds(3).plusMillis(500)) - .atMost(Duration.ofSeconds(6)) - .untilAsserted(() -> { - doPost("/api/auth/2fa/verification/send") - .andExpect(status().isUnauthorized()); - }); - } - - @Test - public void testCheckVerificationCode_userBlocked() throws Exception { - configureTotpTwoFa(twoFaSettings -> { - twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10); - }); - - logInWithPreVerificationToken(); - - Stream.generate(() -> RandomStringUtils.randomNumeric(6)) - .limit(9) - .forEach(incorrectVerificationCode -> { - try { - String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + incorrectVerificationCode) - .andExpect(status().isBadRequest())); - assertThat(errorMessage).containsIgnoringCase("verification code is incorrect"); - } catch (Exception e) { - fail(); - } - }); - - String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils.randomNumeric(6)) - .andExpect(status().isUnauthorized())); - assertThat(errorMessage).containsIgnoringCase("account was locked due to exceeded 2fa verification attempts"); - - errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils.randomNumeric(6)) - .andExpect(status().isUnauthorized())); - assertThat(errorMessage).containsIgnoringCase("user is disabled"); - } - - @Test - public void testSendVerificationCode_rateLimit() throws Exception { - configureTotpTwoFa(twoFaSettings -> { - twoFaSettings.setVerificationCodeSendRateLimit("3:10"); - }); - - logInWithPreVerificationToken(); - - for (int i = 0; i < 3; i++) { - doPost("/api/auth/2fa/verification/send") - .andExpect(status().isOk()); - } - - String rateLimitExceededError = getErrorMessage(doPost("/api/auth/2fa/verification/send") - .andExpect(status().isTooManyRequests())); - assertThat(rateLimitExceededError).containsIgnoringCase("too many verification code sending requests"); - - await("verification code sending rate limit resetting") - .atLeast(Duration.ofSeconds(8)) - .atMost(Duration.ofSeconds(12)) - .untilAsserted(() -> { - doPost("/api/auth/2fa/verification/send") - .andExpect(status().isOk()); - }); - } - - @Test - public void testCheckVerificationCode_rateLimit() throws Exception { - TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(twoFaSettings -> { - twoFaSettings.setVerificationCodeCheckRateLimit("3:10"); - }); - - logInWithPreVerificationToken(); - - for (int i = 0; i < 3; i++) { - String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") - .andExpect(status().isBadRequest())); - assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect"); - } - - String rateLimitExceededError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") - .andExpect(status().isTooManyRequests())); - assertThat(rateLimitExceededError).containsIgnoringCase("too many verification code checking requests"); - - await("verification code checking rate limit resetting") - .atLeast(Duration.ofSeconds(8)) - .atMost(Duration.ofSeconds(12)) - .untilAsserted(() -> { - String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") - .andExpect(status().isBadRequest())); - assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect"); - }); - - doPost("/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp(totpTwoFaAccountConfig)) - .andExpect(status().isOk()); - } - - @Test - public void testCheckVerificationCode_invalidVerificationCode() throws Exception { - configureTotpTwoFa(); - logInWithPreVerificationToken(); - - for (String invalidVerificationCode : new String[]{"1234567", "ab1212", "12311 ", "oewkriwejqf"}) { - String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + invalidVerificationCode) - .andExpect(status().isBadRequest())); - assertThat(errorMessage).containsIgnoringCase("verification code is incorrect"); - } - } - - @Test - public void testCheckVerificationCode_codeExpiration() throws Exception { - configureSmsTwoFa(smsTwoFaProviderConfig -> { - smsTwoFaProviderConfig.setVerificationCodeLifetime(10); - }); - - logInWithPreVerificationToken(); - - ArgumentCaptor verificationCodeCaptor = ArgumentCaptor.forClass(String.class); - doPost("/api/auth/2fa/verification/send").andExpect(status().isOk()); - verify(smsService).sendSms(eq(tenantId), any(), any(), verificationCodeCaptor.capture()); - - String correctVerificationCode = verificationCodeCaptor.getValue(); - - await("verification code expiration") - .pollDelay(10, TimeUnit.SECONDS) - .atLeast(10, TimeUnit.SECONDS) - .atMost(12, TimeUnit.SECONDS) - .untilAsserted(() -> { - String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + correctVerificationCode) - .andExpect(status().isBadRequest())); - assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect"); - }); - } - - @Test - public void testTwoFa_logLoginAction() throws Exception { - TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(); - - logInWithPreVerificationToken(); - await("async audit log saving").during(1, TimeUnit.SECONDS); - assertThat(getLogInAuditLogs()).isEmpty(); - assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo() - .get("lastLoginTs")).isNull(); - - doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") - .andExpect(status().isBadRequest()); - - await("async audit log saving").atMost(1, TimeUnit.SECONDS) - .until(() -> getLogInAuditLogs().size() == 1); - assertThat(getLogInAuditLogs().get(0)).satisfies(failedLogInAuditLog -> { - assertThat(failedLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.FAILURE); - assertThat(failedLogInAuditLog.getActionFailureDetails()).containsIgnoringCase("verification code is incorrect"); - assertThat(failedLogInAuditLog.getUserName()).isEqualTo(username); - }); - - doPost("/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp(totpTwoFaAccountConfig)) - .andExpect(status().isOk()); - await("async audit log saving").atMost(1, TimeUnit.SECONDS) - .until(() -> getLogInAuditLogs().size() == 2); - assertThat(getLogInAuditLogs().get(0)).satisfies(successfulLogInAuditLog -> { - assertThat(successfulLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.SUCCESS); - assertThat(successfulLogInAuditLog.getUserName()).isEqualTo(username); - }); - assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo() - .get("lastLoginTs").asLong()) - .isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(3)); - } - - private List getLogInAuditLogs() { - return auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, user.getId(), List.of(ActionType.LOGIN), - new TimePageLink(new PageLink(10, 0, null, new SortOrder("createdTime", SortOrder.Direction.DESC)), 0L, System.currentTimeMillis())).getData(); - } - - @Test - public void testAuthWithoutTwoFaAccountConfig() throws ThingsboardException { - configureTotpTwoFa(); - twoFactorAuthConfigManager.deleteTwoFaAccountConfig(tenantId, user.getId()); - - assertDoesNotThrow(() -> { - login(username, password); - }); - } - - private void logInWithPreVerificationToken() throws Exception { - LoginRequest loginRequest = new LoginRequest(username, password); - - JwtTokenPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtTokenPair.class); - assertThat(response.getToken()).isNotNull(); - assertThat(response.getRefreshToken()).isNull(); - assertThat(response.getScope()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN); - - this.token = response.getToken(); - } - - private TotpTwoFactorAuthAccountConfig configureTotpTwoFa(Consumer... customizer) throws ThingsboardException { - TotpTwoFactorAuthProviderConfig totpTwoFaProviderConfig = new TotpTwoFactorAuthProviderConfig(); - totpTwoFaProviderConfig.setIssuerName("tb"); - - TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings(); - twoFaSettings.setUseSystemTwoFactorAuthSettings(false); - twoFaSettings.setProviders(Arrays.stream(new TwoFactorAuthProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList())); - Arrays.stream(customizer).forEach(c -> c.accept(twoFaSettings)); - twoFactorAuthConfigManager.saveTwoFaSettings(tenantId, twoFaSettings); - - TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig = (TotpTwoFactorAuthAccountConfig) twoFactorAuthService.generateNewAccountConfig(user, TwoFactorAuthProviderType.TOTP); - twoFactorAuthConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), totpTwoFaAccountConfig); - return totpTwoFaAccountConfig; - } - - private SmsTwoFactorAuthAccountConfig configureSmsTwoFa(Consumer... customizer) throws ThingsboardException { - SmsTwoFactorAuthProviderConfig smsTwoFaProviderConfig = new SmsTwoFactorAuthProviderConfig(); - smsTwoFaProviderConfig.setVerificationCodeLifetime(60); - smsTwoFaProviderConfig.setSmsVerificationMessageTemplate("${verificationCode}"); - Arrays.stream(customizer).forEach(c -> c.accept(smsTwoFaProviderConfig)); - - TwoFactorAuthSettings twoFaSettings = new TwoFactorAuthSettings(); - twoFaSettings.setUseSystemTwoFactorAuthSettings(false); - twoFaSettings.setProviders(Arrays.stream(new TwoFactorAuthProviderConfig[]{smsTwoFaProviderConfig}).collect(Collectors.toList())); - twoFactorAuthConfigManager.saveTwoFaSettings(tenantId, twoFaSettings); - - SmsTwoFactorAuthAccountConfig smsTwoFaAccountConfig = new SmsTwoFactorAuthAccountConfig(); - smsTwoFaAccountConfig.setPhoneNumber("+38050505050"); - twoFactorAuthConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), smsTwoFaAccountConfig); - return smsTwoFaAccountConfig; - } - - private String getCorrectTotp(TotpTwoFactorAuthAccountConfig totpTwoFaAccountConfig) { - String secret = StringUtils.substringAfterLast(totpTwoFaAccountConfig.getAuthUrl(), "secret="); - return new Totp(secret).now(); - } +// +// @Autowired +// private TwoFaConfigManager twoFaConfigManager; +// @Autowired +// private TwoFactorAuthService twoFactorAuthService; +// @MockBean +// private SmsService smsService; +// @Autowired +// private AuditLogService auditLogService; +// @Autowired +// private UserService userService; +// +// private User user; +// private String username; +// private String password; +// +// @Before +// public void beforeEach() throws Exception { +// username = "mfa@tb.io"; +// password = "psswrd"; +// +// user = new User(); +// user.setAuthority(Authority.TENANT_ADMIN); +// user.setEmail(username); +// user.setTenantId(tenantId); +// +// loginSysAdmin(); +// user = createUser(user, password); +// } +// +// @After +// public void afterEach() { +// twoFaConfigManager.deletePlatformTwoFaSettings(tenantId); +// twoFaConfigManager.deletePlatformTwoFaSettings(TenantId.SYS_TENANT_ID); +// } +// +// @Test +// public void testTwoFa_totp() throws Exception { +// TotpTwoFaAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(); +// +// logInWithPreVerificationToken(); +// +// doPost("/api/auth/2fa/verification/send") +// .andExpect(status().isOk()); +// +// String correctVerificationCode = getCorrectTotp(totpTwoFaAccountConfig); +// +// JsonNode tokenPair = readResponse(doPost("/api/auth/2fa/verification/check?verificationCode=" + correctVerificationCode) +// .andExpect(status().isOk()), JsonNode.class); +// validateAndSetJwtToken(tokenPair, username); +// +// User currentUser = readResponse(doGet("/api/auth/user") +// .andExpect(status().isOk()), User.class); +// assertThat(currentUser.getId()).isEqualTo(user.getId()); +// } +// +// @Test +// public void testTwoFa_sms() throws Exception { +// configureSmsTwoFa(); +// +// logInWithPreVerificationToken(); +// +// doPost("/api/auth/2fa/verification/send") +// .andExpect(status().isOk()); +// +// ArgumentCaptor verificationCodeCaptor = ArgumentCaptor.forClass(String.class); +// verify(smsService).sendSms(eq(tenantId), any(), any(), verificationCodeCaptor.capture()); +// String correctVerificationCode = verificationCodeCaptor.getValue(); +// +// JsonNode tokenPair = readResponse(doPost("/api/auth/2fa/verification/check?verificationCode=" + correctVerificationCode) +// .andExpect(status().isOk()), JsonNode.class); +// validateAndSetJwtToken(tokenPair, username); +// +// User currentUser = readResponse(doGet("/api/auth/user") +// .andExpect(status().isOk()), User.class); +// assertThat(currentUser.getId()).isEqualTo(user.getId()); +// } +// +// @Test +// public void testTwoFaPreVerificationTokenLifetime() throws Exception { +// configureTotpTwoFa(twoFaSettings -> { +// twoFaSettings.setTotalAllowedTimeForVerification(5); +// }); +// +// logInWithPreVerificationToken(); +// +// await("expiration of the pre-verification token") +// .atLeast(Duration.ofSeconds(3).plusMillis(500)) +// .atMost(Duration.ofSeconds(6)) +// .untilAsserted(() -> { +// doPost("/api/auth/2fa/verification/send") +// .andExpect(status().isUnauthorized()); +// }); +// } +// +// @Test +// public void testCheckVerificationCode_userBlocked() throws Exception { +// configureTotpTwoFa(twoFaSettings -> { +// twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10); +// }); +// +// logInWithPreVerificationToken(); +// +// Stream.generate(() -> RandomStringUtils.randomNumeric(6)) +// .limit(9) +// .forEach(incorrectVerificationCode -> { +// try { +// String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + incorrectVerificationCode) +// .andExpect(status().isBadRequest())); +// assertThat(errorMessage).containsIgnoringCase("verification code is incorrect"); +// } catch (Exception e) { +// fail(); +// } +// }); +// +// String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils.randomNumeric(6)) +// .andExpect(status().isUnauthorized())); +// assertThat(errorMessage).containsIgnoringCase("account was locked due to exceeded 2fa verification attempts"); +// +// errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + RandomStringUtils.randomNumeric(6)) +// .andExpect(status().isUnauthorized())); +// assertThat(errorMessage).containsIgnoringCase("user is disabled"); +// } +// +// @Test +// public void testSendVerificationCode_rateLimit() throws Exception { +// configureTotpTwoFa(twoFaSettings -> { +// twoFaSettings.setVerificationCodeSendRateLimit("3:10"); +// }); +// +// logInWithPreVerificationToken(); +// +// for (int i = 0; i < 3; i++) { +// doPost("/api/auth/2fa/verification/send") +// .andExpect(status().isOk()); +// } +// +// String rateLimitExceededError = getErrorMessage(doPost("/api/auth/2fa/verification/send") +// .andExpect(status().isTooManyRequests())); +// assertThat(rateLimitExceededError).containsIgnoringCase("too many verification code sending requests"); +// +// await("verification code sending rate limit resetting") +// .atLeast(Duration.ofSeconds(8)) +// .atMost(Duration.ofSeconds(12)) +// .untilAsserted(() -> { +// doPost("/api/auth/2fa/verification/send") +// .andExpect(status().isOk()); +// }); +// } +// +// @Test +// public void testCheckVerificationCode_rateLimit() throws Exception { +// TotpTwoFaAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(twoFaSettings -> { +// twoFaSettings.setVerificationCodeCheckRateLimit("3:10"); +// }); +// +// logInWithPreVerificationToken(); +// +// for (int i = 0; i < 3; i++) { +// String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") +// .andExpect(status().isBadRequest())); +// assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect"); +// } +// +// String rateLimitExceededError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") +// .andExpect(status().isTooManyRequests())); +// assertThat(rateLimitExceededError).containsIgnoringCase("too many verification code checking requests"); +// +// await("verification code checking rate limit resetting") +// .atLeast(Duration.ofSeconds(8)) +// .atMost(Duration.ofSeconds(12)) +// .untilAsserted(() -> { +// String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") +// .andExpect(status().isBadRequest())); +// assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect"); +// }); +// +// doPost("/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp(totpTwoFaAccountConfig)) +// .andExpect(status().isOk()); +// } +// +// @Test +// public void testCheckVerificationCode_invalidVerificationCode() throws Exception { +// configureTotpTwoFa(); +// logInWithPreVerificationToken(); +// +// for (String invalidVerificationCode : new String[]{"1234567", "ab1212", "12311 ", "oewkriwejqf"}) { +// String errorMessage = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + invalidVerificationCode) +// .andExpect(status().isBadRequest())); +// assertThat(errorMessage).containsIgnoringCase("verification code is incorrect"); +// } +// } +// +// @Test +// public void testCheckVerificationCode_codeExpiration() throws Exception { +// configureSmsTwoFa(smsTwoFaProviderConfig -> { +// smsTwoFaProviderConfig.setVerificationCodeLifetime(10); +// }); +// +// logInWithPreVerificationToken(); +// +// ArgumentCaptor verificationCodeCaptor = ArgumentCaptor.forClass(String.class); +// doPost("/api/auth/2fa/verification/send").andExpect(status().isOk()); +// verify(smsService).sendSms(eq(tenantId), any(), any(), verificationCodeCaptor.capture()); +// +// String correctVerificationCode = verificationCodeCaptor.getValue(); +// +// await("verification code expiration") +// .pollDelay(10, TimeUnit.SECONDS) +// .atLeast(10, TimeUnit.SECONDS) +// .atMost(12, TimeUnit.SECONDS) +// .untilAsserted(() -> { +// String incorrectVerificationCodeError = getErrorMessage(doPost("/api/auth/2fa/verification/check?verificationCode=" + correctVerificationCode) +// .andExpect(status().isBadRequest())); +// assertThat(incorrectVerificationCodeError).containsIgnoringCase("verification code is incorrect"); +// }); +// } +// +// @Test +// public void testTwoFa_logLoginAction() throws Exception { +// TotpTwoFaAccountConfig totpTwoFaAccountConfig = configureTotpTwoFa(); +// +// logInWithPreVerificationToken(); +// await("async audit log saving").during(1, TimeUnit.SECONDS); +// assertThat(getLogInAuditLogs()).isEmpty(); +// assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo() +// .get("lastLoginTs")).isNull(); +// +// doPost("/api/auth/2fa/verification/check?verificationCode=incorrect") +// .andExpect(status().isBadRequest()); +// +// await("async audit log saving").atMost(1, TimeUnit.SECONDS) +// .until(() -> getLogInAuditLogs().size() == 1); +// assertThat(getLogInAuditLogs().get(0)).satisfies(failedLogInAuditLog -> { +// assertThat(failedLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.FAILURE); +// assertThat(failedLogInAuditLog.getActionFailureDetails()).containsIgnoringCase("verification code is incorrect"); +// assertThat(failedLogInAuditLog.getUserName()).isEqualTo(username); +// }); +// +// doPost("/api/auth/2fa/verification/check?verificationCode=" + getCorrectTotp(totpTwoFaAccountConfig)) +// .andExpect(status().isOk()); +// await("async audit log saving").atMost(1, TimeUnit.SECONDS) +// .until(() -> getLogInAuditLogs().size() == 2); +// assertThat(getLogInAuditLogs().get(0)).satisfies(successfulLogInAuditLog -> { +// assertThat(successfulLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.SUCCESS); +// assertThat(successfulLogInAuditLog.getUserName()).isEqualTo(username); +// }); +// assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo() +// .get("lastLoginTs").asLong()) +// .isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(3)); +// } +// +// private List getLogInAuditLogs() { +// return auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, user.getId(), List.of(ActionType.LOGIN), +// new TimePageLink(new PageLink(10, 0, null, new SortOrder("createdTime", SortOrder.Direction.DESC)), 0L, System.currentTimeMillis())).getData(); +// } +// +// @Test +// public void testAuthWithoutTwoFaAccountConfig() throws ThingsboardException { +// configureTotpTwoFa(); +// twoFaConfigManager.deleteTwoFaAccountConfig(tenantId, user.getId(), ); +// +// assertDoesNotThrow(() -> { +// login(username, password); +// }); +// } +// +// private void logInWithPreVerificationToken() throws Exception { +// LoginRequest loginRequest = new LoginRequest(username, password); +// +// JwtTokenPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtTokenPair.class); +// assertThat(response.getToken()).isNotNull(); +// assertThat(response.getRefreshToken()).isNull(); +// assertThat(response.getScope()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN); +// +// this.token = response.getToken(); +// } +// +// private TotpTwoFaAccountConfig configureTotpTwoFa(Consumer... customizer) throws ThingsboardException { +// TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig(); +// totpTwoFaProviderConfig.setIssuerName("tb"); +// +// PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); +// twoFaSettings.setUseSystemTwoFactorAuthSettings(false); +// twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList())); +// Arrays.stream(customizer).forEach(c -> c.accept(twoFaSettings)); +// twoFaConfigManager.savePlatformTwoFaSettings(tenantId, twoFaSettings); +// +// TotpTwoFaAccountConfig totpTwoFaAccountConfig = (TotpTwoFaAccountConfig) twoFactorAuthService.generateNewAccountConfig(user, TwoFaProviderType.TOTP); +// twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), totpTwoFaAccountConfig); +// return totpTwoFaAccountConfig; +// } +// +// private SmsTwoFaAccountConfig configureSmsTwoFa(Consumer... customizer) throws ThingsboardException { +// SmsTwoFaProviderConfig smsTwoFaProviderConfig = new SmsTwoFaProviderConfig(); +// smsTwoFaProviderConfig.setVerificationCodeLifetime(60); +// smsTwoFaProviderConfig.setSmsVerificationMessageTemplate("${verificationCode}"); +// Arrays.stream(customizer).forEach(c -> c.accept(smsTwoFaProviderConfig)); +// +// PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings(); +// twoFaSettings.setUseSystemTwoFactorAuthSettings(false); +// twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{smsTwoFaProviderConfig}).collect(Collectors.toList())); +// twoFaConfigManager.savePlatformTwoFaSettings(tenantId, twoFaSettings); +// +// SmsTwoFaAccountConfig smsTwoFaAccountConfig = new SmsTwoFaAccountConfig(); +// smsTwoFaAccountConfig.setPhoneNumber("+38050505050"); +// twoFaConfigManager.saveTwoFaAccountConfig(tenantId, user.getId(), smsTwoFaAccountConfig); +// return smsTwoFaAccountConfig; +// } +// +// private String getCorrectTotp(TotpTwoFaAccountConfig totpTwoFaAccountConfig) { +// String secret = StringUtils.substringAfterLast(totpTwoFaAccountConfig.getAuthUrl(), "secret="); +// return new Totp(secret).now(); +// } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserAuthSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserAuthSettings.java index 769f861435..1c4eba8a5d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserAuthSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserAuthSettings.java @@ -20,7 +20,7 @@ import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.UserAuthSettingsId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.model.mfa.account.TwoFactorAuthAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings; @Data @EqualsAndHashCode(callSuper = true) @@ -29,6 +29,6 @@ public class UserAuthSettings extends BaseData { private static final long serialVersionUID = 2628320657987010348L; private UserId userId; - private TwoFactorAuthAccountConfig twoFaAccountConfig; + private AccountTwoFaSettings twoFaSettings; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/TwoFactorAuthSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java similarity index 92% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/TwoFactorAuthSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java index 49d5b64b4b..749eb08329 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/TwoFactorAuthSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/PlatformTwoFaSettings.java @@ -18,8 +18,8 @@ package org.thingsboard.server.common.data.security.model.mfa; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderConfig; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderConfig; import javax.validation.Valid; import javax.validation.constraints.Min; @@ -29,7 +29,7 @@ import java.util.Optional; @Data @ApiModel -public class TwoFactorAuthSettings { +public class PlatformTwoFaSettings { @ApiModelProperty(value = "Option for tenant admins to use 2FA settings configured by sysadmin. " + "If this param is set to true, then the settings will not be validated for constraints " + @@ -37,7 +37,7 @@ public class TwoFactorAuthSettings { private boolean useSystemTwoFactorAuthSettings; @ApiModelProperty(value = "The list of 2FA providers' configs. Users will only be allowed to use 2FA providers from this list.") @Valid - private List providers; + private List providers; @ApiModelProperty(value = "Rate limit configuration for verification code sending. The format is standard: 'amountOfRequests:periodInSeconds'. " + "The value of '1:60' would limit verification code sending requests to one per minute.", example = "1:60", required = false) @@ -55,7 +55,7 @@ public class TwoFactorAuthSettings { private Integer totalAllowedTimeForVerification; - public Optional getProviderConfig(TwoFactorAuthProviderType providerType) { + public Optional getProviderConfig(TwoFaProviderType providerType) { return Optional.ofNullable(providers) .flatMap(providersConfigs -> providersConfigs.stream() .filter(providerConfig -> providerConfig.getProviderType() == providerType) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/AccountTwoFaSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/AccountTwoFaSettings.java new file mode 100644 index 0000000000..d621f47a71 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/AccountTwoFaSettings.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security.model.mfa.account; + +import lombok.Data; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; + +import java.util.LinkedHashMap; + +@Data +public class AccountTwoFaSettings { + private LinkedHashMap configs; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/OtpBasedTwoFactorAuthAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/OtpBasedTwoFaAccountConfig.java similarity index 82% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/OtpBasedTwoFactorAuthAccountConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/OtpBasedTwoFaAccountConfig.java index c58287556b..67e66d3886 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/OtpBasedTwoFactorAuthAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/OtpBasedTwoFaAccountConfig.java @@ -16,7 +16,9 @@ package org.thingsboard.server.common.data.security.model.mfa.account; import lombok.Data; +import lombok.EqualsAndHashCode; @Data -public abstract class OtpBasedTwoFactorAuthAccountConfig implements TwoFactorAuthAccountConfig { +@EqualsAndHashCode(callSuper = true) +public abstract class OtpBasedTwoFaAccountConfig extends TwoFaAccountConfig { } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFactorAuthAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java similarity index 86% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFactorAuthAccountConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java index 2863f3f42e..67d9d499ae 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFactorAuthAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/SmsTwoFaAccountConfig.java @@ -19,7 +19,7 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; @@ -27,7 +27,7 @@ import javax.validation.constraints.Pattern; @ApiModel @EqualsAndHashCode(callSuper = true) @Data -public class SmsTwoFactorAuthAccountConfig extends OtpBasedTwoFactorAuthAccountConfig { +public class SmsTwoFaAccountConfig extends OtpBasedTwoFaAccountConfig { @ApiModelProperty(value = "Phone number to use for 2FA. Must no be blank and must be of E.164 number format.", required = true) @NotBlank(message = "phone number cannot be blank") @@ -35,8 +35,8 @@ public class SmsTwoFactorAuthAccountConfig extends OtpBasedTwoFactorAuthAccountC private String phoneNumber; @Override - public TwoFactorAuthProviderType getProviderType() { - return TwoFactorAuthProviderType.SMS; + public TwoFaProviderType getProviderType() { + return TwoFaProviderType.SMS; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFactorAuthAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java similarity index 84% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFactorAuthAccountConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java index cc1171a713..ecafde86ce 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFactorAuthAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TotpTwoFaAccountConfig.java @@ -18,14 +18,16 @@ package org.thingsboard.server.common.data.security.model.mfa.account; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; -@ApiModel +@ApiModel // FIXME [viacheslav] @Data -public class TotpTwoFactorAuthAccountConfig implements TwoFactorAuthAccountConfig { +@EqualsAndHashCode(callSuper = true) +public class TotpTwoFaAccountConfig extends TwoFaAccountConfig { @ApiModelProperty(value = "OTP auth URL used to generate a QR code to scan with an authenticator app. Must not be blank and must follow specific pattern.", example = "otpauth://totp/ThingsBoard:tenant@thingsboard.org?issuer=ThingsBoard&secret=FUNBIM3CXFNNGQR6ZIPVWHP65PPFWDII", required = true) @@ -34,8 +36,9 @@ public class TotpTwoFactorAuthAccountConfig implements TwoFactorAuthAccountConfi private String authUrl; @Override - public TwoFactorAuthProviderType getProviderType() { - return TwoFactorAuthProviderType.TOTP; + public TwoFaProviderType getProviderType() { + return TwoFaProviderType.TOTP; } } + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TwoFactorAuthAccountConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TwoFaAccountConfig.java similarity index 79% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TwoFactorAuthAccountConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TwoFaAccountConfig.java index fc1c9bc636..5d366205cd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TwoFactorAuthAccountConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/account/TwoFaAccountConfig.java @@ -20,19 +20,23 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFactorAuthProviderType; +import lombok.Data; +import org.thingsboard.server.common.data.security.model.mfa.provider.TwoFaProviderType; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, property = "providerType") @JsonSubTypes({ - @Type(name = "TOTP", value = TotpTwoFactorAuthAccountConfig.class), - @Type(name = "SMS", value = SmsTwoFactorAuthAccountConfig.class) + @Type(name = "TOTP", value = TotpTwoFaAccountConfig.class), + @Type(name = "SMS", value = SmsTwoFaAccountConfig.class) }) -public interface TwoFactorAuthAccountConfig { +@Data +public abstract class TwoFaAccountConfig { + + private boolean useByDefault; @JsonIgnore - TwoFactorAuthProviderType getProviderType(); + public abstract TwoFaProviderType getProviderType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFactorAuthProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java similarity index 91% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFactorAuthProviderConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java index 655816fcc6..23d64c79aa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFactorAuthProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/OtpBasedTwoFaProviderConfig.java @@ -21,7 +21,7 @@ import lombok.Data; import javax.validation.constraints.Min; @Data -public abstract class OtpBasedTwoFactorAuthProviderConfig implements TwoFactorAuthProviderConfig { +public abstract class OtpBasedTwoFaProviderConfig implements TwoFaProviderConfig { @ApiModelProperty(value = "Verification code lifetime in seconds. Verification codes with a lifetime bigger than this param " + "will be considered incorrect", example = "60", required = true) @Min(value = 1, message = "verification code lifetime is required") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFactorAuthProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFactorAuthProviderConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java index 4096fe36f4..81efb7058e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFactorAuthProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/SmsTwoFaProviderConfig.java @@ -23,10 +23,10 @@ import lombok.EqualsAndHashCode; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; -@ApiModel(parent = OtpBasedTwoFactorAuthProviderConfig.class) +@ApiModel(parent = OtpBasedTwoFaProviderConfig.class) @EqualsAndHashCode(callSuper = true) @Data -public class SmsTwoFactorAuthProviderConfig extends OtpBasedTwoFactorAuthProviderConfig { +public class SmsTwoFaProviderConfig extends OtpBasedTwoFaProviderConfig { @ApiModelProperty(value = "SMS verification message template. Available template variables are ${verificationCode} and ${userEmail}. " + "It must not be blank and must contain verification code variable.", @@ -36,8 +36,8 @@ public class SmsTwoFactorAuthProviderConfig extends OtpBasedTwoFactorAuthProvide private String smsVerificationMessageTemplate; @Override - public TwoFactorAuthProviderType getProviderType() { - return TwoFactorAuthProviderType.SMS; + public TwoFaProviderType getProviderType() { + return TwoFaProviderType.SMS; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFactorAuthProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFactorAuthProviderConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java index df44a662ec..b631d367e5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFactorAuthProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TotpTwoFaProviderConfig.java @@ -23,7 +23,7 @@ import javax.validation.constraints.NotBlank; @ApiModel @Data -public class TotpTwoFactorAuthProviderConfig implements TwoFactorAuthProviderConfig { +public class TotpTwoFaProviderConfig implements TwoFaProviderConfig { @ApiModelProperty(value = "Issuer name that will be displayed in an authenticator app near a username. " + "Must not be blank.", example = "ThingsBoard", required = true) @@ -31,8 +31,8 @@ public class TotpTwoFactorAuthProviderConfig implements TwoFactorAuthProviderCon private String issuerName; @Override - public TwoFactorAuthProviderType getProviderType() { - return TwoFactorAuthProviderType.TOTP; + public TwoFaProviderType getProviderType() { + return TwoFaProviderType.TOTP; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFactorAuthProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFaProviderConfig.java similarity index 82% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFactorAuthProviderConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFaProviderConfig.java index 24458af562..69d9989af4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFactorAuthProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFaProviderConfig.java @@ -26,12 +26,12 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; use = JsonTypeInfo.Id.NAME, property = "providerType") @JsonSubTypes({ - @Type(name = "TOTP", value = TotpTwoFactorAuthProviderConfig.class), - @Type(name = "SMS", value = SmsTwoFactorAuthProviderConfig.class) + @Type(name = "TOTP", value = TotpTwoFaProviderConfig.class), + @Type(name = "SMS", value = SmsTwoFaProviderConfig.class) }) -public interface TwoFactorAuthProviderConfig { +public interface TwoFaProviderConfig { @JsonIgnore - TwoFactorAuthProviderType getProviderType(); + TwoFaProviderType getProviderType(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFactorAuthProviderType.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFaProviderType.java similarity index 94% rename from common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFactorAuthProviderType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFaProviderType.java index 04e4401395..7e6f25195e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFactorAuthProviderType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/mfa/provider/TwoFaProviderType.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.security.model.mfa.provider; -public enum TwoFactorAuthProviderType { +public enum TwoFaProviderType { TOTP, SMS } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java index f7a75381c9..90df7fb266 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java @@ -28,6 +28,7 @@ import java.time.Duration; */ public class TbRateLimits { private final LocalBucket bucket; + private final String config; public TbRateLimits(String limitsConfiguration) { this(limitsConfiguration, false); @@ -48,6 +49,7 @@ public class TbRateLimits { } else { throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration); } + this.config = limitsConfiguration; } public boolean tryConsume() { @@ -58,4 +60,8 @@ public class TbRateLimits { return bucket.tryConsume(number); } + public String getConfig() { + return config; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index d054b2b051..a5da95ca37 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -564,7 +564,7 @@ public class ModelConstants { * */ public static final String USER_AUTH_SETTINGS_COLUMN_FAMILY_NAME = "user_auth_settings"; public static final String USER_AUTH_SETTINGS_USER_ID_PROPERTY = USER_ID_PROPERTY; - public static final String USER_AUTH_SETTINGS_TWO_FA_ACCOUNT_CONFIG_PROPERTY = "mfa_account_config"; + public static final String USER_AUTH_SETTINGS_TWO_FA_SETTINGS = "two_fa_settings"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java index 59c24c3405..1728fca936 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UserAuthSettingsEntity.java @@ -25,7 +25,8 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.UserAuthSettingsId; 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.account.TwoFactorAuthAccountConfig; +import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoFaSettings; +import org.thingsboard.server.common.data.security.model.mfa.account.TwoFaAccountConfig; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -47,8 +48,8 @@ public class UserAuthSettingsEntity extends BaseSqlEntity impl @Column(name = ModelConstants.USER_AUTH_SETTINGS_USER_ID_PROPERTY, nullable = false, unique = true) private UUID userId; @Type(type = "json") - @Column(name = ModelConstants.USER_AUTH_SETTINGS_TWO_FA_ACCOUNT_CONFIG_PROPERTY) - private JsonNode twoFaAccountConfig; + @Column(name = ModelConstants.USER_AUTH_SETTINGS_TWO_FA_SETTINGS) + private JsonNode twoFaSettings; public UserAuthSettingsEntity(UserAuthSettings userAuthSettings) { if (userAuthSettings.getId() != null) { @@ -58,8 +59,8 @@ public class UserAuthSettingsEntity extends BaseSqlEntity impl if (userAuthSettings.getUserId() != null) { this.userId = userAuthSettings.getUserId().getId(); } - if (userAuthSettings.getTwoFaAccountConfig() != null) { - this.twoFaAccountConfig = JacksonUtil.valueToTree(userAuthSettings.getTwoFaAccountConfig()); + if (userAuthSettings.getTwoFaSettings() != null) { + this.twoFaSettings = JacksonUtil.valueToTree(userAuthSettings.getTwoFaSettings()); } } @@ -71,8 +72,8 @@ public class UserAuthSettingsEntity extends BaseSqlEntity impl if (userId != null) { userAuthSettings.setUserId(new UserId(userId)); } - if (twoFaAccountConfig != null) { - userAuthSettings.setTwoFaAccountConfig(JacksonUtil.treeToValue(twoFaAccountConfig, TwoFactorAuthAccountConfig.class)); + if (twoFaSettings != null) { + userAuthSettings.setTwoFaSettings(JacksonUtil.treeToValue(twoFaSettings, AccountTwoFaSettings.class)); } return userAuthSettings; } diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 8ee0854ae9..9f9348dc55 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -699,5 +699,5 @@ CREATE TABLE IF NOT EXISTS user_auth_settings ( id uuid NOT NULL CONSTRAINT user_auth_settings_pkey PRIMARY KEY, created_time bigint NOT NULL, user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id), - mfa_account_config varchar + two_fa_settings varchar );