31 changed files with 957 additions and 4 deletions
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
|
||||
|
} |
||||
@ -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()); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
Loading…
Reference in new issue