Browse Source

2FA: cleanup code

pull/6235/head
Viacheslav Klimov 4 years ago
parent
commit
062af3af81
  1. 6
      application/src/main/java/org/thingsboard/server/config/JwtSettings.java
  2. 3
      application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java
  3. 2
      application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
  6. 20
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java
  7. 8
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProviderType.java
  9. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFactorAuthProvider.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  13. 3
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  14. 4
      application/src/main/resources/thingsboard.yml
  15. 21
      common/util/src/main/java/org/thingsboard/common/util/ThrowingBiConsumer.java
  16. 21
      common/util/src/main/java/org/thingsboard/common/util/ThrowingBiFunction.java
  17. 21
      common/util/src/main/java/org/thingsboard/common/util/ThrowingTripleConsumer.java
  18. 21
      common/util/src/main/java/org/thingsboard/common/util/ThrowingTripleFunction.java
  19. 6
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java

6
application/src/main/java/org/thingsboard/server/config/JwtSettings.java

@ -44,10 +44,4 @@ public class JwtSettings {
*/
private Integer refreshTokenExpTime;
/**
* Issued when 2FA is being used.
* Valid only for 2FA verification code checking.
* */
private Integer preVerificationTokenExpirationTime;
}

3
application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java

@ -15,11 +15,8 @@
*/
package org.thingsboard.server.config;
import io.github.bucket4j.Bucket4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

2
application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java

