enforce 2FA feature draft
This commit is contained in:
parent
c34574ae1a
commit
b1d19eb3cc
@ -39,6 +39,7 @@ 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.limit.LimitedApi;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.common.data.security.UserCredentials;
|
||||
import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent;
|
||||
import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent;
|
||||
@ -48,7 +49,9 @@ import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
|
||||
import org.thingsboard.server.config.annotations.ApiOperation;
|
||||
import org.thingsboard.server.dao.settings.SecuritySettingsService;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
|
||||
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
|
||||
import org.thingsboard.server.service.security.auth.rest.RestAwareAuthenticationSuccessHandler;
|
||||
import org.thingsboard.server.service.security.model.ActivateUserRequest;
|
||||
import org.thingsboard.server.service.security.model.ChangePasswordRequest;
|
||||
import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest;
|
||||
@ -74,7 +77,8 @@ public class AuthController extends BaseController {
|
||||
private final SecuritySettingsService securitySettingsService;
|
||||
private final RateLimitService rateLimitService;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final TwoFactorAuthService twoFactorAuthService;
|
||||
private final RestAwareAuthenticationSuccessHandler authenticationSuccessHandler;
|
||||
|
||||
@ApiOperation(value = "Get current User (getUser)",
|
||||
notes = "Get the information about the User which credentials are used to perform this REST API call.")
|
||||
@ -221,7 +225,13 @@ public class AuthController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
var tokenPair = tokenFactory.createTokenPair(securityUser);
|
||||
JwtPair tokenPair;
|
||||
if (twoFactorAuthService.isEnforceTwoFaEnabled(securityUser.getTenantId())) {
|
||||
tokenPair = authenticationSuccessHandler.createMfaTokenPair(securityUser, Authority.ENFORCE_MFA_TOKEN);
|
||||
} else {
|
||||
tokenPair = tokenFactory.createTokenPair(securityUser);
|
||||
}
|
||||
|
||||
systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(request), ActionType.LOGIN, null);
|
||||
return tokenPair;
|
||||
}
|
||||
|
||||
@ -56,7 +56,6 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
private final TwoFaConfigManager twoFaConfigManager;
|
||||
private final TwoFactorAuthService twoFactorAuthService;
|
||||
|
||||
|
||||
@ApiOperation(value = "Get account 2FA settings (getAccountTwoFaSettings)",
|
||||
notes = "Get user's account 2FA configuration. Configuration contains configs for different 2FA providers." + NEW_LINE +
|
||||
"Example:\n" +
|
||||
@ -73,7 +72,6 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
return twoFaConfigManager.getAccountTwoFaSettings(user.getTenantId(), user.getId()).orElse(null);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "Generate 2FA account config (generateTwoFaAccountConfig)",
|
||||
notes = "Generate new 2FA account config template for specified provider type. " + NEW_LINE +
|
||||
"For TOTP, this will return a corresponding account config template " +
|
||||
@ -99,7 +97,7 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
"Will throw an error (Bad Request) if the provider is not configured for usage. " +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
@PostMapping("/account/config/generate")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER', 'ENFORCE_MFA_TOKEN')")
|
||||
public TwoFaAccountConfig generateTwoFaAccountConfig(@Parameter(description = "2FA provider type to generate new account config for", schema = @Schema(defaultValue = "TOTP", requiredMode = Schema.RequiredMode.REQUIRED))
|
||||
@RequestParam TwoFaProviderType providerType) throws Exception {
|
||||
SecurityUser user = getCurrentUser();
|
||||
@ -139,7 +137,7 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
"Will throw an error (Bad Request) if the provider is not configured for usage. " +
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER)
|
||||
@PostMapping("/account/config")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER', 'ENFORCE_MFA_TOKEN')")
|
||||
public AccountTwoFaSettings verifyAndSaveTwoFaAccountConfig(@Valid @RequestBody TwoFaAccountConfig accountConfig,
|
||||
@RequestParam(required = false) String verificationCode) throws Exception {
|
||||
SecurityUser user = getCurrentUser();
|
||||
@ -189,7 +187,6 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
return twoFaConfigManager.deleteTwoFaAccountConfig(user.getTenantId(), user.getId(), providerType);
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "Get available 2FA providers (getAvailableTwoFaProviders)", notes =
|
||||
"Get the list of provider types available for user to use (the ones configured by tenant or sysadmin).\n" +
|
||||
"Example of response:\n" +
|
||||
@ -197,7 +194,7 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER
|
||||
)
|
||||
@GetMapping("/providers")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER', 'ENFORCE_MFA_TOKEN')")
|
||||
public List<TwoFaProviderType> getAvailableTwoFaProviders() throws ThingsboardException {
|
||||
return twoFaConfigManager.getPlatformTwoFaSettings(getTenantId(), true)
|
||||
.map(PlatformTwoFaSettings::getProviders).orElse(Collections.emptyList()).stream()
|
||||
@ -205,7 +202,6 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "Get platform 2FA settings (getPlatformTwoFaSettings)",
|
||||
notes = "Get platform settings for 2FA. The settings are described for savePlatformTwoFaSettings API method. " +
|
||||
"If 2FA is not configured, then an empty response will be returned." +
|
||||
@ -260,11 +256,10 @@ public class TwoFactorAuthConfigController extends BaseController {
|
||||
@PostMapping("/settings")
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
|
||||
public PlatformTwoFaSettings savePlatformTwoFaSettings(@Parameter(description = "Settings value", required = true)
|
||||
@RequestBody PlatformTwoFaSettings twoFaSettings) throws ThingsboardException {
|
||||
@RequestBody PlatformTwoFaSettings twoFaSettings) throws ThingsboardException {
|
||||
return twoFaConfigManager.savePlatformTwoFaSettings(getTenantId(), twoFaSettings);
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public static class TwoFaAccountConfigUpdateRequest {
|
||||
private boolean useByDefault;
|
||||
|
||||
@ -64,7 +64,6 @@ public class TwoFactorAuthController extends BaseController {
|
||||
private final SystemSecurityService systemSecurityService;
|
||||
private final UserService userService;
|
||||
|
||||
|
||||
@ApiOperation(value = "Request 2FA verification code (requestTwoFaVerificationCode)",
|
||||
notes = "Request 2FA verification code." + NEW_LINE +
|
||||
"To make a request to this endpoint, you need an access token with the scope of PRE_VERIFICATION_TOKEN, " +
|
||||
@ -91,18 +90,9 @@ public class TwoFactorAuthController extends BaseController {
|
||||
@RequestParam String verificationCode, HttpServletRequest servletRequest) throws Exception {
|
||||
SecurityUser user = getCurrentUser();
|
||||
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());
|
||||
return tokenFactory.createTokenPair(user);
|
||||
} else {
|
||||
ThingsboardException error = new ThingsboardException("Verification code is incorrect", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
|
||||
systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(servletRequest), ActionType.LOGIN, error);
|
||||
throw error;
|
||||
}
|
||||
return getRegularJwtPair(servletRequest, user, verificationSuccess, "Verification code is incorrect");
|
||||
}
|
||||
|
||||
|
||||
@ApiOperation(value = "Get available 2FA providers (getAvailableTwoFaProviders)", notes =
|
||||
"Get the list of 2FA provider infos available for user to use. Example:\n" +
|
||||
"```\n[\n" +
|
||||
@ -139,6 +129,28 @@ public class TwoFactorAuthController extends BaseController {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Get regular token pair after successfully saved two factor settings",
|
||||
notes = "Checks 2FA setting saved, and if it success the method returns a regular access and refresh token pair.")
|
||||
@PostMapping("/login")
|
||||
@PreAuthorize("hasAuthority('ENFORCE_MFA_TOKEN')")
|
||||
public JwtPair authorizeByTwoFaEnforceToken(HttpServletRequest servletRequest) throws ThingsboardException {
|
||||
SecurityUser user = getCurrentUser();
|
||||
boolean isEnabled = twoFactorAuthService.isTwoFaEnabled(user.getTenantId(), user.getId());
|
||||
return getRegularJwtPair(servletRequest, user, isEnabled, "Two factor settings is not set up!");
|
||||
}
|
||||
|
||||
private JwtPair getRegularJwtPair(HttpServletRequest servletRequest, SecurityUser user, boolean isAvailable, String errorMessage) throws ThingsboardException {
|
||||
if (isAvailable) {
|
||||
systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(servletRequest), ActionType.LOGIN, null);
|
||||
user = new SecurityUser(userService.findUserById(user.getTenantId(), user.getId()), true, user.getUserPrincipal());
|
||||
return tokenFactory.createTokenPair(user);
|
||||
} else {
|
||||
ThingsboardException error = new ThingsboardException(errorMessage, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
|
||||
systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(servletRequest), ActionType.LOGIN, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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;
|
||||
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
|
||||
public class ForceMfaAuthenticationToken extends AbstractJwtAuthenticationToken {
|
||||
public ForceMfaAuthenticationToken(SecurityUser securityUser) {
|
||||
super(securityUser);
|
||||
}
|
||||
}
|
||||
@ -67,6 +67,13 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnforceTwoFaEnabled(TenantId tenantId) {
|
||||
return configManager.getPlatformTwoFaSettings(tenantId, true)
|
||||
.map(PlatformTwoFaSettings::isEnforceTwoFa)
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkProvider(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException {
|
||||
getTwoFaProvider(providerType).check(tenantId);
|
||||
|
||||
@ -27,8 +27,9 @@ public interface TwoFactorAuthService {
|
||||
|
||||
boolean isTwoFaEnabled(TenantId tenantId, UserId userId);
|
||||
|
||||
void checkProvider(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException;
|
||||
boolean isEnforceTwoFaEnabled(TenantId tenantId);
|
||||
|
||||
void checkProvider(TenantId tenantId, TwoFaProviderType providerType) throws ThingsboardException;
|
||||
|
||||
void prepareVerificationCode(SecurityUser user, TwoFaProviderType providerType, boolean checkLimits) throws Exception;
|
||||
|
||||
|
||||
@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.model.mfa.account.AccountTwoF
|
||||
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.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.service.ConstraintValidator;
|
||||
import org.thingsboard.server.dao.settings.AdminSettingsDao;
|
||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
||||
@ -166,7 +167,9 @@ public class DefaultTwoFaConfigManager implements TwoFaConfigManager {
|
||||
for (TwoFaProviderConfig providerConfig : twoFactorAuthSettings.getProviders()) {
|
||||
twoFactorAuthService.checkProvider(tenantId, providerConfig.getProviderType());
|
||||
}
|
||||
|
||||
if (twoFactorAuthSettings.isEnforceTwoFa() && twoFactorAuthSettings.getProviders().isEmpty()) {
|
||||
throw new DataValidationException("At least one 2FA provider is required if enforce enabled!");
|
||||
}
|
||||
AdminSettings settings = Optional.ofNullable(adminSettingsService.findAdminSettingsByKey(tenantId, TWO_FACTOR_AUTH_SETTINGS_KEY))
|
||||
.orElseGet(() -> {
|
||||
AdminSettings newSettings = new AdminSettings();
|
||||
|
||||
@ -43,6 +43,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.settings.SecuritySettingsService;
|
||||
import org.thingsboard.server.dao.user.UserService;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.security.auth.ForceMfaAuthenticationToken;
|
||||
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
|
||||
import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService;
|
||||
import org.thingsboard.server.service.security.exception.UserPasswordNotValidException;
|
||||
@ -105,6 +106,8 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
|
||||
securityUser = authenticateByUsernameAndPassword(authentication, userPrincipal, username, password);
|
||||
if (twoFactorAuthService.isTwoFaEnabled(securityUser.getTenantId(), securityUser.getId())) {
|
||||
return new MfaAuthenticationToken(securityUser);
|
||||
} else if (twoFactorAuthService.isEnforceTwoFaEnabled(securityUser.getTenantId())) {
|
||||
return new ForceMfaAuthenticationToken(securityUser);
|
||||
} else {
|
||||
systemSecurityService.logLoginAction(securityUser, authentication.getDetails(), ActionType.LOGIN, null);
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.common.data.security.model.JwtPair;
|
||||
import org.thingsboard.server.service.security.auth.ForceMfaAuthenticationToken;
|
||||
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
|
||||
import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager;
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
@ -48,16 +49,13 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
|
||||
JwtPair tokenPair = new JwtPair();
|
||||
JwtPair tokenPair;
|
||||
|
||||
if (authentication instanceof MfaAuthenticationToken) {
|
||||
int preVerificationTokenLifetime = twoFaConfigManager.getPlatformTwoFaSettings(securityUser.getTenantId(), true)
|
||||
.flatMap(settings -> Optional.ofNullable(settings.getTotalAllowedTimeForVerification())
|
||||
.filter(time -> time > 0))
|
||||
.orElse((int) TimeUnit.MINUTES.toSeconds(30));
|
||||
tokenPair.setToken(tokenFactory.createPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken());
|
||||
tokenPair.setRefreshToken(null);
|
||||
tokenPair.setScope(Authority.PRE_VERIFICATION_TOKEN);
|
||||
tokenPair = createMfaTokenPair(securityUser, Authority.PRE_VERIFICATION_TOKEN);
|
||||
}
|
||||
else if (authentication instanceof ForceMfaAuthenticationToken) {
|
||||
tokenPair = createMfaTokenPair(securityUser, Authority.ENFORCE_MFA_TOKEN);
|
||||
} else {
|
||||
tokenPair = tokenFactory.createTokenPair(securityUser);
|
||||
}
|
||||
@ -69,6 +67,18 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
|
||||
clearAuthenticationAttributes(request);
|
||||
}
|
||||
|
||||
public JwtPair createMfaTokenPair(SecurityUser securityUser, Authority scope) {
|
||||
JwtPair tokenPair = new JwtPair();
|
||||
int preVerificationTokenLifetime = twoFaConfigManager.getPlatformTwoFaSettings(securityUser.getTenantId(), true)
|
||||
.flatMap(settings -> Optional.ofNullable(settings.getTotalAllowedTimeForVerification())
|
||||
.filter(time -> time > 0))
|
||||
.orElse((int) TimeUnit.MINUTES.toSeconds(30));
|
||||
tokenPair.setToken(tokenFactory.createMfaToken(securityUser, scope, preVerificationTokenLifetime).getToken());
|
||||
tokenPair.setRefreshToken(null);
|
||||
tokenPair.setScope(scope);
|
||||
return tokenPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes temporary authentication-related data which may have been stored
|
||||
* in the session during the authentication process..
|
||||
|
||||
@ -115,13 +115,16 @@ public class JwtTokenFactory {
|
||||
throw new IllegalArgumentException("JWT Token doesn't have any scopes");
|
||||
}
|
||||
|
||||
Authority authority = Authority.parse(scopes.get(0));
|
||||
|
||||
SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class))));
|
||||
securityUser.setEmail(subject);
|
||||
securityUser.setAuthority(Authority.parse(scopes.get(0)));
|
||||
securityUser.setAuthority(authority);
|
||||
String tenantId = claims.get(TENANT_ID, String.class);
|
||||
|
||||
if (tenantId != null) {
|
||||
securityUser.setTenantId(TenantId.fromUUID(UUID.fromString(tenantId)));
|
||||
} else if (securityUser.getAuthority() == Authority.SYS_ADMIN) {
|
||||
} else if (authority == Authority.SYS_ADMIN) {
|
||||
securityUser.setTenantId(TenantId.SYS_TENANT_ID);
|
||||
}
|
||||
String customerId = claims.get(CUSTOMER_ID, String.class);
|
||||
@ -133,7 +136,7 @@ public class JwtTokenFactory {
|
||||
}
|
||||
|
||||
UserPrincipal principal;
|
||||
if (securityUser.getAuthority() != Authority.PRE_VERIFICATION_TOKEN) {
|
||||
if (authority != Authority.PRE_VERIFICATION_TOKEN && authority != Authority.ENFORCE_MFA_TOKEN) {
|
||||
securityUser.setFirstName(claims.get(FIRST_NAME, String.class));
|
||||
securityUser.setLastName(claims.get(LAST_NAME, String.class));
|
||||
securityUser.setEnabled(claims.get(ENABLED, Boolean.class));
|
||||
@ -179,8 +182,8 @@ public class JwtTokenFactory {
|
||||
return securityUser;
|
||||
}
|
||||
|
||||
public JwtToken createPreVerificationToken(SecurityUser user, Integer expirationTime) {
|
||||
JwtBuilder jwtBuilder = setUpToken(user, Collections.singletonList(Authority.PRE_VERIFICATION_TOKEN.name()), expirationTime)
|
||||
public JwtToken createMfaToken(SecurityUser user, Authority scope, Integer expirationTime) {
|
||||
JwtBuilder jwtBuilder = setUpToken(user, Collections.singletonList(scope.name()), expirationTime)
|
||||
.claim(TENANT_ID, user.getTenantId().toString());
|
||||
if (user.getCustomerId() != null) {
|
||||
jwtBuilder.claim(CUSTOMER_ID, user.getCustomerId().toString());
|
||||
|
||||
@ -85,7 +85,6 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
twoFaConfigManager.deletePlatformTwoFaSettings(tenantId);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSavePlatformTwoFaSettings() throws Exception {
|
||||
loginSysAdmin();
|
||||
@ -102,6 +101,7 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
twoFaSettings.setVerificationCodeCheckRateLimit("3:900");
|
||||
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(3600);
|
||||
twoFaSettings.setEnforceTwoFa(true);
|
||||
|
||||
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isOk());
|
||||
|
||||
@ -111,6 +111,21 @@ public class TwoFactorAuthConfigTest extends AbstractControllerTest {
|
||||
assertThat(savedTwoFaSettings.getProviders()).contains(totpTwoFaProviderConfig, smsTwoFaProviderConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSavePlatformTwoFaSettingsWithEnforceTwoFaWithoutProviders() throws Exception {
|
||||
loginSysAdmin();
|
||||
|
||||
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
|
||||
twoFaSettings.setProviders(List.of());
|
||||
twoFaSettings.setMinVerificationCodeSendPeriod(5);
|
||||
twoFaSettings.setVerificationCodeCheckRateLimit("3:900");
|
||||
twoFaSettings.setMaxVerificationFailuresBeforeUserLockout(10);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(3600);
|
||||
twoFaSettings.setEnforceTwoFa(true);
|
||||
|
||||
doPost("/api/2fa/settings", twoFaSettings).andExpect(status().isBadRequest());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSavePlatformTwoFaSettings_validationError() throws Exception {
|
||||
loginSysAdmin();
|
||||
|
||||
@ -25,6 +25,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.mock.mockito.SpyBean;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.thingsboard.rule.engine.api.SmsService;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.User;
|
||||
@ -66,6 +67,10 @@ import java.util.stream.Stream;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
@ -395,6 +400,42 @@ public class TwoFactorAuthTest extends AbstractControllerTest {
|
||||
assertThat(providersInfos.get(TwoFaProviderType.EMAIL).isDefault()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnforceTwoFactorSetting() throws Exception {
|
||||
TotpTwoFaProviderConfig totpTwoFaProviderConfig = new TotpTwoFaProviderConfig();
|
||||
totpTwoFaProviderConfig.setIssuerName("tb");
|
||||
|
||||
PlatformTwoFaSettings twoFaSettings = new PlatformTwoFaSettings();
|
||||
twoFaSettings.setProviders(Arrays.stream(new TwoFaProviderConfig[]{totpTwoFaProviderConfig}).collect(Collectors.toList()));
|
||||
twoFaSettings.setMinVerificationCodeSendPeriod(5);
|
||||
twoFaSettings.setTotalAllowedTimeForVerification(100);
|
||||
twoFaSettings.setEnforceTwoFa(true);
|
||||
twoFaSettings = twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
|
||||
|
||||
JsonNode node = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class);
|
||||
assertNotNull(node.get("token").asText());
|
||||
assertNull(node.get("refreshToken"));
|
||||
assertEquals(node.get("scope").asText(), Authority.ENFORCE_MFA_TOKEN.name());
|
||||
|
||||
this.token = node.get("token").asText();
|
||||
TotpTwoFaAccountConfig totpTwoFaAccountConfig = (TotpTwoFaAccountConfig) twoFactorAuthService.generateNewAccountConfig(user, totpTwoFaProviderConfig.getProviderType());
|
||||
String secret = UriComponentsBuilder.fromUriString(totpTwoFaAccountConfig.getAuthUrl()).build()
|
||||
.getQueryParams().getFirst("secret");
|
||||
String verificationCode = new Totp(secret).now();
|
||||
readResponse(doPost("/api/2fa/account/config?verificationCode=" + verificationCode, totpTwoFaAccountConfig).andExpect(status().isOk()), JsonNode.class);
|
||||
|
||||
JwtPair tokenPair = readResponse(doPost("/api/auth/2fa/login").andExpect(status().isOk()), JwtPair.class);
|
||||
assertNotNull(tokenPair);
|
||||
|
||||
this.token = tokenPair.getToken();
|
||||
this.refreshToken = tokenPair.getRefreshToken();
|
||||
|
||||
doGet("/api/user/" + user.getId()).andExpect(status().isOk());
|
||||
|
||||
twoFaSettings.setEnforceTwoFa(false);
|
||||
twoFaConfigManager.savePlatformTwoFaSettings(TenantId.SYS_TENANT_ID, twoFaSettings);
|
||||
}
|
||||
|
||||
private void logInWithPreVerificationToken(String username, String password) throws Exception {
|
||||
LoginRequest loginRequest = new LoginRequest(username, password);
|
||||
|
||||
|
||||
@ -125,7 +125,7 @@ public class JwtTokenFactoryTest {
|
||||
public void testCreateAndParsePreVerificationJwtToken() {
|
||||
SecurityUser securityUser = createSecurityUser();
|
||||
int tokenLifetime = (int) TimeUnit.MINUTES.toSeconds(30);
|
||||
JwtToken preVerificationToken = tokenFactory.createPreVerificationToken(securityUser, tokenLifetime);
|
||||
JwtToken preVerificationToken = tokenFactory.createMfaToken(securityUser, Authority.PRE_VERIFICATION_TOKEN, tokenLifetime);
|
||||
checkExpirationTime(preVerificationToken, tokenLifetime);
|
||||
|
||||
SecurityUser parsedSecurityUser = tokenFactory.parseAccessJwtToken(preVerificationToken.getToken());
|
||||
|
||||
@ -21,7 +21,8 @@ public enum Authority {
|
||||
TENANT_ADMIN(1),
|
||||
CUSTOMER_USER(2),
|
||||
REFRESH_TOKEN(10),
|
||||
PRE_VERIFICATION_TOKEN(11);
|
||||
PRE_VERIFICATION_TOKEN(11),
|
||||
ENFORCE_MFA_TOKEN(12);
|
||||
|
||||
private int code;
|
||||
|
||||
|
||||
@ -46,6 +46,7 @@ public class PlatformTwoFaSettings {
|
||||
@Min(value = 60)
|
||||
private Integer totalAllowedTimeForVerification;
|
||||
|
||||
private boolean enforceTwoFa;
|
||||
|
||||
public Optional<TwoFaProviderConfig> getProviderConfig(TwoFaProviderType providerType) {
|
||||
return Optional.ofNullable(providers)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user