Browse Source

mobile app QR code initial implementation

pull/10591/head
dashevchenko 2 years ago
parent
commit
d0ff4a2d86
  1. 12
      application/src/main/data/upgrade/3.6.4/schema_update.sql
  2. 31
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  3. 86
      application/src/main/java/org/thingsboard/server/controller/MobileAppLinksController.java
  4. 94
      application/src/main/java/org/thingsboard/server/controller/QRCodeController.java
  5. 33
      application/src/main/java/org/thingsboard/server/service/qr/QRSecretCaffeineCache.java
  6. 25
      application/src/main/java/org/thingsboard/server/service/qr/QRSecretEvictEvent.java
  7. 36
      application/src/main/java/org/thingsboard/server/service/qr/QRSecretRedisCache.java
  8. 28
      application/src/main/java/org/thingsboard/server/service/qr/QRService.java
  9. 66
      application/src/main/java/org/thingsboard/server/service/qr/QRServiceImpl.java
  10. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  11. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  12. 1
      application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java
  13. 4
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  14. 6
      application/src/main/resources/thingsboard.yml
  15. 24
      application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java
  16. 1
      application/src/test/resources/application-test.properties
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  18. 63
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java
  19. 4
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java
  20. 2
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java
  21. 92
      dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java
  22. 34
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsCaffeineCache.java
  23. 29
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsDao.java
  24. 24
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsEvictEvent.java
  25. 36
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsRedisCache.java
  26. 27
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsService.java
  27. 9
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  28. 92
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppSettingsEntity.java
  29. 53
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java
  30. 34
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppSettingsRepository.java
  31. 8
      dao/src/main/resources/sql/schema-entities.sql

12
application/src/main/data/upgrade/3.6.4/schema_update.sql

@ -134,3 +134,15 @@ DELETE FROM asset WHERE type='TbServiceQueue';
DELETE FROM asset_profile WHERE name ='TbServiceQueue';
-- QUEUE STATS UPDATE END
-- MOBILE APPS TABLE CREATE START
CREATE TABLE IF NOT EXISTS mobile_app_settings (
tenant_id UUID NOT NULL,
app_package VARCHAR(100) UNIQUE,
sha256_cert_fingerprints VARCHAR(10000),
app_id VARCHAR(100) UNIQUE,
settings VARCHAR(100000)
);
-- MOBILE APPS TABLE CREATE END

31
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -65,6 +65,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
@ -75,6 +76,7 @@ import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo;
import org.thingsboard.server.common.data.sync.vc.VcUtils;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.mobile.MobileAppSettingsService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
@ -95,6 +97,7 @@ import java.util.Optional;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
@RestController
@TbCoreComponent
@ -119,6 +122,7 @@ public class AdminController extends BaseController {
private final UpdateService updateService;
private final SystemInfoService systemInfoService;
private final AuditLogService auditLogService;
private final MobileAppSettingsService mobileAppSettingsService;
@ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)",
notes = "Get the Administration Settings object using specified string key. Referencing non-existing key will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@ -482,4 +486,31 @@ public class AdminController extends BaseController {
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings);
response.sendRedirect(prevUri);
}
@ApiOperation(value = "Create Or Update the Mobile application settings (saveMobileAppSettings)",
notes = "The payload contains platform qr code widget settings and associated android and iOS applications." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/mobileAppSettings", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public MobileAppSettings saveMobileAppSettings(@Parameter(description = "A JSON value representing the mobile apps configuration")
@RequestBody MobileAppSettings mobileAppSettings) throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.WRITE);
mobileAppSettings.setTenantId(getTenantId());
return mobileAppSettingsService.saveMobileAppSettings(currentUser.getTenantId(), mobileAppSettings);
}
@ApiOperation(value = "Get Mobile application settings (getMobileAppSettings)",
notes = "The payload contains platform qr code widget settings and creds for associated android and iOS applications." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/mobileAppSettings", method = RequestMethod.GET)
@ResponseBody
public MobileAppSettings getMobileAppSettings() throws ThingsboardException {
SecurityUser currentUser = getCurrentUser();
accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.READ);
return mobileAppSettingsService.getMobileAppSettings(currentUser.getTenantId());
}
}

