29 changed files with 552 additions and 364 deletions
@ -1,86 +0,0 @@ |
|||
/** |
|||
* 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,173 @@ |
|||
/** |
|||
* 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.beans.factory.annotation.Value; |
|||
import org.springframework.http.ResponseEntity; |
|||
import org.springframework.security.access.prepost.PreAuthorize; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.bind.annotation.PostMapping; |
|||
import org.springframework.web.bind.annotation.RequestBody; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.thingsboard.common.util.JacksonUtil; |
|||
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.AndroidConfig; |
|||
import org.thingsboard.server.common.data.mobile.IosConfig; |
|||
import org.thingsboard.server.common.data.mobile.MobileAppSettings; |
|||
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.MobileAppSecretService; |
|||
import org.thingsboard.server.service.security.model.SecurityUser; |
|||
import org.thingsboard.server.service.security.permission.Operation; |
|||
import org.thingsboard.server.service.security.permission.Resource; |
|||
import org.thingsboard.server.service.security.system.SystemSecurityService; |
|||
|
|||
import java.net.URI; |
|||
import java.net.URISyntaxException; |
|||
|
|||
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER; |
|||
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; |
|||
|
|||
@RequiredArgsConstructor |
|||
@RestController |
|||
@TbCoreComponent |
|||
public class MobileApplicationController extends BaseController { |
|||
|
|||
@Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}") |
|||
private int mobileSecretKeyTtl; |
|||
|
|||
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\": [ \"/api/noauth/qr\" ]\n" + |
|||
" }\n" + |
|||
" ]\n" + |
|||
" }\n" + |
|||
"}"; |
|||
|
|||
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&ttl=%s"; |
|||
private final SystemSecurityService systemSecurityService; |
|||
private final MobileAppSecretService mobileAppSecretService; |
|||
private final MobileAppSettingsService mobileAppSettingsService; |
|||
|
|||
@ApiOperation(value = "Get associated android applications (getAssetLinks)") |
|||
@GetMapping(value = "/.well-known/assetlinks.json") |
|||
public ResponseEntity<JsonNode> getAssetLinks() { |
|||
MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); |
|||
AndroidConfig androidConfig = mobileAppSettings.getAndroidConfig(); |
|||
if (androidConfig != null && androidConfig.isEnabled() && !androidConfig.getAppPackage().isBlank() && !androidConfig.getSha256CertFingerprints().isBlank()) { |
|||
return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(ASSET_LINKS_PATTERN, androidConfig.getAppPackage(), androidConfig.getSha256CertFingerprints()))); |
|||
} else { |
|||
return ResponseEntity.notFound().build(); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "Get associated ios applications (getAppleAppSiteAssociation)") |
|||
@GetMapping(value = "/.well-known/apple-app-site-association") |
|||
public ResponseEntity<JsonNode> getAppleAppSiteAssociation() { |
|||
MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); |
|||
IosConfig iosConfig = mobileAppSettings.getIosConfig(); |
|||
|
|||
if (iosConfig != null && iosConfig.isEnabled() && !iosConfig.getAppId().isBlank()) { |
|||
return ResponseEntity.ok(JacksonUtil.toJsonNode(String.format(APPLE_APP_SITE_ASSOCIATION_PATTERN, iosConfig.getAppId()))); |
|||
} else { |
|||
return ResponseEntity.notFound().build(); |
|||
} |
|||
} |
|||
|
|||
@ApiOperation(value = "Create Or Update the Mobile application settings (saveMobileAppSettings)", |
|||
notes = "The payload contains associated android and iOS applications and platform qr code widget settings." + SYSTEM_AUTHORITY_PARAGRAPH) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") |
|||
@PostMapping(value = "/api/mobile/app/settings") |
|||
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 associated android and iOS applications and platform qr code widget settings." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@GetMapping(value = "/api/mobile/app/settings") |
|||
public MobileAppSettings getMobileAppSettings() throws ThingsboardException { |
|||
SecurityUser currentUser = getCurrentUser(); |
|||
accessControlService.checkPermission(currentUser, Resource.MOBILE_APP_SETTINGS, Operation.READ); |
|||
|
|||
return mobileAppSettingsService.getMobileAppSettings(currentUser.getTenantId()); |
|||
} |
|||
|
|||
@ApiOperation(value = "Get the deep link to the associated mobile application (getMobileAppDeepLink)", |
|||
notes = "Fetch the url that takes user to associated mobile application " + AVAILABLE_FOR_ANY_AUTHORIZED_USER) |
|||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
|||
@GetMapping(value = "/api/mobile/deepLink", produces = "text/plain") |
|||
public String getMobileAppDeepLink(HttpServletRequest request) throws ThingsboardException, URISyntaxException { |
|||
SecurityUser currentUser = getCurrentUser(); |
|||
String secret = mobileAppSecretService.generateMobileAppSecret(currentUser); |
|||
|
|||
String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); |
|||
String platformDomain = new URI(baseUrl).getHost(); |
|||
MobileAppSettings mobileAppSettings = mobileAppSettingsService.getMobileAppSettings(TenantId.SYS_TENANT_ID); |
|||
String appDomain; |
|||
if (!mobileAppSettings.isUseDefault()) { |
|||
appDomain = platformDomain; |
|||
} else { |
|||
appDomain = DEFAULT_APP_DOMAIN; |
|||
} |
|||
String deepLink = String.format(DEEP_LINK_PATTERN, appDomain, secret, mobileSecretKeyTtl); |
|||
if (!appDomain.equals(platformDomain)) { |
|||
deepLink = deepLink + "&host=" + baseUrl; |
|||
} |
|||
return deepLink; |
|||
} |
|||
|
|||
@ApiOperation(value = "Get User Token (getUserTokenByMobileSecret)", |
|||
notes = "Returns the token of the User based on the provided secret key.") |
|||
@GetMapping(value = "/api/noauth/qr/{secret}") |
|||
public JwtPair getUserTokenByMobileSecret(@Parameter(description = SECRET_PARAM_DESCRIPTION) |
|||
@PathVariable(SECRET) String secret) throws ThingsboardException { |
|||
checkParameter(SECRET, secret); |
|||
return mobileAppSecretService.getJwtPair(secret); |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
/** |
|||
* 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,132 @@ |
|||
/** |
|||
* 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.extern.slf4j.Slf4j; |
|||
import org.junit.Before; |
|||
import org.junit.Test; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.thingsboard.server.common.data.mobile.AndroidConfig; |
|||
import org.thingsboard.server.common.data.mobile.IosConfig; |
|||
import org.thingsboard.server.common.data.mobile.MobileAppSettings; |
|||
import org.thingsboard.server.common.data.mobile.QRCodeConfig; |
|||
import org.thingsboard.server.common.data.security.model.JwtPair; |
|||
import org.thingsboard.server.dao.service.DaoSqlTest; |
|||
|
|||
import java.util.regex.Matcher; |
|||
import java.util.regex.Pattern; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
|||
|
|||
@Slf4j |
|||
@DaoSqlTest |
|||
public class MobileApplicationControllerTest extends AbstractControllerTest { |
|||
|
|||
@Value("${cache.specs.mobileSecretKey.timeToLiveInMinutes:2}") |
|||
private int mobileSecretKeyTtl; |
|||
private static final String ANDROID_PACKAGE_NAME = "testAppPackage"; |
|||
private static final String ANDROID_APP_SHA256 = "DF:28:32:66:8B:A7:D3:EC:7D:73:CF:CC"; |
|||
private static final String APPLE_APP_ID = "testId"; |
|||
private static final String TEST_LABEL = "Test label"; |
|||
|
|||
@Before |
|||
public void setUp() throws Exception { |
|||
loginSysAdmin(); |
|||
|
|||
QRCodeConfig qrCodeConfig = new QRCodeConfig(); |
|||
qrCodeConfig.setQrCodeLabel(TEST_LABEL); |
|||
|
|||
MobileAppSettings mobileAppSettings = new MobileAppSettings(); |
|||
mobileAppSettings.setUseDefault(true); |
|||
AndroidConfig androidConfig = AndroidConfig.builder() |
|||
.appPackage(ANDROID_PACKAGE_NAME) |
|||
.sha256CertFingerprints(ANDROID_APP_SHA256) |
|||
.enabled(true) |
|||
.build(); |
|||
|
|||
IosConfig iosConfig = IosConfig.builder() |
|||
.appId(APPLE_APP_ID) |
|||
.enabled(true) |
|||
.build(); |
|||
mobileAppSettings.setAndroidConfig(androidConfig); |
|||
mobileAppSettings.setIosConfig(iosConfig); |
|||
mobileAppSettings.setQrCodeConfig(qrCodeConfig); |
|||
|
|||
doPost("/api/mobile/app/settings", mobileAppSettings); |
|||
} |
|||
|
|||
@Test |
|||
public void testSaveMobileAppSettings() throws Exception { |
|||
loginSysAdmin(); |
|||
MobileAppSettings mobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); |
|||
assertThat(mobileAppSettings.getQrCodeConfig().getQrCodeLabel()).isEqualTo(TEST_LABEL); |
|||
assertThat(mobileAppSettings.isUseDefault()).isTrue(); |
|||
|
|||
mobileAppSettings.setUseDefault(false); |
|||
|
|||
doPost("/api/mobile/app/settings", mobileAppSettings) |
|||
.andExpect(status().isOk()); |
|||
|
|||
MobileAppSettings updatedMobileAppSettings = doGet("/api/mobile/app/settings", MobileAppSettings.class); |
|||
assertThat(updatedMobileAppSettings.isUseDefault()).isFalse(); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetApplicationAssociations() throws Exception { |
|||
JsonNode assetLinks = doGet("/.well-known/assetlinks.json", JsonNode.class); |
|||
assertThat(assetLinks.get(0).get("target").get("package_name").asText()).isEqualTo(ANDROID_PACKAGE_NAME); |
|||
assertThat(assetLinks.get(0).get("target").get("sha256_cert_fingerprints").get(0).asText()).isEqualTo(ANDROID_APP_SHA256); |
|||
|
|||
JsonNode appleAssociation = doGet("/.well-known/apple-app-site-association", JsonNode.class); |
|||
assertThat(appleAssociation.get("applinks").get("details").get(0).get("appID").asText()).isEqualTo(APPLE_APP_ID); |
|||
} |
|||
|
|||
@Test |
|||
public void testGetMobileDeepLink() throws Exception { |
|||
loginSysAdmin(); |
|||
String deepLink = doGet("/api/mobile/deepLink", String.class); |
|||
|
|||
Pattern expectedPattern = Pattern.compile("https://([^/]+)/api/noauth/qr\\?secret=([^&]+)&ttl=([^&]+)&host=([^&]+)"); |
|||
Matcher parsedDeepLink = expectedPattern.matcher(deepLink); |
|||
assertThat(parsedDeepLink.matches()).isTrue(); |
|||
String secret = parsedDeepLink.group(2); |
|||
String ttl = parsedDeepLink.group(3); |
|||
assertThat(ttl).isEqualTo(String.valueOf(mobileSecretKeyTtl)); |
|||
|
|||
JwtPair jwtPair = doGet("/api/noauth/qr/" + secret, JwtPair.class); |
|||
assertThat(jwtPair).isNotNull(); |
|||
|
|||
loginTenantAdmin(); |
|||
String tenantDeepLink = doGet("/api/mobile/deepLink", String.class); |
|||
Matcher tenantParsedDeepLink = expectedPattern.matcher(tenantDeepLink); |
|||
assertThat(tenantParsedDeepLink.matches()).isTrue(); |
|||
String tenantSecret = tenantParsedDeepLink.group(2); |
|||
|
|||
JwtPair tenantJwtPair = doGet("/api/noauth/qr/" + tenantSecret, JwtPair.class); |
|||
assertThat(tenantJwtPair).isNotNull(); |
|||
|
|||
loginCustomerUser(); |
|||
String customerDeepLink = doGet("/api/mobile/deepLink", String.class); |
|||
Matcher customerParsedDeepLink = expectedPattern.matcher(customerDeepLink); |
|||
assertThat(customerParsedDeepLink.matches()).isTrue(); |
|||
String customerSecret = customerParsedDeepLink.group(2); |
|||
|
|||
JwtPair customerJwtPair = doGet("/api/noauth/qr/" + customerSecret, JwtPair.class); |
|||
assertThat(customerJwtPair).isNotNull(); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* 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 lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.validation.NoXss; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode |
|||
public class AndroidConfig { |
|||
|
|||
private boolean enabled; |
|||
@NoXss |
|||
private String appPackage; |
|||
@NoXss |
|||
private String sha256CertFingerprints; |
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* 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; |
|||
|
|||
public enum BadgePosition { |
|||
|
|||
RIGHT, |
|||
LEFT; |
|||
|
|||
} |
|||
@ -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.common.data.mobile; |
|||
|
|||
|
|||
public enum BadgeStyle { |
|||
|
|||
ORIGINAL, |
|||
WHITE; |
|||
|
|||
} |
|||
@ -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.common.data.mobile; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.validation.NoXss; |
|||
|
|||
@Data |
|||
@Builder |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode |
|||
public class IosConfig { |
|||
|
|||
private boolean enabled; |
|||
@NoXss |
|||
private String appId; |
|||
|
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/** |
|||
* 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 lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import lombok.NoArgsConstructor; |
|||
import org.thingsboard.server.common.data.validation.NoXss; |
|||
|
|||
@Data |
|||
@NoArgsConstructor |
|||
@AllArgsConstructor |
|||
@EqualsAndHashCode |
|||
public class QRCodeConfig { |
|||
|
|||
private boolean showOnHomePage; |
|||
private boolean badgeEnabled; |
|||
private boolean labelEnabled; |
|||
private BadgePosition badgePosition; |
|||
private BadgeStyle badgeStyle; |
|||
@NoXss |
|||
private String qrCodeLabel; |
|||
|
|||
} |
|||
Loading…
Reference in new issue