@ -31,6 +31,7 @@ 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.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthSettings;
import org.thingsboard.server.service.security.auth.mfa.config.account.TotpTwoFactorAuthAccountConfig;
@ -45,6 +46,7 @@ import javax.validation.Valid;
@RestController
@RequestMapping("/api/2fa")
@TbCoreComponent
@RequiredArgsConstructor
public class TwoFactorAuthConfigController extends BaseController {

2
application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java

@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
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.model.JwtTokenPair;
@ -50,6 +51,7 @@ import javax.servlet.http.HttpServletRequest;
* */
@RestController
@RequestMapping("/api/auth/2fa")
@TbCoreComponent
@RequiredArgsConstructor
public class TwoFactorAuthController extends BaseController {

2
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java

@ -33,11 +33,11 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;

20
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
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.service.security.auth.mfa.config.TwoFactorAuthSettings;
import org.thingsboard.server.service.security.auth.mfa.config.account.TwoFactorAuthAccountConfig;
@ -44,6 +45,7 @@ import java.util.concurrent.ConcurrentMap;
@Service
@RequiredArgsConstructor
@TbCoreComponent
public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
private final TwoFactorAuthConfigManager configManager;
@ -61,17 +63,17 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
@Override
public void prepareVerificationCode(SecurityUser securityUser, boolean rateLimit) throws Exception {
public void prepareVerificationCode(SecurityUser securityUser, boolean checkLimits) throws Exception {
TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId())
.orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR);
prepareVerificationCode(securityUser, accountConfig, rateLimit);
prepareVerificationCode(securityUser, accountConfig, checkLimits);
}
@Override
public void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean rateLimit) throws ThingsboardException {
public void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException {
TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
if (rateLimit) {
if (checkLimits) {
if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeSendRateLimit())) {
TbRateLimits rateLimits = verificationCodeSendingRateLimits.computeIfAbsent(securityUser.getId(), sessionId -> {
return new TbRateLimits(twoFaSettings.getVerificationCodeSendRateLimit());
@ -88,21 +90,21 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
}
@Override
public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean rateLimit) throws ThingsboardException {
public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean checkLimits) throws ThingsboardException {
TwoFactorAuthAccountConfig accountConfig = configManager.getTwoFaAccountConfig(securityUser.getTenantId(), securityUser.getId())
.orElseThrow(() -> ACCOUNT_NOT_CONFIGURED_ERROR);
return checkVerificationCode(securityUser, verificationCode, accountConfig, rateLimit);
return checkVerificationCode(securityUser, verificationCode, accountConfig, checkLimits);
}
@Override
public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TwoFactorAuthAccountConfig accountConfig, boolean rateLimit) throws ThingsboardException {
public boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException {
if (!userService.findUserCredentialsByUserId(securityUser.getTenantId(), securityUser.getId()).isEnabled()) {
throw new ThingsboardException("User is disabled", ThingsboardErrorCode.AUTHENTICATION);
}
TwoFactorAuthSettings twoFaSettings = configManager.getTwoFaSettings(securityUser.getTenantId())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
if (rateLimit) {
if (checkLimits) {
if (StringUtils.isNotEmpty(twoFaSettings.getVerificationCodeCheckRateLimit())) {
TbRateLimits rateLimits = verificationCodeCheckingRateLimits.computeIfAbsent(securityUser.getId(), sessionId -> {
return new TbRateLimits(twoFaSettings.getVerificationCodeCheckRateLimit());
@ -116,7 +118,7 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService {
TwoFactorAuthProviderConfig providerConfig = twoFaSettings.getProviderConfig(accountConfig.getProviderType())
.orElseThrow(() -> PROVIDER_NOT_CONFIGURED_ERROR);
boolean verificationSuccess = getTwoFaProvider(accountConfig.getProviderType()).checkVerificationCode(securityUser, verificationCode, providerConfig, accountConfig);
if (rateLimit) {
if (checkLimits) {
systemSecurityService.validateTwoFaVerification(securityUser, verificationSuccess, twoFaSettings);
if (verificationSuccess) {
verificationCodeCheckingRateLimits.remove(securityUser.getId());

8
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/TwoFactorAuthService.java

@ -23,13 +23,13 @@ import org.thingsboard.server.service.security.model.SecurityUser;
public interface TwoFactorAuthService {
void prepareVerificationCode(SecurityUser securityUser, boolean rateLimit) throws Exception;
void prepareVerificationCode(SecurityUser securityUser, boolean checkLimits) throws Exception;
void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean rateLimit) throws ThingsboardException;
void prepareVerificationCode(SecurityUser securityUser, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException;
boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean rateLimit) throws ThingsboardException;
boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, boolean checkLimits) throws ThingsboardException;
boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TwoFactorAuthAccountConfig accountConfig, boolean rateLimit) throws ThingsboardException;
boolean checkVerificationCode(SecurityUser securityUser, String verificationCode, TwoFactorAuthAccountConfig accountConfig, boolean checkLimits) throws ThingsboardException;
TwoFactorAuthAccountConfig generateNewAccountConfig(User user, TwoFactorAuthProviderType providerType) throws ThingsboardException;

3
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/TwoFactorAuthProviderType.java

@ -17,6 +17,5 @@ package org.thingsboard.server.service.security.auth.mfa.provider;
public enum TwoFactorAuthProviderType {
TOTP,
SMS,
EMAIL
SMS
}

5
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFactorAuthProvider.java

@ -57,7 +57,7 @@ public abstract class OtpBasedTwoFactorAuthProvider<C extends OtpBasedTwoFactorA
return false;
}
if (verificationCode.equals(correctVerificationCode.getValue())
&& correctVerificationCode.getConfig().equals(accountConfig)) {
&& correctVerificationCode.getAccountConfig().equals(accountConfig)) {
verificationCodesCache.evict(securityUser.getId());
return true;
}
@ -65,12 +65,13 @@ public abstract class OtpBasedTwoFactorAuthProvider<C extends OtpBasedTwoFactorA
return false;
}
// TODO [viacheslav]: periodically clean up codes cache
@Data
private static class Otp {
private final long timestamp;
private final String value;
private final OtpBasedTwoFactorAuthAccountConfig config;
private final OtpBasedTwoFactorAuthAccountConfig accountConfig;
}
}

2
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAuthenticationProvider.java

@ -85,7 +85,7 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
if (twoFactorAuthConfigManager.isTwoFaEnabled(securityUser)) {
return new MfaAuthenticationToken(securityUser);
} else {
systemSecurityService.logLoginAction((User) authentication.getPrincipal(), authentication.getDetails(), ActionType.LOGIN, null);
systemSecurityService.logLoginAction(securityUser, authentication.getDetails(), ActionType.LOGIN, null);
}
} else {
String publicId = userPrincipal.getValue();

6
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java

@ -17,13 +17,13 @@ package org.thingsboard.server.service.security.auth.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.auth.MfaAuthenticationToken;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.auth.mfa.config.TwoFactorAuthConfigManager;
@ -41,7 +41,6 @@ import java.util.concurrent.TimeUnit;
@Component(value = "defaultAuthenticationSuccessHandler")
@RequiredArgsConstructor
@Slf4j
public class RestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final ObjectMapper mapper;
private final JwtTokenFactory tokenFactory;
@ -57,8 +56,9 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc
if (authentication instanceof MfaAuthenticationToken) {
int preVerificationTokenLifetime = (int) TimeUnit.MINUTES.toSeconds(twoFactorAuthConfigManager.getTwoFaSettings(securityUser.getTenantId())
.map(TwoFactorAuthSettings::getTotalAllowedTimeForVerification).orElse(30));
tokenPair.setToken(tokenFactory.createTwoFaPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken());
tokenPair.setToken(tokenFactory.createPreVerificationToken(securityUser, preVerificationTokenLifetime).getToken());
tokenPair.setRefreshToken(null);
tokenPair.setScope(Authority.PRE_VERIFICATION_TOKEN);
} else {
tokenPair.setToken(tokenFactory.createAccessJwtToken(securityUser).getToken());
tokenPair.setRefreshToken(refreshTokenRepository.requestRefreshToken(securityUser).getToken());

2
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -163,7 +163,7 @@ public class JwtTokenFactory {
return securityUser;
}
public JwtToken createTwoFaPreVerificationToken(SecurityUser user, Integer expirationTime) {
public JwtToken createPreVerificationToken(SecurityUser user, Integer expirationTime) {
String token = setUpToken(user, Collections.singletonList(Authority.PRE_VERIFICATION_TOKEN.name()), expirationTime)
.claim(TENANT_ID, user.getTenantId().toString())
.compact();

3
application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java

@ -164,11 +164,12 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
TenantId tenantId = securityUser.getTenantId();
UserId userId = securityUser.getId();
int failedVerificationAttempts = 0;
int failedVerificationAttempts;
if (!verificationSuccess) {
failedVerificationAttempts = userService.increaseFailedLoginAttempts(tenantId, userId);
} else {
userService.resetFailedLoginAttempts(tenantId, userId);
return;
}
if (twoFaSettings.getMaxVerificationFailuresBeforeUserLockout() > 0

4
application/src/main/resources/thingsboard.yml

@ -127,8 +127,6 @@ security:
jwt:
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}" # Number of seconds (2.5 hours)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" # Number of seconds (1 week)
# Number of seconds. Issued when 2FA is being used; valid only for checking 2FA verification code after which usual token pair is issued
preVerificationTokenExpirationTime: "${JWT_PRE_VERIFICATION_TOKEN_EXPIRATION_TIME:30}"
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
# Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator
@ -428,7 +426,7 @@ caffeine:
timeToLiveInMinutes: "${CACHE_SPECS_EDGES_TTL:1440}"
maxSize: "${CACHE_SPECS_EDGES_MAX_SIZE:10000}"
twoFaVerificationCodes:
timeToLiveInMinutes: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_TTL:1}"
timeToLiveInMinutes: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_TTL:60}"
maxSize: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_MAX_SIZE:100000}"
redis:

21
common/util/src/main/java/org/thingsboard/common/util/ThrowingBiConsumer.java

@ -1,21 +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.common.util;
@FunctionalInterface
public interface ThrowingBiConsumer<A, B> {
void accept(A a, B b) throws Exception;
}

21
common/util/src/main/java/org/thingsboard/common/util/ThrowingBiFunction.java

@ -1,21 +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.common.util;
@FunctionalInterface
public interface ThrowingBiFunction<A, B, R> {
R apply(A a, B b) throws Exception;
}

21
common/util/src/main/java/org/thingsboard/common/util/ThrowingTripleConsumer.java

@ -1,21 +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.common.util;
@FunctionalInterface
public interface ThrowingTripleConsumer<A, B, C> {
void accept(A a, B b, C c) throws Exception;
}

21
common/util/src/main/java/org/thingsboard/common/util/ThrowingTripleFunction.java

@ -1,21 +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.common.util;
@FunctionalInterface
public interface ThrowingTripleFunction<A, B, C, R> {
R apply(A a, B b, C c) throws Exception;
}

6
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java

@ -94,11 +94,7 @@ public class TbNodeUtils {
}
public static String processPattern(String pattern, TbMsgMetaData metaData) {
String result = pattern;
for (Map.Entry<String, String> keyVal : metaData.values().entrySet()) {
result = processVar(result, keyVal.getKey(), keyVal.getValue());
}
return result;
return processTemplate(pattern, metaData.values());
}
public static String processTemplate(String template, Map<String, String> data) {

Loading…
Cancel
Save