86
application/src/main/java/org/thingsboard/server/controller/MobileAppLinksController.java

@ -0,0 +1,86 @@
/**
* 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.controller;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.mobile.MobileAppSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@RequiredArgsConstructor
@RestController
@TbCoreComponent
public class MobileAppLinksController extends BaseController {
public static final String ASSET_LINKS_PATTERN = "[{\n" +
" \"relation\": [\"delegate_permission/common.handle_all_urls\"],\n" +
" \"target\": {\n" +
" \"namespace\": \"android_app\",\n" +
" \"package_name\": \"%s\",\n" +
" \"sha256_cert_fingerprints\":\n" +
" [\"%s\"]\n" +
" }\n" +
"}]";
public static final String APPLE_APP_SITE_ASSOCIATION_PATTERN = "{\n" +
" \"applinks\": {\n" +
" \"apps\": [],\n" +
" \"details\": [\n" +
" {\n" +
" \"appID\": \"%s\",\n" +
" \"paths\": [ \"*\" ]\n" +
" }\n" +
" ]\n" +
" }\n" +
"}";
private final MobileAppSettingsService mobileAppSettingsService;
@ApiOperation(value = "Get associated android applications (getAssetLinks)")
@RequestMapping(value = "/.well-known/assetlinks.json", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<JsonNode> getAssetLinks() {
MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID);
if (mobileAppSettings != null && mobileAppSettings.getAppPackage() != null && mobileAppSettings.getSha256CertFingerprints() != null) {
return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(ASSET_LINKS_PATTERN, mobileAppSettings.getAppPackage(), mobileAppSettings.getSha256CertFingerprints())));
} else {
return ResponseEntity.notFound().build();
}
}
@ApiOperation(value = "Get associated ios applications (getAppleAppSiteAssociation)")
@RequestMapping(value = "/.well-known/apple-app-site-association", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<JsonNode> getAppleAppSiteAssociation() {
MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID);
if (mobileAppSettings != null && mobileAppSettings.getAppId() != null) {
return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(APPLE_APP_SITE_ASSOCIATION_PATTERN, mobileAppSettings.getAppId())));
} else {
return ResponseEntity.notFound().build();
}
}
}

94
application/src/main/java/org/thingsboard/server/controller/QRCodeController.java

@ -0,0 +1,94 @@
/**
* 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.controller;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.Parameter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.mobile.MobileAppSettingsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.qr.QRService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.system.SystemSecurityService;
import java.net.URI;
import java.net.URISyntaxException;
@RequiredArgsConstructor
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class QRCodeController extends BaseController {
public static final String SECRET = "secret";
public static final String SECRET_PARAM_DESCRIPTION = "A string value representing short-live secret key";
public static final String DEFAULT_APP_DOMAIN = "demo.thingsboard.io";
public static final String DEEP_LINK_PATTERN = "https://%s/api/noauth/qr?secret=%s";
private final QRService qrService;
private final SystemSecurityService systemSecurityService;
private final MobileAppSettingsService mobileAppSettingsService;
@ApiOperation(value = "Get the deep link to the associated mobile application (getQRCodeDeepLink)",
notes = "Fetch the url that takes user to associated mobile application ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/qr/deepLink", method = RequestMethod.GET, produces = "text/plain")
@ResponseBody
public String getQRCodeDeepLink(HttpServletRequest request) throws ThingsboardException, URISyntaxException {
SecurityUser currentUser = getCurrentUser();
String secret = qrService.generateSecret(currentUser);
String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
String platformDomain = new URI(baseUrl).getHost();
JsonNode mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID).getSettings();
String appDomain;
if (mobileAppSettings != null && mobileAppSettings.get("useDefault") != null
&& !mobileAppSettings.get("useDefault").asBoolean()) {
appDomain = platformDomain;
} else {
appDomain = DEFAULT_APP_DOMAIN;
}
String deepLink = String.format(DEEP_LINK_PATTERN, appDomain, secret);
if (!appDomain.equals(platformDomain)) {
deepLink = deepLink + "&host=" + baseUrl;
}
return deepLink;
}
@ApiOperation(value = "Get User Token (getUserToken)",
notes = "Returns the token of the User based on the provided secret key.")
@RequestMapping(value = "/noauth/qr/{secret}", method = RequestMethod.GET)
@ResponseBody
public JwtPair getUserToken(@Parameter(description = SECRET_PARAM_DESCRIPTION)
@PathVariable(SECRET) String secret) throws ThingsboardException {
checkParameter(SECRET, secret);
return qrService.getJwtPair(secret);
}
}

33
application/src/main/java/org/thingsboard/server/service/qr/QRSecretCaffeineCache.java

@ -0,0 +1,33 @@
/**
* 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.qr;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.security.model.JwtPair;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("QRSecretCache")
public class QRSecretCaffeineCache extends CaffeineTbTransactionalCache<String, JwtPair> {
public QRSecretCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.QR_SECRET_KEY_CACHE);
}
}

25
application/src/main/java/org/thingsboard/server/service/qr/QRSecretEvictEvent.java

@ -0,0 +1,25 @@
/**
* 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.qr;
import lombok.Data;
@Data
public class QRSecretEvictEvent {
private final String secret;
}

36
application/src/main/java/org/thingsboard/server/service/qr/QRSecretRedisCache.java

@ -0,0 +1,36 @@
/**
* 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.qr;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbJsonRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.model.JwtPair;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("QRSecretCache")
public class QRSecretRedisCache extends RedisTbTransactionalCache<UserId, JwtPair> {
public QRSecretRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.QR_SECRET_KEY_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(JwtPair.class));
}
}

28
application/src/main/java/org/thingsboard/server/service/qr/QRService.java

@ -0,0 +1,28 @@
/**
* 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.qr;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface QRService {
String generateSecret(SecurityUser securityUser);
JwtPair getJwtPair(String secret) throws ThingsboardException;
}

66
application/src/main/java/org/thingsboard/server/service/qr/QRServiceImpl.java

@ -0,0 +1,66 @@
/**
* 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.qr;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.server.cache.TbCacheValueWrapper;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.dao.entity.AbstractCachedService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
import static org.thingsboard.server.service.security.system.DefaultSystemSecurityService.DEFAULT_QR_SECRET_KEY_LENGTH;
@Service
@Slf4j
@RequiredArgsConstructor
public class QRServiceImpl extends AbstractCachedService<String, JwtPair, QRSecretEvictEvent> implements QRService {
private final JwtTokenFactory tokenFactory;
private final SystemSecurityService systemSecurityService;
@Override
public String generateSecret(SecurityUser securityUser) {
log.trace("Executing generateSecret for user [{}]", securityUser.getId());
Integer qrSecretKeyLength = systemSecurityService.getSecuritySettings().getQrSecretKeyLength();
String secret = StringUtils.generateSafeToken(qrSecretKeyLength == null ? DEFAULT_QR_SECRET_KEY_LENGTH : qrSecretKeyLength);
cache.put(secret, tokenFactory.createTokenPair(securityUser));
return secret;
}
@Override
public JwtPair getJwtPair(String secret) throws ThingsboardException {
TbCacheValueWrapper<JwtPair> jwtPair = cache.get(secret);
if (jwtPair != null) {
return jwtPair.get();
} else {
throw new ThingsboardException("Jwt token not found or expired!", ThingsboardErrorCode.JWT_TOKEN_EXPIRED);
}
}
@TransactionalEventListener(classes = QRSecretEvictEvent.class)
@Override
public void handleEvictEvent(QRSecretEvictEvent event) {
cache.evict(event.getSecret());
}
}

4
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -46,8 +46,8 @@ public enum Resource {
QUEUE(EntityType.QUEUE),
VERSION_CONTROL,
NOTIFICATION(EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE);
EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE),
MOBILE_APP_SETTINGS;
private final Set<EntityType> entityTypes;
Resource() {

1
application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java

@ -41,6 +41,7 @@ public class SysAdminPermissions extends AbstractPermissions {
put(Resource.TB_RESOURCE, systemEntityPermissionChecker);
put(Resource.QUEUE, systemEntityPermissionChecker);
put(Resource.NOTIFICATION, systemEntityPermissionChecker);
put(Resource.MOBILE_APP_SETTINGS, PermissionChecker.allowAllPermissionChecker);
}
private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {

1
application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java

@ -50,6 +50,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
put(Resource.QUEUE, queuePermissionChecker);
put(Resource.VERSION_CONTROL, PermissionChecker.allowAllPermissionChecker);
put(Resource.NOTIFICATION, tenantEntityPermissionChecker);
put(Resource.MOBILE_APP_SETTINGS, PermissionChecker.allowAllPermissionChecker);
}
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {

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

@ -57,7 +57,6 @@ import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.user.UserServiceImpl;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
import org.thingsboard.server.service.security.exception.UserPasswordNotValidException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.utils.MiscUtils;
import ua_parser.Client;
@ -75,6 +74,8 @@ import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTING
@Slf4j
public class DefaultSystemSecurityService implements SystemSecurityService {
public static final int DEFAULT_QR_SECRET_KEY_LENGTH = 64;
@Autowired
private AdminSettingsService adminSettingsService;
@ -109,6 +110,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
securitySettings.setPasswordPolicy(new UserPasswordPolicy());
securitySettings.getPasswordPolicy().setMinimumLength(6);
securitySettings.getPasswordPolicy().setMaximumLength(72);
securitySettings.setQrSecretKeyLength(DEFAULT_QR_SECRET_KEY_LENGTH);
}
return securitySettings;
}

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

@ -579,6 +579,12 @@ cache:
alarmTypes:
timeToLiveInMinutes: "${CACHE_SPECS_ALARM_TYPES_TTL:60}" # Alarm types cache TTL
maxSize: "${CACHE_SPECS_ALARM_TYPES_MAX_SIZE:10000}" # 0 means the cache is disabled
mobileAppSettings:
timeToLiveInMinutes: "${CACHE_SPECS_MOBILE_APP_SETTINGS_TTL:1440}" # Mobile application cache TTL
maxSize: "${CACHE_SPECS_MOBILE_APP_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled
qrSecretKey:
timeToLiveInMinutes: "${CACHE_QR_SECRET_KEY_TTL:2}" # QR secret key cache TTL
maxSize: "${CACHE_QR_SECRET_KEY_MAX_SIZE:10000}" # 0 means the cache is disabled
# Deliberately placed outside the 'specs' group above
notificationRules:

24
application/src/test/java/org/thingsboard/server/controller/AdminControllerTest.java

@ -23,6 +23,7 @@ import org.junit.Test;
import org.mockito.Mockito;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.common.data.security.model.JwtSettings;
import org.thingsboard.server.dao.service.DaoSqlTest;
@ -181,4 +182,27 @@ public class AdminControllerTest extends AbstractControllerTest {
resetJwtSettingsToDefault();
}
@Test
public void testSaveMobileAppSettings() throws Exception {
loginSysAdmin();
MobileAppSettings mobileAppSettings = doGet("/api/admin/mobileAppSettings", MobileAppSettings.class);
assertThat(mobileAppSettings.getSettings().get("qrSecretKeyRefreshRateInMin").asInt()).isEqualTo(1);
JsonNode jsonValue = mobileAppSettings.getSettings();
((ObjectNode) jsonValue).put("useDefault", false);
doPost("/api/admin/mobileAppSettings", mobileAppSettings)
.andExpect(status().isOk());
doGet("/api/admin/mobileAppSettings")
.andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.settings.useDefault", is(false)));
// check refresh rate should be less than secret ttl (5 min)
((ObjectNode) jsonValue).put("qrSecretKeyRefreshRateInMin", 6);
doPost("/api/admin/mobileAppSettings", mobileAppSettings)
.andExpect(status().isBadRequest());
}
}

1
application/src/test/resources/application-test.properties

@ -8,6 +8,7 @@ transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credent
transport.lwm2m.security.trust-credentials.enabled=true
transport.lwm2m.security.trust-credentials.type=KEYSTORE
transport.lwm2m.security.trust-credentials.keystore.store_file=lwm2m/credentials/lwm2mtruststorechain.jks
cache.specs.qrSecretKey.timeToLiveInMinutes=5
# Edge disabled to speed up the context init. Will be enabled by @TestPropertySource in respective tests
edges.enabled=false

2
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -46,4 +46,6 @@ public class CacheConstants {
public static final String ENTITY_COUNT_CACHE = "entityCount";
public static final String RESOURCE_INFO_CACHE = "resourceInfo";
public static final String ALARM_TYPES_CACHE = "alarmTypes";
public static final String MOBILE_APP_SETTINGS_CACHE = "mobileAppSettings";
public static final String QR_SECRET_KEY_CACHE = "qrSecretKey";
}

63
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppSettings.java

@ -0,0 +1,63 @@
/**
* 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.common.data.mobile;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.Serializable;
import static org.thingsboard.server.common.data.BaseDataWithAdditionalInfo.getJson;
import static org.thingsboard.server.common.data.BaseDataWithAdditionalInfo.setJson;
@Data
public class MobileAppSettings implements Serializable {
private static final long serialVersionUID = 2628323657987010348L;
private TenantId tenantId;
@NoXss
@Length(fieldName = "appPackage")
private String appPackage;
@NoXss
@Length(fieldName = "sha256CertFingerprints")
private String sha256CertFingerprints;
@NoXss
@Length(fieldName = "appId")
private String appId;
@NoXss
@Length(fieldName = "settings", max = 10000000)
private transient JsonNode settings;
@JsonIgnore
private byte[] settingsBytes;
public JsonNode getSettings() {
return getJson(() -> settings, () -> settingsBytes);
}
public void setSettings(JsonNode settings) {
setJson(settings, json -> this.settings = json, bytes -> this.settingsBytes = bytes);
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java

@ -20,10 +20,12 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.security.Authority;
import java.io.Serializable;
@Schema(description = "JWT Pair")
@Data
@NoArgsConstructor
public class JwtPair {
public class JwtPair implements Serializable {
@Schema(description = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..")
private String token;

2
common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java

@ -32,4 +32,6 @@ public class SecuritySettings implements Serializable {
private Integer maxFailedLoginAttempts;
@Schema(description = "Email to use for notifications about locked users." )
private String userLockoutNotificationEmail;
@Schema(description = "QR secret key length" )
private Integer qrSecretKeyLength;
}

92
dao/src/main/java/org/thingsboard/server/dao/mobile/BaseMobileAppSettingsService.java

@ -0,0 +1,92 @@
/**
* 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.dao.mobile;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionalEventListener;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.dao.entity.AbstractCachedService;
import org.thingsboard.server.dao.exception.DataValidationException;
@Service
@Slf4j
@RequiredArgsConstructor
public class BaseMobileAppSettingsService extends AbstractCachedService<TenantId, MobileAppSettings, MobileAppSettingsEvictEvent> implements MobileAppSettingsService {
@Value("${cache.specs.qrSecretKey.timeToLiveInMinutes:2}")
private int secretKeyTtl;
private static final int MIN_TIME_TO_DOWNLOAD_MOBILE_APP_IN_MIN = 1;
private final MobileAppSettingsDao mobileAppSettingsDao;
@Override
public MobileAppSettings saveMobileAppSettings(TenantId tenantId, MobileAppSettings settings) {
if (settings.getSettings() != null && settings.getSettings().get("qrSecretKeyRefreshRateInMin") != null
&& (settings.getSettings().get("qrSecretKeyRefreshRateInMin").asInt() + MIN_TIME_TO_DOWNLOAD_MOBILE_APP_IN_MIN > secretKeyTtl)) {
throw new DataValidationException("Refresh rate should be less than server secret key ttl");
}
MobileAppSettings mobileAppSettings = mobileAppSettingsDao.save(tenantId, settings);
publishEvictEvent(new MobileAppSettingsEvictEvent(tenantId));
return mobileAppSettings;
}
@Override
public MobileAppSettings getMobileAppSettings(TenantId tenantId) {
log.trace("Executing getMobileAppSettings for tenant [{}] ", tenantId);
MobileAppSettings mobileAppSettings = cache.getAndPutInTransaction(tenantId,
() -> mobileAppSettingsDao.findByTenantId(tenantId), false);
return constructMobileAppSettings(mobileAppSettings);
}
@TransactionalEventListener(classes = MobileAppSettingsEvictEvent.class)
@Override
public void handleEvictEvent(MobileAppSettingsEvictEvent event) {
cache.evict(event.getTenantId());
}
private MobileAppSettings constructMobileAppSettings(MobileAppSettings mobileAppSettings) {
if (mobileAppSettings == null) {
mobileAppSettings = new MobileAppSettings();
ObjectNode settings = JacksonUtil.newObjectNode();
settings.put("useDefault", true);
settings.put("showOnHomePage", true);
settings.put("qrLabel", "Scan to connect or download mobile app");
settings.put("qrSecretKeyRefreshRateInMin", 1);
ObjectNode androidSettings = JacksonUtil.newObjectNode();
androidSettings.put("badge", "original");
androidSettings.put("badgePosition", "Right");
settings.set("android", androidSettings);
ObjectNode iOSSettings = JacksonUtil.newObjectNode();
iOSSettings.put("badge", "original");
iOSSettings.put("badgePosition", "Right");
settings.set("iOS", iOSSettings);
mobileAppSettings.setSettings(settings);
}
return mobileAppSettings;
}
}

34
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsCaffeineCache.java

@ -0,0 +1,34 @@
/**
* 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.dao.mobile;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
@Service("MobileAppCache")
public class MobileAppSettingsCaffeineCache extends CaffeineTbTransactionalCache<TenantId, MobileAppSettings> {
public MobileAppSettingsCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.MOBILE_APP_SETTINGS_CACHE);
}
}

29
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsDao.java

@ -0,0 +1,29 @@
/**
* 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.dao.mobile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
public interface MobileAppSettingsDao {
MobileAppSettings save(TenantId tenantId, MobileAppSettings whiteLabeling);
MobileAppSettings findByTenantId(TenantId tenantId);
void removeByTenantId(TenantId tenantId);
}

24
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsEvictEvent.java

@ -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.dao.mobile;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class MobileAppSettingsEvictEvent {
private final TenantId tenantId;
}

36
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsRedisCache.java

@ -0,0 +1,36 @@
/**
* 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.dao.mobile;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.CacheSpecsMap;
import org.thingsboard.server.cache.RedisTbTransactionalCache;
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.cache.TbJsonRedisSerializer;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
@Service("MobileAppCache")
public class MobileAppSettingsRedisCache extends RedisTbTransactionalCache<TenantId, MobileAppSettings> {
public MobileAppSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.MOBILE_APP_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(MobileAppSettings.class));
}
}

27
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppSettingsService.java

@ -0,0 +1,27 @@
/**
* 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.dao.mobile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
public interface MobileAppSettingsService {
MobileAppSettings saveMobileAppSettings(TenantId tenantId, MobileAppSettings settings);
MobileAppSettings getMobileAppSettings(TenantId tenantId);
}

9
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -659,6 +659,15 @@ public class ModelConstants {
public static final String NOTIFICATION_TEMPLATE_NOTIFICATION_TYPE_PROPERTY = "notification_type";
public static final String NOTIFICATION_TEMPLATE_CONFIGURATION_PROPERTY = "configuration";
/**
* Mobile application settings constants.
*/
public static final String MOBILE_APP_SETTINGS_TABLE_NAME = "mobile_app_settings";
public static final String MOBILE_APP_SETTINGS_APP_PACKAGE_PROPERTY = "app_package";
public static final String MOBILE_APP_SETTINGS_SHA256_CERT_FINGERPRINTS_PROPERTY = "sha256_cert_fingerprints";
public static final String MOBILE_APP_APP_ID_PROPERTY = "app_id";
public static final String MOBILE_APP_SETTINGS_PROPERTY = "settings";
protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN};
protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN), max(TS_COLUMN)};

