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