Merge remote-tracking branch 'upstream/master' into AD/imp/paste-hex-with-prefix
This commit is contained in:
commit
0d23f7b321
@ -136,6 +136,7 @@ public class ThingsboardInstallService {
|
||||
dataUpdateService.updateData("3.6.4");
|
||||
entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists();
|
||||
systemDataLoaderService.updateDefaultNotificationConfigs(false);
|
||||
systemDataLoaderService.updateJwtSettings();
|
||||
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
|
||||
break;
|
||||
default:
|
||||
|
||||
@ -19,13 +19,17 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -81,6 +85,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.common.data.security.DeviceCredentials;
|
||||
import org.thingsboard.server.common.data.security.UserCredentials;
|
||||
import org.thingsboard.server.common.data.security.model.JwtSettings;
|
||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
|
||||
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
|
||||
import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration;
|
||||
@ -100,14 +105,11 @@ import org.thingsboard.server.dao.tenant.TenantProfileService;
|
||||
import org.thingsboard.server.dao.tenant.TenantService;
|
||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
||||
import org.thingsboard.server.dao.user.UserService;
|
||||
import org.thingsboard.server.dao.widget.WidgetTypeService;
|
||||
import org.thingsboard.server.dao.widget.WidgetsBundleService;
|
||||
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeMap;
|
||||
@ -117,79 +119,51 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.thingsboard.server.common.data.DataConstants.DEFAULT_DEVICE_TYPE;
|
||||
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault;
|
||||
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.validateTokenSigningKeyLength;
|
||||
|
||||
@Service
|
||||
@Profile("install")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
|
||||
|
||||
public static final String CUSTOMER_CRED = "customer";
|
||||
public static final String ACTIVITY_STATE = "active";
|
||||
|
||||
@Autowired
|
||||
private InstallScripts installScripts;
|
||||
private final InstallScripts installScripts;
|
||||
private final UserService userService;
|
||||
private final AdminSettingsService adminSettingsService;
|
||||
private final TenantService tenantService;
|
||||
private final TenantProfileService tenantProfileService;
|
||||
private final CustomerService customerService;
|
||||
private final DeviceService deviceService;
|
||||
private final DeviceProfileService deviceProfileService;
|
||||
private final AttributesService attributesService;
|
||||
private final DeviceCredentialsService deviceCredentialsService;
|
||||
private final RuleChainService ruleChainService;
|
||||
private final TimeseriesService tsService;
|
||||
private final DeviceConnectivityConfiguration connectivityConfiguration;
|
||||
private final QueueService queueService;
|
||||
private final JwtSettingsService jwtSettingsService;
|
||||
private final NotificationSettingsService notificationSettingsService;
|
||||
private final NotificationTargetService notificationTargetService;
|
||||
|
||||
@Autowired
|
||||
private BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private AdminSettingsService adminSettingsService;
|
||||
|
||||
@Autowired
|
||||
private WidgetTypeService widgetTypeService;
|
||||
|
||||
@Autowired
|
||||
private WidgetsBundleService widgetsBundleService;
|
||||
|
||||
@Autowired
|
||||
private TenantService tenantService;
|
||||
|
||||
@Autowired
|
||||
private TenantProfileService tenantProfileService;
|
||||
|
||||
@Autowired
|
||||
private CustomerService customerService;
|
||||
|
||||
@Autowired
|
||||
private DeviceService deviceService;
|
||||
|
||||
@Autowired
|
||||
private DeviceProfileService deviceProfileService;
|
||||
|
||||
@Autowired
|
||||
private AttributesService attributesService;
|
||||
|
||||
@Autowired
|
||||
private DeviceCredentialsService deviceCredentialsService;
|
||||
|
||||
@Autowired
|
||||
private RuleChainService ruleChainService;
|
||||
|
||||
@Autowired
|
||||
private TimeseriesService tsService;
|
||||
|
||||
@Autowired
|
||||
private DeviceConnectivityConfiguration connectivityConfiguration;
|
||||
|
||||
@Value("${state.persistToTelemetry:false}")
|
||||
@Getter
|
||||
private boolean persistActivityToTelemetry;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private QueueService queueService;
|
||||
|
||||
@Autowired
|
||||
private JwtSettingsService jwtSettingsService;
|
||||
|
||||
@Autowired
|
||||
private NotificationSettingsService notificationSettingsService;
|
||||
|
||||
@Autowired
|
||||
private NotificationTargetService notificationTargetService;
|
||||
@Value("${security.jwt.tokenExpirationTime:9000}")
|
||||
private Integer tokenExpirationTime;
|
||||
@Value("${security.jwt.refreshTokenExpTime:604800}")
|
||||
private Integer refreshTokenExpTime;
|
||||
@Value("${security.jwt.tokenIssuer:thingsboard.io}")
|
||||
private String tokenIssuer;
|
||||
@Value("${security.jwt.tokenSigningKey:thingsboardDefaultSigningKey}")
|
||||
private String tokenSigningKey;
|
||||
|
||||
@Bean
|
||||
protected BCryptPasswordEncoder passwordEncoder() {
|
||||
@ -295,7 +269,42 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
|
||||
|
||||
@Override
|
||||
public void createRandomJwtSettings() throws Exception {
|
||||
jwtSettingsService.createRandomJwtSettings();
|
||||
if (jwtSettingsService.getJwtSettings() == null) {
|
||||
log.info("Creating JWT admin settings...");
|
||||
var jwtSettings = new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey);
|
||||
if (isSigningKeyDefault(jwtSettings) || !validateTokenSigningKeyLength(jwtSettings)) {
|
||||
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
|
||||
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
jwtSettingsService.saveJwtSettings(jwtSettings);
|
||||
} else {
|
||||
log.info("Skip creating JWT admin settings because they already exist.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateJwtSettings() {
|
||||
JwtSettings jwtSettings = jwtSettingsService.getJwtSettings();
|
||||
boolean invalidSignKey = false;
|
||||
String warningMessage = null;
|
||||
|
||||
if (isSigningKeyDefault(jwtSettings)) {
|
||||
warningMessage = "The platform is using the default JWT Signing Key, which is a security risk.";
|
||||
invalidSignKey = true;
|
||||
} else if (!validateTokenSigningKeyLength(jwtSettings)) {
|
||||
warningMessage = "The JWT Signing Key is shorter than 512 bits, which is a security risk.";
|
||||
invalidSignKey = true;
|
||||
}
|
||||
|
||||
if (invalidSignKey) {
|
||||
log.warn("WARNING: {}. A new JWT Signing Key has been added automatically. " +
|
||||
"You can change the JWT Signing Key using the Web UI: " +
|
||||
"Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.", warningMessage);
|
||||
|
||||
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
|
||||
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
|
||||
jwtSettingsService.saveJwtSettings(jwtSettings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -84,13 +84,17 @@ public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchema
|
||||
}
|
||||
|
||||
protected void executeQuery(String query) {
|
||||
executeQuery(query, null);
|
||||
}
|
||||
|
||||
protected void executeQuery(String query, String logQuery) {
|
||||
logQuery = logQuery != null ? logQuery : query;
|
||||
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
|
||||
conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
|
||||
log.info("Successfully executed query: {}", query);
|
||||
log.info("Successfully executed query: {}", logQuery);
|
||||
Thread.sleep(5000);
|
||||
} catch (InterruptedException | SQLException e) {
|
||||
log.error("Failed to execute query: {} due to: {}", query, e.getMessage());
|
||||
throw new RuntimeException("Failed to execute query: " + query, e);
|
||||
throw new RuntimeException("Failed to execute query: " + logQuery, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer
|
||||
@Override
|
||||
public void createCustomerTitleUniqueConstraintIfNotExists() {
|
||||
executeQuery("DO $$ BEGIN IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'customer_title_unq_key') THEN " +
|
||||
"ALTER TABLE customer ADD CONSTRAINT customer_title_unq_key UNIQUE(tenant_id, title); END IF; END; $$;");
|
||||
"ALTER TABLE customer ADD CONSTRAINT customer_title_unq_key UNIQUE(tenant_id, title); END IF; END; $$;",
|
||||
"create 'customer_title_unq_key' constraint if it doesn't already exist!");
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ public interface SystemDataLoaderService {
|
||||
|
||||
void createRandomJwtSettings() throws Exception;
|
||||
|
||||
void updateJwtSettings() throws Exception;
|
||||
|
||||
void createOAuth2Templates() throws Exception;
|
||||
|
||||
void loadSystemWidgets() throws Exception;
|
||||
|
||||
@ -175,7 +175,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
|
||||
NotificationSchedulerService notificationSchedulerService,
|
||||
NotificationRuleProcessor notificationRuleProcessor,
|
||||
TbImageService imageService) {
|
||||
super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
|
||||
super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService,
|
||||
eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
|
||||
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
|
||||
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
|
||||
this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer();
|
||||
|
||||
@ -56,6 +56,7 @@ import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
|
||||
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -48,6 +48,8 @@ import org.thingsboard.server.service.queue.TbPackProcessingContext;
|
||||
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@ -17,25 +17,24 @@ package org.thingsboard.server.service.security.auth.jwt.settings;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.rule.engine.api.NotificationCenter;
|
||||
import org.thingsboard.server.cluster.TbClusterService;
|
||||
import org.thingsboard.server.common.data.AdminSettings;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter;
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
|
||||
import org.thingsboard.server.common.data.security.model.JwtSettings;
|
||||
import org.thingsboard.server.dao.notification.DefaultNotifications;
|
||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
|
||||
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.thingsboard.server.service.security.model.token.JwtTokenFactory.KEY_LENGTH;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@ -43,49 +42,11 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
|
||||
|
||||
private final AdminSettingsService adminSettingsService;
|
||||
private final Optional<TbClusterService> tbClusterService;
|
||||
private final Optional<NotificationCenter> notificationCenter;
|
||||
private final JwtSettingsValidator jwtSettingsValidator;
|
||||
|
||||
@Value("${security.jwt.tokenExpirationTime:9000}")
|
||||
private Integer tokenExpirationTime;
|
||||
@Value("${security.jwt.refreshTokenExpTime:604800}")
|
||||
private Integer refreshTokenExpTime;
|
||||
@Value("${security.jwt.tokenIssuer:thingsboard.io}")
|
||||
private String tokenIssuer;
|
||||
@Value("${security.jwt.tokenSigningKey:thingsboardDefaultSigningKey}")
|
||||
private String tokenSigningKey;
|
||||
private final Optional<JwtTokenFactory> jwtTokenFactory;
|
||||
|
||||
private volatile JwtSettings jwtSettings = null; //lazy init
|
||||
|
||||
/**
|
||||
* Create JWT admin settings is intended to be called from Install scripts only
|
||||
*/
|
||||
@Override
|
||||
public void createRandomJwtSettings() {
|
||||
if (getJwtSettingsFromDb() == null) {
|
||||
log.info("Creating JWT admin settings...");
|
||||
this.jwtSettings = getJwtSettingsFromYml();
|
||||
if (isSigningKeyDefault(jwtSettings)) {
|
||||
this.jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
|
||||
RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
saveJwtSettings(jwtSettings);
|
||||
} else {
|
||||
log.info("Skip creating JWT admin settings because they already exist.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create JWT admin settings is intended to be called from Upgrade scripts only
|
||||
*/
|
||||
@Override
|
||||
public void saveLegacyYmlSettings() {
|
||||
log.info("Saving legacy JWT admin settings from YML...");
|
||||
if (getJwtSettingsFromDb() == null) {
|
||||
saveJwtSettings(getJwtSettingsFromYml());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtSettings saveJwtSettings(JwtSettings jwtSettings) {
|
||||
jwtSettingsValidator.validate(jwtSettings);
|
||||
@ -105,7 +66,9 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
|
||||
@Override
|
||||
public JwtSettings reloadJwtSettings() {
|
||||
log.trace("Executing reloadJwtSettings");
|
||||
return getJwtSettings(true);
|
||||
var settings = getJwtSettings(true);
|
||||
jwtTokenFactory.ifPresent(JwtTokenFactory::reload);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,30 +81,13 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
|
||||
if (this.jwtSettings == null || forceReload) {
|
||||
synchronized (this) {
|
||||
if (this.jwtSettings == null || forceReload) {
|
||||
JwtSettings result = getJwtSettingsFromDb();
|
||||
if (result == null) {
|
||||
result = getJwtSettingsFromYml();
|
||||
log.warn("Loading the JWT settings from YML since there are no settings in DB. Looks like the upgrade script was not applied.");
|
||||
}
|
||||
if (isSigningKeyDefault(result)) {
|
||||
log.warn("WARNING: The platform is configured to use default JWT Signing Key. " +
|
||||
"This is a security issue that needs to be resolved. Please change the JWT Signing Key using the Web UI. " +
|
||||
"Navigate to \"System settings -> Security settings\" while logged in as a System Administrator.");
|
||||
notificationCenter.ifPresent(notificationCenter -> {
|
||||
notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(), DefaultNotifications.jwtSigningKeyIssue.toTemplate());
|
||||
});
|
||||
}
|
||||
this.jwtSettings = result;
|
||||
jwtSettings = getJwtSettingsFromDb();
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.jwtSettings;
|
||||
}
|
||||
|
||||
private JwtSettings getJwtSettingsFromYml() {
|
||||
return new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey);
|
||||
}
|
||||
|
||||
private JwtSettings getJwtSettingsFromDb() {
|
||||
AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY);
|
||||
return adminJwtSettings != null ? mapAdminToJwtSettings(adminJwtSettings) : null;
|
||||
@ -161,8 +107,12 @@ public class DefaultJwtSettingsService implements JwtSettingsService {
|
||||
return adminJwtSettings;
|
||||
}
|
||||
|
||||
private boolean isSigningKeyDefault(JwtSettings settings) {
|
||||
public static boolean isSigningKeyDefault(JwtSettings settings) {
|
||||
return TOKEN_SIGNING_KEY_DEFAULT.equals(settings.getTokenSigningKey());
|
||||
}
|
||||
|
||||
public static boolean validateTokenSigningKeyLength(JwtSettings settings) {
|
||||
return Base64.getDecoder().decode(settings.getTokenSigningKey()).length * Byte.SIZE >= KEY_LENGTH;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,6 +27,9 @@ import java.util.Base64;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.thingsboard.server.service.security.auth.jwt.settings.DefaultJwtSettingsService.isSigningKeyDefault;
|
||||
import static org.thingsboard.server.service.security.model.token.JwtTokenFactory.KEY_LENGTH;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DefaultJwtSettingsValidator implements JwtSettingsValidator {
|
||||
@ -59,8 +62,8 @@ public class DefaultJwtSettingsValidator implements JwtSettingsValidator {
|
||||
if (Arrays.isNullOrEmpty(decodedKey)) {
|
||||
throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!");
|
||||
}
|
||||
if (decodedKey.length * Byte.SIZE < 256 && !JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT.equals(jwtSettings.getTokenSigningKey())) {
|
||||
throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 256 bits of data!");
|
||||
if (decodedKey.length * Byte.SIZE < KEY_LENGTH && !isSigningKeyDefault(jwtSettings)) {
|
||||
throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 512 bits of data!");
|
||||
}
|
||||
|
||||
System.arraycopy(decodedKey, 0, RandomUtils.nextBytes(decodedKey.length), 0, decodedKey.length); //secure memory
|
||||
|
||||
@ -26,10 +26,6 @@ public interface JwtSettingsService {
|
||||
|
||||
JwtSettings reloadJwtSettings();
|
||||
|
||||
void createRandomJwtSettings();
|
||||
|
||||
void saveLegacyYmlSettings();
|
||||
|
||||
JwtSettings saveJwtSettings(JwtSettings jwtSettings);
|
||||
|
||||
}
|
||||
|
||||
@ -16,16 +16,19 @@
|
||||
package org.thingsboard.server.service.security.model.token;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ClaimsBuilder;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -41,7 +44,10 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio
|
||||
import org.thingsboard.server.service.security.model.SecurityUser;
|
||||
import org.thingsboard.server.service.security.model.UserPrincipal;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@ -53,6 +59,8 @@ import java.util.stream.Collectors;
|
||||
@Slf4j
|
||||
public class JwtTokenFactory {
|
||||
|
||||
public static int KEY_LENGTH = Jwts.SIG.HS512.getKeyBitLength();
|
||||
|
||||
private static final String SCOPES = "scopes";
|
||||
private static final String USER_ID = "userId";
|
||||
private static final String FIRST_NAME = "firstName";
|
||||
@ -63,8 +71,12 @@ public class JwtTokenFactory {
|
||||
private static final String CUSTOMER_ID = "customerId";
|
||||
private static final String SESSION_ID = "sessionId";
|
||||
|
||||
@Lazy
|
||||
private final JwtSettingsService jwtSettingsService;
|
||||
|
||||
private volatile JwtParser jwtParser;
|
||||
private volatile SecretKey secretKey;
|
||||
|
||||
/**
|
||||
* Factory method for issuing new JWT Tokens.
|
||||
*/
|
||||
@ -95,7 +107,7 @@ public class JwtTokenFactory {
|
||||
|
||||
public SecurityUser parseAccessJwtToken(String token) {
|
||||
Jws<Claims> jwsClaims = parseTokenClaims(token);
|
||||
Claims claims = jwsClaims.getBody();
|
||||
Claims claims = jwsClaims.getPayload();
|
||||
String subject = claims.getSubject();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> scopes = claims.get(SCOPES, List.class);
|
||||
@ -140,14 +152,14 @@ public class JwtTokenFactory {
|
||||
|
||||
String token = setUpToken(securityUser, Collections.singletonList(Authority.REFRESH_TOKEN.name()), jwtSettingsService.getJwtSettings().getRefreshTokenExpTime())
|
||||
.claim(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID)
|
||||
.setId(UUID.randomUUID().toString()).compact();
|
||||
.id(UUID.randomUUID().toString()).compact();
|
||||
|
||||
return new AccessJwtToken(token);
|
||||
}
|
||||
|
||||
public SecurityUser parseRefreshToken(String token) {
|
||||
Jws<Claims> jwsClaims = parseTokenClaims(token);
|
||||
Claims claims = jwsClaims.getBody();
|
||||
Claims claims = jwsClaims.getPayload();
|
||||
String subject = claims.getSubject();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> scopes = claims.get(SCOPES, List.class);
|
||||
@ -176,6 +188,11 @@ public class JwtTokenFactory {
|
||||
return new AccessJwtToken(jwtBuilder.compact());
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
getSecretKey(true);
|
||||
getJwtParser(true);
|
||||
}
|
||||
|
||||
private JwtBuilder setUpToken(SecurityUser securityUser, List<String> scopes, long expirationTime) {
|
||||
if (StringUtils.isBlank(securityUser.getEmail())) {
|
||||
throw new IllegalArgumentException("Cannot create JWT Token without username/email");
|
||||
@ -183,28 +200,27 @@ public class JwtTokenFactory {
|
||||
|
||||
UserPrincipal principal = securityUser.getUserPrincipal();
|
||||
|
||||
Claims claims = Jwts.claims().setSubject(principal.getValue());
|
||||
claims.put(USER_ID, securityUser.getId().getId().toString());
|
||||
claims.put(SCOPES, scopes);
|
||||
ClaimsBuilder claimsBuilder = Jwts.claims()
|
||||
.subject(principal.getValue())
|
||||
.add(USER_ID, securityUser.getId().getId().toString())
|
||||
.add(SCOPES, scopes);
|
||||
if (securityUser.getSessionId() != null) {
|
||||
claims.put(SESSION_ID, securityUser.getSessionId());
|
||||
claimsBuilder.add(SESSION_ID, securityUser.getSessionId());
|
||||
}
|
||||
|
||||
ZonedDateTime currentTime = ZonedDateTime.now();
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setIssuer(jwtSettingsService.getJwtSettings().getTokenIssuer())
|
||||
.setIssuedAt(Date.from(currentTime.toInstant()))
|
||||
.setExpiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant()))
|
||||
.signWith(SignatureAlgorithm.HS512, jwtSettingsService.getJwtSettings().getTokenSigningKey());
|
||||
.claims(claimsBuilder.build())
|
||||
.issuer(jwtSettingsService.getJwtSettings().getTokenIssuer())
|
||||
.issuedAt(Date.from(currentTime.toInstant()))
|
||||
.expiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant()))
|
||||
.signWith(getSecretKey(false), Jwts.SIG.HS512);
|
||||
}
|
||||
|
||||
public Jws<Claims> parseTokenClaims(String token) {
|
||||
try {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(jwtSettingsService.getJwtSettings().getTokenSigningKey())
|
||||
.parseClaimsJws(token);
|
||||
return getJwtParser(false).parseSignedClaims(token);
|
||||
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) {
|
||||
log.debug("Invalid JWT Token", ex);
|
||||
throw new BadCredentialsException("Invalid JWT token: ", ex);
|
||||
@ -220,4 +236,28 @@ public class JwtTokenFactory {
|
||||
return new JwtPair(accessToken.getToken(), refreshToken.getToken());
|
||||
}
|
||||
|
||||
private SecretKey getSecretKey(boolean forceReload) {
|
||||
if (secretKey == null || forceReload) {
|
||||
synchronized (this) {
|
||||
if (secretKey == null || forceReload) {
|
||||
byte[] decodedToken = Base64.getDecoder().decode(jwtSettingsService.getJwtSettings().getTokenSigningKey());
|
||||
secretKey = new SecretKeySpec(decodedToken, "HmacSHA512");
|
||||
}
|
||||
}
|
||||
}
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
private JwtParser getJwtParser(boolean forceReload) {
|
||||
if (jwtParser == null || forceReload) {
|
||||
synchronized (this) {
|
||||
if (jwtParser == null || forceReload) {
|
||||
jwtParser = Jwts.parser()
|
||||
.verifyWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(jwtSettingsService.getJwtSettings().getTokenSigningKey())))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return jwtParser;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,10 +22,12 @@ import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -40,14 +42,14 @@ public class OAuth2AppTokenFactory {
|
||||
public String validateTokenAndGetCallbackUrlScheme(String appPackage, String appToken, String appSecret) {
|
||||
Jws<Claims> jwsClaims;
|
||||
try {
|
||||
jwsClaims = Jwts.parser().setSigningKey(appSecret).parseClaimsJws(appToken);
|
||||
jwsClaims = Jwts.parser().verifyWith(Keys.hmacShaKeyFor(Base64.getDecoder().decode(appSecret))).build().parseSignedClaims(appToken);
|
||||
}
|
||||
catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
|
||||
throw new IllegalArgumentException("Invalid Application token: ", ex);
|
||||
} catch (ExpiredJwtException expiredEx) {
|
||||
throw new IllegalArgumentException("Application token expired", expiredEx);
|
||||
}
|
||||
Claims claims = jwsClaims.getBody();
|
||||
Claims claims = jwsClaims.getPayload();
|
||||
Date expiration = claims.getExpiration();
|
||||
if (expiration == null) {
|
||||
throw new IllegalArgumentException("Application token must have expiration date");
|
||||
|
||||
@ -21,9 +21,7 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Header;
|
||||
import io.jsonwebtoken.Jwt;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.hamcrest.Matcher;
|
||||
@ -121,6 +119,7 @@ import org.thingsboard.server.queue.memory.InMemoryStorage;
|
||||
import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService;
|
||||
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
|
||||
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
|
||||
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@ -241,6 +240,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
||||
@Autowired
|
||||
protected ClaimDevicesService claimDevicesService;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenFactory jwtTokenFactory;
|
||||
|
||||
@SpyBean
|
||||
protected MailService mailService;
|
||||
|
||||
@ -561,13 +563,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
|
||||
}
|
||||
|
||||
protected void validateJwtToken(String token, String username) {
|
||||
Assert.assertNotNull(token);
|
||||
Assert.assertFalse(token.isEmpty());
|
||||
int i = token.lastIndexOf('.');
|
||||
Assert.assertTrue(i > 0);
|
||||
String withoutSignature = token.substring(0, i + 1);
|
||||
Jwt<Header, Claims> jwsClaims = Jwts.parser().parseClaimsJwt(withoutSignature);
|
||||
Claims claims = jwsClaims.getBody();
|
||||
Jws<Claims> jwsClaims = jwtTokenFactory.parseTokenClaims(token);
|
||||
Claims claims = jwsClaims.getPayload();
|
||||
String subject = claims.getSubject();
|
||||
Assert.assertEquals(username, subject);
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
@Slf4j
|
||||
@DaoSqlTest
|
||||
public class AdminControllerTest extends AbstractControllerTest {
|
||||
final JwtSettings defaultJwtSettings = new JwtSettings(9000, 604800, "thingsboard.io", "thingsboardDefaultSigningKey");
|
||||
final JwtSettings defaultJwtSettings = new JwtSettings(9000, 604800, "thingsboard.io", "QmlicmJkZk9tSzZPVFozcWY0Sm94UVhybmtBWXZ5YmZMOUZSZzZvcUFiOVhsb3VHUThhUWJGaXp3UHhtcGZ6Tw==");
|
||||
|
||||
@Test
|
||||
public void testFindAdminSettingsByKey() throws Exception {
|
||||
@ -168,7 +168,7 @@ public class AdminControllerTest extends AbstractControllerTest {
|
||||
assertThat(jwtSettings).isEqualTo(defaultJwtSettings);
|
||||
|
||||
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(
|
||||
RandomStringUtils.randomAlphanumeric(256 / Byte.SIZE).getBytes(StandardCharsets.UTF_8)));
|
||||
RandomStringUtils.randomAlphanumeric(512 / Byte.SIZE).getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk());
|
||||
|
||||
|
||||
@ -640,7 +640,7 @@ public class TbRuleEngineQueueConsumerManagerTest {
|
||||
}
|
||||
|
||||
private void verifyMsgProcessed(TbMsg tbMsg) {
|
||||
await().atMost(2, TimeUnit.SECONDS).untilAsserted(() -> {
|
||||
await().atMost(15, TimeUnit.SECONDS).untilAsserted(() -> {
|
||||
verify(actorContext, atLeastOnce()).tell(argThat(msg -> {
|
||||
return ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId());
|
||||
}));
|
||||
|
||||
@ -16,16 +16,14 @@
|
||||
package org.thingsboard.server.service.security.auth;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.rule.engine.api.NotificationCenter;
|
||||
import org.thingsboard.server.common.data.AdminSettings;
|
||||
import org.thingsboard.server.common.data.id.CustomerId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.id.UserId;
|
||||
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod;
|
||||
import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter;
|
||||
import org.thingsboard.server.common.data.security.Authority;
|
||||
import org.thingsboard.server.common.data.security.model.JwtSettings;
|
||||
import org.thingsboard.server.common.data.security.model.JwtToken;
|
||||
@ -38,6 +36,8 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
|
||||
import org.thingsboard.server.service.security.model.token.AccessJwtToken;
|
||||
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
@ -45,19 +45,13 @@ import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JwtTokenFactoryTest {
|
||||
|
||||
private JwtTokenFactory tokenFactory;
|
||||
private AdminSettingsService adminSettingsService;
|
||||
private NotificationCenter notificationCenter;
|
||||
private JwtSettingsService jwtSettingsService;
|
||||
|
||||
private JwtSettings jwtSettings;
|
||||
@ -66,12 +60,11 @@ public class JwtTokenFactoryTest {
|
||||
public void beforeEach() {
|
||||
jwtSettings = new JwtSettings();
|
||||
jwtSettings.setTokenIssuer("tb");
|
||||
jwtSettings.setTokenSigningKey("abewafaf");
|
||||
jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString(RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8)));
|
||||
jwtSettings.setTokenExpirationTime((int) TimeUnit.HOURS.toSeconds(2));
|
||||
jwtSettings.setRefreshTokenExpTime((int) TimeUnit.DAYS.toSeconds(7));
|
||||
|
||||
adminSettingsService = mock(AdminSettingsService.class);
|
||||
notificationCenter = mock(NotificationCenter.class);
|
||||
jwtSettingsService = mockJwtSettingsService();
|
||||
mockJwtSettings(jwtSettings);
|
||||
|
||||
@ -169,21 +162,6 @@ public class JwtTokenFactoryTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJwtSigningKeyIssueNotification() {
|
||||
JwtSettings badJwtSettings = jwtSettings;
|
||||
badJwtSettings.setTokenSigningKey(JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT);
|
||||
mockJwtSettings(badJwtSettings);
|
||||
jwtSettingsService = mockJwtSettingsService();
|
||||
|
||||
for (int i = 0; i < 5; i++) { // to check if notification is not sent twice
|
||||
jwtSettingsService.getJwtSettings();
|
||||
}
|
||||
verify(notificationCenter, times(1)).sendGeneralWebNotification(eq(TenantId.SYS_TENANT_ID),
|
||||
isA(SystemAdministratorsFilter.class), argThat(template -> template.getConfiguration().getDeliveryMethodsTemplates().get(NotificationDeliveryMethod.WEB)
|
||||
.getBody().contains("The platform is configured to use default JWT Signing Key")));
|
||||
}
|
||||
|
||||
private void mockJwtSettings(JwtSettings settings) {
|
||||
AdminSettings adminJwtSettings = new AdminSettings();
|
||||
adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(settings));
|
||||
@ -192,12 +170,11 @@ public class JwtTokenFactoryTest {
|
||||
}
|
||||
|
||||
private DefaultJwtSettingsService mockJwtSettingsService() {
|
||||
return new DefaultJwtSettingsService(adminSettingsService, Optional.empty(),
|
||||
Optional.of(notificationCenter), new DefaultJwtSettingsValidator());
|
||||
return new DefaultJwtSettingsService(adminSettingsService, Optional.empty(), new DefaultJwtSettingsValidator(), Optional.empty());
|
||||
}
|
||||
|
||||
private void checkExpirationTime(JwtToken jwtToken, int tokenLifetime) {
|
||||
Claims claims = tokenFactory.parseTokenClaims(jwtToken.getToken()).getBody();
|
||||
Claims claims = tokenFactory.parseTokenClaims(jwtToken.getToken()).getPayload();
|
||||
assertThat(claims.getExpiration()).matches(actualExpirationTime -> {
|
||||
Calendar expirationTime = Calendar.getInstance();
|
||||
expirationTime.setTime(new Date());
|
||||
|
||||
@ -63,12 +63,21 @@ public class DefaultRateLimitService implements RateLimitService {
|
||||
|
||||
@Override
|
||||
public boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level) {
|
||||
return checkRateLimit(api, tenantId, level, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level, boolean ignoreTenantNotFound) {
|
||||
if (tenantId.isSysTenantId()) {
|
||||
return true;
|
||||
}
|
||||
TenantProfile tenantProfile = tenantProfileProvider.get(tenantId);
|
||||
if (tenantProfile == null) {
|
||||
throw new TenantProfileNotFoundException(tenantId);
|
||||
if (ignoreTenantNotFound) {
|
||||
return true;
|
||||
} else {
|
||||
throw new TenantProfileNotFoundException(tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
String rateLimitConfig = tenantProfile.getProfileConfiguration()
|
||||
|
||||
@ -24,6 +24,8 @@ public interface RateLimitService {
|
||||
|
||||
boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level);
|
||||
|
||||
boolean checkRateLimit(LimitedApi api, TenantId tenantId, Object level, boolean ignoreTenantNotFound);
|
||||
|
||||
boolean checkRateLimit(LimitedApi api, Object level, String rateLimitConfig);
|
||||
|
||||
void cleanUp(LimitedApi api, Object level);
|
||||
|
||||
@ -48,7 +48,7 @@ public class JwtSettings {
|
||||
* Key is used to sign {@link JwtToken}.
|
||||
* Base64 encoded
|
||||
*/
|
||||
@Schema(description = "The JWT key is used to sing token. Base64 encoded.", example = "cTU4WnNqemI2aU5wbWVjdm1vYXRzanhjNHRUcXliMjE=")
|
||||
@Schema(description = "The JWT key is used to sing token. Base64 encoded.", example = "dkVTUzU2M2VMWUNwVVltTUhQU2o5SUM0Tkc3M0k2Ykdwcm85QTl6R0RaQ252OFlmVDk2OEptZXBNcndGeExFZg==")
|
||||
private String tokenSigningKey;
|
||||
|
||||
}
|
||||
|
||||
@ -372,15 +372,6 @@ public class DefaultNotifications {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
public static final DefaultNotification jwtSigningKeyIssue = DefaultNotification.builder()
|
||||
.name("JWT Signing Key issue notification")
|
||||
.type(NotificationType.GENERAL)
|
||||
.subject("WARNING: security issue")
|
||||
.text("The platform is configured to use default JWT Signing Key. Please change it on the security settings page")
|
||||
.icon("warning").color(YELLOW_COLOR)
|
||||
.button("Go to settings").link("/security-settings/general")
|
||||
.build();
|
||||
|
||||
private final NotificationTemplateService templateService;
|
||||
private final NotificationRuleService ruleService;
|
||||
|
||||
|
||||
@ -27,20 +27,20 @@ import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.common.util.ThingsBoardExecutors;
|
||||
import org.thingsboard.common.util.ThingsBoardThreadFactory;
|
||||
import org.thingsboard.server.cache.limits.RateLimitService;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.limit.LimitedApi;
|
||||
import org.thingsboard.server.common.stats.DefaultCounter;
|
||||
import org.thingsboard.server.common.stats.StatsCounter;
|
||||
import org.thingsboard.server.common.stats.StatsFactory;
|
||||
import org.thingsboard.server.common.stats.StatsType;
|
||||
import org.thingsboard.server.dao.entity.EntityService;
|
||||
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
|
||||
import org.thingsboard.server.common.data.limit.LimitedApi;
|
||||
import org.thingsboard.server.cache.limits.RateLimitService;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
@ -114,7 +114,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
|
||||
boolean perTenantLimitReached = false;
|
||||
TenantId tenantId = task.getTenantId();
|
||||
if (tenantId != null && !tenantId.isSysTenantId()) {
|
||||
if (!rateLimitService.checkRateLimit(LimitedApi.CASSANDRA_QUERIES, tenantId)) {
|
||||
if (!rateLimitService.checkRateLimit(LimitedApi.CASSANDRA_QUERIES, tenantId, tenantId, true)) {
|
||||
stats.incrementRateLimitedTenant(tenantId);
|
||||
stats.getTotalRateLimited().increment();
|
||||
settableFuture.setException(new TenantRateLimitException());
|
||||
|
||||
@ -53,6 +53,14 @@ VALUES ( '23199d80-6e7e-11ee-8829-ef9fd52a6141', 1697719852888, '13814000-1dd2-1
|
||||
"coaps":{"enabled":false,"host":"","port":"5684"}
|
||||
}' );
|
||||
|
||||
INSERT INTO admin_settings ( id, created_time, tenant_id, key, json_value )
|
||||
VALUES ( '1e33c6f0-061e-11ef-b5b7-dba0ee077a1b', 1714391189727, '13814000-1dd2-11b2-8080-808080808080', 'jwt', '{
|
||||
"tokenExpirationTime": "9000",
|
||||
"refreshTokenExpTime": "604800",
|
||||
"tokenIssuer": "thingsboard.io",
|
||||
"tokenSigningKey": "QmlicmJkZk9tSzZPVFozcWY0Sm94UVhybmtBWXZ5YmZMOUZSZzZvcUFiOVhsb3VHUThhUWJGaXp3UHhtcGZ6Tw=="
|
||||
}' );
|
||||
|
||||
INSERT INTO queue ( id, created_time, tenant_id, name, topic, poll_interval, partitions, consumer_per_partition, pack_processing_timeout, submit_strategy, processing_strategy )
|
||||
VALUES ( '6eaaefa6-4612-11e7-a919-92ebcb67fe33', 1592576748000 ,'13814000-1dd2-11b2-8080-808080808080', 'Main' ,'tb_rule_engine.main', 25, 10, true, 2000,
|
||||
'{"type": "BURST", "batchSize": 1000}',
|
||||
|
||||
2
pom.xml
2
pom.xml
@ -49,7 +49,7 @@
|
||||
<spring-redis.version>6.2.4</spring-redis.version>
|
||||
<spring-security.version>6.2.4</spring-security.version>
|
||||
<jedis.version>5.1.2</jedis.version>
|
||||
<jjwt.version>0.9.1</jjwt.version> <!-- 0.12.5 requires JWT usage refactoring-->
|
||||
<jjwt.version>0.12.5</jjwt.version>
|
||||
<slf4j.version>2.0.13</slf4j.version>
|
||||
<log4j.version>2.23.1</log4j.version>
|
||||
<logback.version>1.5.5</logback.version>
|
||||
|
||||
@ -482,9 +482,9 @@
|
||||
"issuer-name": "اسم الجهة المصدرة",
|
||||
"issuer-name-required": "اسم الجهة المصدرة مطلوب.",
|
||||
"signings-key": "مفتاح التوقيع",
|
||||
"signings-key-hint": "سلسلة مشفرة بتنسيق Base64 تمثل ما لا يقل عن 256 بت من البيانات.",
|
||||
"signings-key-hint": "سلسلة مشفرة بتنسيق Base64 تمثل ما لا يقل عن 512 بت من البيانات.",
|
||||
"signings-key-required": "مفتاح التوقيع مطلوب.",
|
||||
"signings-key-min-length": "يجب أن يكون مفتاح التوقيع ما لا يقل عن 256 بت من البيانات.",
|
||||
"signings-key-min-length": "يجب أن يكون مفتاح التوقيع ما لا يقل عن 512 بت من البيانات.",
|
||||
"signings-key-base64": "يجب أن يكون مفتاح التوقيع بتنسيق base64.",
|
||||
"expiration-time": "وقت انتهاء صلاحية الرمز (ثانية)",
|
||||
"expiration-time-required": "وقت انتهاء صلاحية الرمز مطلوب.",
|
||||
|
||||
@ -457,9 +457,9 @@
|
||||
"issuer-name": "Issuer name",
|
||||
"issuer-name-required": "Issuer name is required.",
|
||||
"signings-key": "Signing key",
|
||||
"signings-key-hint": "Base64 encoded string representing at least 256 bits of data.",
|
||||
"signings-key-hint": "Base64 encoded string representing at least 512 bits of data.",
|
||||
"signings-key-required": "Signing key is required.",
|
||||
"signings-key-min-length": "Signing key must be at least 256 bits of data.",
|
||||
"signings-key-min-length": "Signing key must be at least 512 bits of data.",
|
||||
"signings-key-base64": "Signing key must be base64 format.",
|
||||
"expiration-time": "Token expiration time (sec)",
|
||||
"expiration-time-required": "Token expiration time is required.",
|
||||
|
||||
@ -431,9 +431,9 @@
|
||||
"issuer-name": "Nombre del emisor",
|
||||
"issuer-name-required": "Se requiere nombre del emisor.",
|
||||
"signings-key": "Clave de firma",
|
||||
"signings-key-hint": "Una string codificada en Base64 representando por lo menos 256 bits de datos.",
|
||||
"signings-key-hint": "Una string codificada en Base64 representando por lo menos 512 bits de datos.",
|
||||
"signings-key-required": "Se requiere clave de firma.",
|
||||
"signings-key-min-length": "La clave de firma debe tener al menos 256 bits de datos.",
|
||||
"signings-key-min-length": "La clave de firma debe tener al menos 512 bits de datos.",
|
||||
"signings-key-base64": "La clave de firma debe estar en formato base64.",
|
||||
"expiration-time": "Caducidad del token (en segundos)",
|
||||
"expiration-time-required": "Se requiere caducidad del token.",
|
||||
|
||||
@ -425,9 +425,9 @@
|
||||
"issuer-name": "Naam van de uitgever",
|
||||
"issuer-name-required": "De naam van de uitgever is vereist.",
|
||||
"signings-key": "Sleutel ondertekenen",
|
||||
"signings-key-hint": "Base64-gecodeerde tekenreeks die ten minste 256 bits aan gegevens vertegenwoordigt.",
|
||||
"signings-key-hint": "Base64-gecodeerde tekenreeks die ten minste 512 bits aan gegevens vertegenwoordigt.",
|
||||
"signings-key-required": "Ondertekeningssleutel is vereist.",
|
||||
"signings-key-min-length": "De ondertekeningssleutel moet ten minste 256 bits aan gegevens bevatten.",
|
||||
"signings-key-min-length": "De ondertekeningssleutel moet ten minste 512 bits aan gegevens bevatten.",
|
||||
"signings-key-base64": "De ondertekeningssleutel moet de base64-indeling hebben.",
|
||||
"expiration-time": "Vervaltijd token (sec)",
|
||||
"expiration-time-required": "De vervaltijd van het token is vereist.",
|
||||
|
||||
@ -457,9 +457,9 @@
|
||||
"issuer-name": "Nazwa emitenta",
|
||||
"issuer-name-required": "Nazwa emitenta jest wymagana.",
|
||||
"signings-key": "Klucz do podpisu",
|
||||
"signings-key-hint": "Ciąg zakodowany w formacie Base64 reprezentujący co najmniej 256 bitów danych.",
|
||||
"signings-key-hint": "Ciąg zakodowany w formacie Base64 reprezentujący co najmniej 512 bitów danych.",
|
||||
"signings-key-required": "Klucz do podpisu jest wymagany.",
|
||||
"signings-key-min-length": "Klucz podpisujący musi mieć co najmniej 256 bitów danych.",
|
||||
"signings-key-min-length": "Klucz podpisujący musi mieć co najmniej 512 bitów danych.",
|
||||
"signings-key-base64": "Klucz podpisujący musi być w formacie base64.",
|
||||
"expiration-time": "Czas ważności tokena (s)",
|
||||
"expiration-time-required": "Czas ważności tokena jest wymagany.",
|
||||
|
||||
@ -451,9 +451,9 @@
|
||||
"issuer-name": "发行者名称",
|
||||
"issuer-name-required": "发行者名称必填。",
|
||||
"signings-key": "签名密钥",
|
||||
"signings-key-hint": "Base64编码的字符串,至少256位数据。",
|
||||
"signings-key-hint": "Base64编码的字符串,至少512位数据。",
|
||||
"signings-key-required": "签名密钥必填。",
|
||||
"signings-key-min-length": "签名密钥必须至少为256位的数据。",
|
||||
"signings-key-min-length": "签名密钥必须至少为512位的数据。",
|
||||
"signings-key-base64": "签名密钥必须是Base64格式。",
|
||||
"expiration-time": "令牌过期时间(秒)",
|
||||
"expiration-time-required": "令牌过期时间是必填。",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user