92
dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppSettingsEntity.java

@ -0,0 +1,92 @@
/**
* 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.dao.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.ToData;
import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.io.Serializable;
import java.util.UUID;
@Data
@NoArgsConstructor
@Entity
@Table(name = ModelConstants.MOBILE_APP_SETTINGS_TABLE_NAME)
public class MobileAppSettingsEntity implements ToData<MobileAppSettings>, Serializable {
@Id
@Column(name = ModelConstants.TENANT_ID_COLUMN, columnDefinition = "uuid")
protected UUID tenantId;
@Column(name = ModelConstants.MOBILE_APP_SETTINGS_APP_PACKAGE_PROPERTY)
private String appPackage;
@Column(name = ModelConstants.MOBILE_APP_SETTINGS_SHA256_CERT_FINGERPRINTS_PROPERTY)
private String sha256CertFingerprints;
@Column(name = ModelConstants.MOBILE_APP_APP_ID_PROPERTY)
private String appId;
@Convert(converter = JsonConverter.class)
@Column(name = ModelConstants.MOBILE_APP_SETTINGS_PROPERTY)
private JsonNode settings;
public MobileAppSettingsEntity(MobileAppSettings mobileAppSettings) {
this.tenantId = mobileAppSettings.getTenantId().getId();
if (mobileAppSettings.getAppPackage() != null) {
this.appPackage = mobileAppSettings.getAppPackage();
}
if (mobileAppSettings.getSha256CertFingerprints() != null) {
this.sha256CertFingerprints = mobileAppSettings.getSha256CertFingerprints();
}
if (mobileAppSettings.getAppId() != null) {
this.appId = mobileAppSettings.getAppId();
}
if (mobileAppSettings.getSettings() != null) {
this.settings = mobileAppSettings.getSettings();
}
}
@Override
public MobileAppSettings toData() {
MobileAppSettings mobileAppSettings = new MobileAppSettings();
mobileAppSettings.setTenantId(TenantId.fromUUID(tenantId));
if (appPackage != null) {
mobileAppSettings.setAppPackage(appPackage);
}
if (sha256CertFingerprints != null) {
mobileAppSettings.setSha256CertFingerprints(sha256CertFingerprints);
}
if (appId != null) {
mobileAppSettings.setAppId(appId);
}
if (settings != null) {
mobileAppSettings.setSettings(settings);
}
return mobileAppSettings;
}
}

53
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java

@ -0,0 +1,53 @@
/**
* 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.dao.sql.mobile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileAppSettings;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.mobile.MobileAppSettingsDao;
import org.thingsboard.server.dao.model.sql.MobileAppSettingsEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import org.thingsboard.server.dao.util.SqlDao;
@Component
@Slf4j
@SqlDao
public class JpaMobileAppDao extends JpaAbstractDaoListeningExecutorService implements MobileAppSettingsDao {
@Autowired
private MobileAppSettingsRepository mobileAppSettingsRepository;
@Override
public MobileAppSettings save(TenantId tenantId, MobileAppSettings whiteLabeling) {
return DaoUtil.getData(mobileAppSettingsRepository.save(new MobileAppSettingsEntity(whiteLabeling)));
}
@Override
public MobileAppSettings findByTenantId(TenantId tenantId) {
return DaoUtil.getData(mobileAppSettingsRepository.findById(tenantId.getId()));
}
@Override
public void removeByTenantId(TenantId tenantId) {
mobileAppSettingsRepository.deleteByTenantId(tenantId.getId());
}
}

34
dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppSettingsRepository.java

@ -0,0 +1,34 @@
/**
* 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.dao.sql.mobile;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.MobileAppSettingsEntity;
import java.util.UUID;
public interface MobileAppSettingsRepository extends JpaRepository<MobileAppSettingsEntity, UUID> {
@Transactional
@Modifying
@Query("DELETE FROM MobileAppSettingsEntity r WHERE r.tenantId = :tenantId")
void deleteByTenantId(@Param("tenantId") UUID tenantId);
}

8
dao/src/main/resources/sql/schema-entities.sql

@ -893,4 +893,12 @@ CREATE TABLE IF NOT EXISTS queue_stats (
queue_name varchar(255) NOT NULL,
service_id varchar(255) NOT NULL,
CONSTRAINT queue_stats_name_unq_key UNIQUE (tenant_id, queue_name, service_id)
);
CREATE TABLE IF NOT EXISTS mobile_app_settings (
tenant_id UUID NOT NULL,
app_package VARCHAR(100) UNIQUE,
sha256_cert_fingerprints VARCHAR(10000),
app_id VARCHAR(100) UNIQUE,
settings VARCHAR(100000)
);
Loading…
Cancel
Save