171 changed files with 5491 additions and 1865 deletions
@ -1,90 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2021 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 lombok.extern.slf4j.Slf4j; |
|
||||
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.RequestParam; |
|
||||
import org.springframework.web.bind.annotation.ResponseBody; |
|
||||
import org.springframework.web.bind.annotation.RestController; |
|
||||
import org.thingsboard.server.common.data.Resource; |
|
||||
import org.thingsboard.server.common.data.ResourceType; |
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.page.PageData; |
|
||||
import org.thingsboard.server.common.data.page.PageLink; |
|
||||
import org.thingsboard.server.dao.resource.ResourceService; |
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent; |
|
||||
|
|
||||
@Slf4j |
|
||||
@RestController |
|
||||
@TbCoreComponent |
|
||||
@RequestMapping("/api") |
|
||||
public class ResourceController extends BaseController { |
|
||||
|
|
||||
private final ResourceService resourceService; |
|
||||
|
|
||||
public ResourceController(ResourceService resourceService) { |
|
||||
this.resourceService = resourceService; |
|
||||
} |
|
||||
|
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
|
||||
@RequestMapping(value = "/resource", method = RequestMethod.POST) |
|
||||
@ResponseBody |
|
||||
public Resource saveResource(Resource resource) throws ThingsboardException { |
|
||||
try { |
|
||||
resource.setTenantId(getTenantId()); |
|
||||
Resource savedResource = checkNotNull(resourceService.saveResource(resource)); |
|
||||
tbClusterService.onResourceChange(savedResource, null); |
|
||||
return savedResource; |
|
||||
} catch (Exception e) { |
|
||||
throw handleException(e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
|
||||
@RequestMapping(value = "/resource", method = RequestMethod.GET) |
|
||||
@ResponseBody |
|
||||
public PageData<Resource> getResources(@RequestParam(required = false) boolean system, |
|
||||
@RequestParam int pageSize, |
|
||||
@RequestParam int page, |
|
||||
@RequestParam(required = false) String sortProperty, |
|
||||
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
|
||||
try { |
|
||||
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder); |
|
||||
return checkNotNull(resourceService.findResourcesByTenantId(system ? TenantId.SYS_TENANT_ID : getTenantId(), pageLink)); |
|
||||
} catch (Exception e) { |
|
||||
throw handleException(e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
|
||||
@RequestMapping(value = "/resource/{resourceType}/{resourceId}", method = RequestMethod.DELETE) |
|
||||
@ResponseBody |
|
||||
public void deleteResource(@PathVariable("resourceType") ResourceType resourceType, |
|
||||
@PathVariable("resourceId") String resourceId) throws ThingsboardException { |
|
||||
try { |
|
||||
Resource resource = checkNotNull(resourceService.getResource(getTenantId(), resourceType, resourceId)); |
|
||||
resourceService.deleteResource(getTenantId(), resourceType, resourceId); |
|
||||
tbClusterService.onResourceDeleted(resource, null); |
|
||||
} catch (Exception e) { |
|
||||
throw handleException(e); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,207 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.core.io.ByteArrayResource; |
||||
|
import org.springframework.http.HttpHeaders; |
||||
|
import org.springframework.http.MediaType; |
||||
|
import org.springframework.http.ResponseEntity; |
||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||
|
import org.springframework.web.bind.annotation.PathVariable; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMethod; |
||||
|
import org.springframework.web.bind.annotation.RequestParam; |
||||
|
import org.springframework.web.bind.annotation.ResponseBody; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
import org.thingsboard.server.common.data.TbResource; |
||||
|
import org.thingsboard.server.common.data.TbResourceInfo; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
||||
|
import org.thingsboard.server.common.data.exception.ThingsboardException; |
||||
|
import org.thingsboard.server.common.data.id.TbResourceId; |
||||
|
import org.thingsboard.server.common.data.lwm2m.LwM2mObject; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
import org.thingsboard.server.dao.resource.TbResourceService; |
||||
|
import org.thingsboard.server.queue.util.TbCoreComponent; |
||||
|
import org.thingsboard.server.service.security.permission.Operation; |
||||
|
import org.thingsboard.server.service.security.permission.Resource; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Base64; |
||||
|
import java.util.List; |
||||
|
import java.util.StringJoiner; |
||||
|
|
||||
|
@Slf4j |
||||
|
@RestController |
||||
|
@TbCoreComponent |
||||
|
@RequestMapping("/api") |
||||
|
public class TbResourceController extends BaseController { |
||||
|
|
||||
|
public static final String RESOURCE_ID = "resourceId"; |
||||
|
|
||||
|
private final TbResourceService resourceService; |
||||
|
|
||||
|
public TbResourceController(TbResourceService resourceService) { |
||||
|
this.resourceService = resourceService; |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public ResponseEntity<org.springframework.core.io.Resource> downloadResource(@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { |
||||
|
checkParameter(RESOURCE_ID, strResourceId); |
||||
|
try { |
||||
|
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); |
||||
|
TbResource tbResource = checkResourceId(resourceId, Operation.READ); |
||||
|
|
||||
|
ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes())); |
||||
|
return ResponseEntity.ok() |
||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName()) |
||||
|
.header("x-filename", tbResource.getFileName()) |
||||
|
.contentLength(resource.contentLength()) |
||||
|
.contentType(MediaType.APPLICATION_OCTET_STREAM) |
||||
|
.body(resource); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource/info/{resourceId}", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public TbResourceInfo getResourceInfoById(@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { |
||||
|
checkParameter(RESOURCE_ID, strResourceId); |
||||
|
try { |
||||
|
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); |
||||
|
return checkResourceInfoId(resourceId, Operation.READ); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public TbResource getResourceById(@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException { |
||||
|
checkParameter(RESOURCE_ID, strResourceId); |
||||
|
try { |
||||
|
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); |
||||
|
return checkResourceId(resourceId, Operation.READ); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource", method = RequestMethod.POST) |
||||
|
@ResponseBody |
||||
|
public List<TbResource> saveResources(@RequestBody List<TbResource> resources) throws ThingsboardException { |
||||
|
try { |
||||
|
List<TbResource> addResources = new ArrayList<>(); |
||||
|
StringJoiner noSaveResources = new StringJoiner("; "); |
||||
|
resources.forEach(resource -> { |
||||
|
try { |
||||
|
resource.setTenantId(getTenantId()); |
||||
|
checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); |
||||
|
addResources.add(addResource(resource)); |
||||
|
} catch (Exception e) { |
||||
|
noSaveResources.add(resource.getFileName()); |
||||
|
log.warn("Fail save resource: [{}]", resource.getFileName(), e); |
||||
|
} |
||||
|
}); |
||||
|
if (noSaveResources.length() > 0) { |
||||
|
throw new ThingsboardException(String.format("Fail save resource: %s", noSaveResources.toString()), ThingsboardErrorCode.INVALID_ARGUMENTS); |
||||
|
} |
||||
|
return addResources; |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public PageData<TbResourceInfo> getResources(@RequestParam int pageSize, |
||||
|
@RequestParam int page, |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@RequestParam(required = false) String sortProperty, |
||||
|
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
||||
|
try { |
||||
|
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); |
||||
|
if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { |
||||
|
return checkNotNull(resourceService.findTenantResourcesByTenantId(getTenantId(), pageLink)); |
||||
|
} else { |
||||
|
return checkNotNull(resourceService.findAllTenantResourcesByTenantId(getTenantId(), pageLink)); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource/lwm2m/page", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public List<LwM2mObject> getLwm2mListObjectsPage(@RequestParam int pageSize, |
||||
|
@RequestParam int page, |
||||
|
@RequestParam(required = false) String textSearch, |
||||
|
@RequestParam(required = false) String sortProperty, |
||||
|
@RequestParam(required = false) String sortOrder) throws ThingsboardException { |
||||
|
try { |
||||
|
PageLink pageLink = new PageLink(pageSize, page, textSearch); |
||||
|
return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink)); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource/lwm2m", method = RequestMethod.GET) |
||||
|
@ResponseBody |
||||
|
public List<LwM2mObject> getLwm2mListObjects(@RequestParam String sortOrder, |
||||
|
@RequestParam String sortProperty, |
||||
|
@RequestParam(required = false) String[] objectIds) throws ThingsboardException { |
||||
|
try { |
||||
|
return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds)); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") |
||||
|
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE) |
||||
|
@ResponseBody |
||||
|
public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException { |
||||
|
checkParameter("resourceId", strResourceId); |
||||
|
try { |
||||
|
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); |
||||
|
TbResource tbResource = checkResourceId(resourceId, Operation.DELETE); |
||||
|
resourceService.deleteResource(getTenantId(), resourceId); |
||||
|
tbClusterService.onResourceDeleted(tbResource, null); |
||||
|
} catch (Exception e) { |
||||
|
throw handleException(e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TbResource addResource(TbResource resource) throws Exception { |
||||
|
checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); |
||||
|
TbResource savedResource = checkNotNull(resourceService.saveResource(resource)); |
||||
|
tbClusterService.onResourceChange(savedResource, null); |
||||
|
return savedResource; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,296 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.core.type.TypeReference; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.data.ResourceType; |
||||
|
import org.thingsboard.server.common.data.TbResource; |
||||
|
import org.thingsboard.server.common.data.TbResourceInfo; |
||||
|
import org.thingsboard.server.common.data.Tenant; |
||||
|
import org.thingsboard.server.common.data.User; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
import org.thingsboard.server.common.data.security.Authority; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
|
||||
|
public abstract class BaseTbResourceControllerTest extends AbstractControllerTest { |
||||
|
|
||||
|
private IdComparator<TbResourceInfo> idComparator = new IdComparator<>(); |
||||
|
|
||||
|
private static final String DEFAULT_FILE_NAME = "test.jks"; |
||||
|
|
||||
|
private Tenant savedTenant; |
||||
|
private User tenantAdmin; |
||||
|
|
||||
|
@Before |
||||
|
public void beforeTest() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
Tenant tenant = new Tenant(); |
||||
|
tenant.setTitle("My tenant"); |
||||
|
savedTenant = doPost("/api/tenant", tenant, Tenant.class); |
||||
|
Assert.assertNotNull(savedTenant); |
||||
|
|
||||
|
tenantAdmin = new User(); |
||||
|
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); |
||||
|
tenantAdmin.setTenantId(savedTenant.getId()); |
||||
|
tenantAdmin.setEmail("tenant2@thingsboard.org"); |
||||
|
tenantAdmin.setFirstName("Joe"); |
||||
|
tenantAdmin.setLastName("Downs"); |
||||
|
|
||||
|
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void afterTest() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testSaveTbResource() throws Exception { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setTitle("My first resource"); |
||||
|
resource.setFileName(DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
|
||||
|
TbResource savedResource = save(resource); |
||||
|
|
||||
|
Assert.assertNotNull(savedResource); |
||||
|
Assert.assertNotNull(savedResource.getId()); |
||||
|
Assert.assertTrue(savedResource.getCreatedTime() > 0); |
||||
|
Assert.assertEquals(savedTenant.getId(), savedResource.getTenantId()); |
||||
|
Assert.assertEquals(resource.getTitle(), savedResource.getTitle()); |
||||
|
Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getFileName()); |
||||
|
Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getResourceKey()); |
||||
|
Assert.assertEquals(resource.getData(), savedResource.getData()); |
||||
|
|
||||
|
savedResource.setTitle("My new resource"); |
||||
|
|
||||
|
save(savedResource); |
||||
|
|
||||
|
TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class); |
||||
|
Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testUpdateTbResourceFromDifferentTenant() throws Exception { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setTitle("My first resource"); |
||||
|
resource.setFileName(DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
|
||||
|
TbResource savedResource = save(resource); |
||||
|
|
||||
|
loginDifferentTenant(); |
||||
|
doPostWithTypedResponse("/api/resource", Collections.singletonList(savedResource), new TypeReference<>(){}, status().isBadRequest()); |
||||
|
deleteDifferentTenant(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindTbResourceById() throws Exception { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setTitle("My first resource"); |
||||
|
resource.setFileName(DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
|
||||
|
TbResource savedResource = save(resource); |
||||
|
|
||||
|
TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class); |
||||
|
Assert.assertNotNull(foundResource); |
||||
|
Assert.assertEquals(savedResource, foundResource); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testDeleteTbResource() throws Exception { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setTitle("My first resource"); |
||||
|
resource.setFileName(DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
|
||||
|
TbResource savedResource = save(resource); |
||||
|
|
||||
|
doDelete("/api/resource/" + savedResource.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
|
||||
|
doGet("/api/resource/" + savedResource.getId().getId().toString()) |
||||
|
.andExpect(status().isNotFound()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindTenantTbResources() throws Exception { |
||||
|
List<TbResource> resourcesToSave = new ArrayList<>(); |
||||
|
for (int i = 0; i < 173; i++) { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setTitle("Resource" + i); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setFileName(i + DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
resourcesToSave.add(resource); |
||||
|
} |
||||
|
|
||||
|
List<TbResourceInfo> resources =save(resourcesToSave).stream().map(TbResourceInfo::new).collect(Collectors.toList()); |
||||
|
|
||||
|
List<TbResourceInfo> loadedResources = new ArrayList<>(); |
||||
|
PageLink pageLink = new PageLink(24); |
||||
|
PageData<TbResourceInfo> pageData; |
||||
|
do { |
||||
|
pageData = doGetTypedWithPageLink("/api/resource?", |
||||
|
new TypeReference<PageData<TbResourceInfo>>() { |
||||
|
}, pageLink); |
||||
|
loadedResources.addAll(pageData.getData()); |
||||
|
if (pageData.hasNext()) { |
||||
|
pageLink = pageLink.nextPageLink(); |
||||
|
} |
||||
|
} while (pageData.hasNext()); |
||||
|
|
||||
|
Collections.sort(resources, idComparator); |
||||
|
Collections.sort(loadedResources, idComparator); |
||||
|
|
||||
|
Assert.assertEquals(resources, loadedResources); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindSystemTbResources() throws Exception { |
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
List<TbResourceInfo> resources = new ArrayList<>(); |
||||
|
for (int i = 0; i < 173; i++) { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setTitle("Resource" + i); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setFileName(i + DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
resources.add(new TbResourceInfo(save(resource))); |
||||
|
} |
||||
|
List<TbResourceInfo> loadedResources = new ArrayList<>(); |
||||
|
PageLink pageLink = new PageLink(24); |
||||
|
PageData<TbResourceInfo> pageData; |
||||
|
do { |
||||
|
pageData = doGetTypedWithPageLink("/api/resource?", |
||||
|
new TypeReference<PageData<TbResourceInfo>>() { |
||||
|
}, pageLink); |
||||
|
loadedResources.addAll(pageData.getData()); |
||||
|
if (pageData.hasNext()) { |
||||
|
pageLink = pageLink.nextPageLink(); |
||||
|
} |
||||
|
} while (pageData.hasNext()); |
||||
|
|
||||
|
Collections.sort(resources, idComparator); |
||||
|
Collections.sort(loadedResources, idComparator); |
||||
|
|
||||
|
Assert.assertEquals(resources, loadedResources); |
||||
|
|
||||
|
for (TbResourceInfo resource : resources) { |
||||
|
doDelete("/api/resource/" + resource.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
|
||||
|
pageLink = new PageLink(27); |
||||
|
loadedResources.clear(); |
||||
|
do { |
||||
|
pageData = doGetTypedWithPageLink("/api/resource?", |
||||
|
new TypeReference<PageData<TbResourceInfo>>() { |
||||
|
}, pageLink); |
||||
|
loadedResources.addAll(pageData.getData()); |
||||
|
if (pageData.hasNext()) { |
||||
|
pageLink = pageLink.nextPageLink(); |
||||
|
} |
||||
|
} while (pageData.hasNext()); |
||||
|
|
||||
|
Assert.assertTrue(loadedResources.isEmpty()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindSystemAndTenantTbResources() throws Exception { |
||||
|
List<TbResourceInfo> systemResources = new ArrayList<>(); |
||||
|
List<TbResourceInfo> expectedResources = new ArrayList<>(); |
||||
|
for (int i = 0; i < 73; i++) { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setTitle("Resource" + i); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setFileName(i + DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
expectedResources.add(new TbResourceInfo(save(resource))); |
||||
|
} |
||||
|
|
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
for (int i = 0; i < 173; i++) { |
||||
|
TbResource resource = new TbResource(); |
||||
|
resource.setTitle("Resource" + i); |
||||
|
resource.setResourceType(ResourceType.JKS); |
||||
|
resource.setFileName(i + DEFAULT_FILE_NAME); |
||||
|
resource.setData("Test Data"); |
||||
|
TbResourceInfo savedResource = new TbResourceInfo(save(resource)); |
||||
|
systemResources.add(savedResource); |
||||
|
if (i >= 73) { |
||||
|
expectedResources.add(savedResource); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
login(tenantAdmin.getEmail(), "testPassword1"); |
||||
|
|
||||
|
List<TbResourceInfo> loadedResources = new ArrayList<>(); |
||||
|
PageLink pageLink = new PageLink(24); |
||||
|
PageData<TbResourceInfo> pageData; |
||||
|
do { |
||||
|
pageData = doGetTypedWithPageLink("/api/resource?", |
||||
|
new TypeReference<PageData<TbResourceInfo>>() { |
||||
|
}, pageLink); |
||||
|
loadedResources.addAll(pageData.getData()); |
||||
|
if (pageData.hasNext()) { |
||||
|
pageLink = pageLink.nextPageLink(); |
||||
|
} |
||||
|
} while (pageData.hasNext()); |
||||
|
|
||||
|
Collections.sort(expectedResources, idComparator); |
||||
|
Collections.sort(loadedResources, idComparator); |
||||
|
|
||||
|
Assert.assertEquals(expectedResources, loadedResources); |
||||
|
|
||||
|
loginSysAdmin(); |
||||
|
|
||||
|
for (TbResourceInfo resource : systemResources) { |
||||
|
doDelete("/api/resource/" + resource.getId().getId().toString()) |
||||
|
.andExpect(status().isOk()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TbResource save(TbResource tbResource) throws Exception { |
||||
|
return save(Collections.singletonList(tbResource)).get(0); |
||||
|
} |
||||
|
|
||||
|
private List<TbResource> save(List<TbResource> tbResources) throws Exception { |
||||
|
return doPostWithTypedResponse("/api/resource", tbResources, new TypeReference<>(){}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.sql; |
||||
|
|
||||
|
import org.thingsboard.server.controller.BaseTbResourceControllerTest; |
||||
|
import org.thingsboard.server.dao.service.DaoSqlTest; |
||||
|
|
||||
|
@DaoSqlTest |
||||
|
public class TbResourceControllerSqlTest extends BaseTbResourceControllerTest { |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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. |
||||
|
|
||||
|
--> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.thingsboard</groupId> |
||||
|
<version>3.3.0-SNAPSHOT</version> |
||||
|
<artifactId>common</artifactId> |
||||
|
</parent> |
||||
|
<groupId>org.thingsboard.common</groupId> |
||||
|
<artifactId>coap-server</artifactId> |
||||
|
<packaging>jar</packaging> |
||||
|
|
||||
|
<name>Thingsboard CoAP server</name> |
||||
|
<url>https://thingsboard.io</url> |
||||
|
|
||||
|
<properties> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<main.dir>${basedir}/../..</main.dir> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard.common</groupId> |
||||
|
<artifactId>queue</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard.common</groupId> |
||||
|
<artifactId>data</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.thingsboard.common.transport</groupId> |
||||
|
<artifactId>transport-api</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework</groupId> |
||||
|
<artifactId>spring-context</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
<scope>provided</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.eclipse.californium</groupId> |
||||
|
<artifactId>californium-core</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.eclipse.californium</groupId> |
||||
|
<artifactId>scandium</artifactId> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
|
||||
|
</project> |
||||
@ -0,0 +1,46 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.coapserver; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
@Slf4j |
||||
|
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") |
||||
|
@Component |
||||
|
public class CoapServerContext { |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${transport.coap.bind_address}") |
||||
|
private String host; |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${transport.coap.bind_port}") |
||||
|
private Integer port; |
||||
|
|
||||
|
@Getter |
||||
|
@Value("${transport.coap.timeout}") |
||||
|
private Long timeout; |
||||
|
|
||||
|
@Getter |
||||
|
@Autowired(required = false) |
||||
|
private TbCoapDtlsSettings dtlsSettings; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.coapserver; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapServer; |
||||
|
|
||||
|
import java.net.UnknownHostException; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
|
||||
|
public interface CoapServerService { |
||||
|
|
||||
|
CoapServer getCoapServer() throws UnknownHostException; |
||||
|
|
||||
|
ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap(); |
||||
|
|
||||
|
long getTimeout(); |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.coapserver; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.core.CoapServer; |
||||
|
import org.eclipse.californium.core.network.CoapEndpoint; |
||||
|
import org.eclipse.californium.core.network.config.NetworkConfig; |
||||
|
import org.eclipse.californium.core.server.resources.Resource; |
||||
|
import org.eclipse.californium.scandium.DTLSConnector; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.PreDestroy; |
||||
|
import java.net.InetAddress; |
||||
|
import java.net.InetSocketAddress; |
||||
|
import java.net.UnknownHostException; |
||||
|
import java.util.Random; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.Executors; |
||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") |
||||
|
public class DefaultCoapServerService implements CoapServerService { |
||||
|
|
||||
|
@Autowired |
||||
|
private CoapServerContext coapServerContext; |
||||
|
|
||||
|
private CoapServer server; |
||||
|
|
||||
|
private TbCoapDtlsCertificateVerifier tbDtlsCertificateVerifier; |
||||
|
|
||||
|
private ScheduledExecutorService dtlsSessionsExecutor; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() throws UnknownHostException { |
||||
|
createCoapServer(); |
||||
|
} |
||||
|
|
||||
|
@PreDestroy |
||||
|
public void shutdown() { |
||||
|
if (dtlsSessionsExecutor != null) { |
||||
|
dtlsSessionsExecutor.shutdownNow(); |
||||
|
} |
||||
|
log.info("Stopping CoAP server!"); |
||||
|
server.destroy(); |
||||
|
log.info("CoAP server stopped!"); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CoapServer getCoapServer() throws UnknownHostException { |
||||
|
if (server != null) { |
||||
|
return server; |
||||
|
} else { |
||||
|
return createCoapServer(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getDtlsSessionsMap() { |
||||
|
return tbDtlsCertificateVerifier != null ? tbDtlsCertificateVerifier.getTbCoapDtlsSessionIdsMap() : null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public long getTimeout() { |
||||
|
return coapServerContext.getTimeout(); |
||||
|
} |
||||
|
|
||||
|
private CoapServer createCoapServer() throws UnknownHostException { |
||||
|
server = new CoapServer(); |
||||
|
|
||||
|
CoapEndpoint.Builder noSecCoapEndpointBuilder = new CoapEndpoint.Builder(); |
||||
|
InetAddress addr = InetAddress.getByName(coapServerContext.getHost()); |
||||
|
InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort()); |
||||
|
noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr); |
||||
|
noSecCoapEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard()); |
||||
|
CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build(); |
||||
|
server.addEndpoint(noSecCoapEndpoint); |
||||
|
|
||||
|
if (isDtlsEnabled()) { |
||||
|
CoapEndpoint.Builder dtlsCoapEndpointBuilder = new CoapEndpoint.Builder(); |
||||
|
TbCoapDtlsSettings dtlsSettings = coapServerContext.getDtlsSettings(); |
||||
|
DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig(); |
||||
|
DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig); |
||||
|
dtlsCoapEndpointBuilder.setConnector(connector); |
||||
|
CoapEndpoint dtlsCoapEndpoint = dtlsCoapEndpointBuilder.build(); |
||||
|
server.addEndpoint(dtlsCoapEndpoint); |
||||
|
if (dtlsConnectorConfig.isClientAuthenticationRequired()) { |
||||
|
tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier(); |
||||
|
dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor(); |
||||
|
dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS); |
||||
|
} |
||||
|
} |
||||
|
Resource root = server.getRoot(); |
||||
|
TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); |
||||
|
server.setMessageDeliverer(messageDeliverer); |
||||
|
|
||||
|
server.start(); |
||||
|
return server; |
||||
|
} |
||||
|
|
||||
|
private boolean isDtlsEnabled() { |
||||
|
return coapServerContext.getDtlsSettings() != null; |
||||
|
} |
||||
|
|
||||
|
private void evictTimeoutSessions() { |
||||
|
tbDtlsCertificateVerifier.evictTimeoutSessions(); |
||||
|
} |
||||
|
|
||||
|
private long getDtlsSessionReportTimeout() { |
||||
|
return tbDtlsCertificateVerifier.getDtlsSessionReportTimeout(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.coapserver; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.elements.util.CertPathUtil; |
||||
|
import org.eclipse.californium.scandium.dtls.AlertMessage; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateMessage; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult; |
||||
|
import org.eclipse.californium.scandium.dtls.ConnectionId; |
||||
|
import org.eclipse.californium.scandium.dtls.DTLSSession; |
||||
|
import org.eclipse.californium.scandium.dtls.HandshakeException; |
||||
|
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; |
||||
|
import org.eclipse.californium.scandium.util.ServerNames; |
||||
|
import org.springframework.util.StringUtils; |
||||
|
import org.thingsboard.server.common.data.DeviceProfile; |
||||
|
import org.thingsboard.server.common.data.DeviceTransportType; |
||||
|
import org.thingsboard.server.common.msg.EncryptionUtil; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.common.transport.TransportServiceCallback; |
||||
|
import org.thingsboard.server.common.transport.auth.SessionInfoCreator; |
||||
|
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
||||
|
import org.thingsboard.server.common.transport.util.SslUtil; |
||||
|
import org.thingsboard.server.gen.transport.TransportProtos; |
||||
|
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
||||
|
|
||||
|
import javax.security.auth.x500.X500Principal; |
||||
|
import java.security.cert.CertPath; |
||||
|
import java.security.cert.CertificateEncodingException; |
||||
|
import java.security.cert.CertificateExpiredException; |
||||
|
import java.security.cert.CertificateNotYetValidException; |
||||
|
import java.security.cert.X509Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
import java.util.concurrent.CountDownLatch; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { |
||||
|
|
||||
|
private final TbCoapDtlsSessionInMemoryStorage tbCoapDtlsSessionInMemoryStorage; |
||||
|
|
||||
|
private TransportService transportService; |
||||
|
private TbServiceInfoProvider serviceInfoProvider; |
||||
|
private boolean skipValidityCheckForClientCert; |
||||
|
|
||||
|
public TbCoapDtlsCertificateVerifier(TransportService transportService, TbServiceInfoProvider serviceInfoProvider, long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout, boolean skipValidityCheckForClientCert) { |
||||
|
this.transportService = transportService; |
||||
|
this.serviceInfoProvider = serviceInfoProvider; |
||||
|
this.skipValidityCheckForClientCert = skipValidityCheckForClientCert; |
||||
|
this.tbCoapDtlsSessionInMemoryStorage = new TbCoapDtlsSessionInMemoryStorage(dtlsSessionInactivityTimeout, dtlsSessionReportTimeout); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<CertificateType> getSupportedCertificateType() { |
||||
|
return Collections.singletonList(CertificateType.X_509); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { |
||||
|
try { |
||||
|
String credentialsBody = null; |
||||
|
CertPath certpath = message.getCertificateChain(); |
||||
|
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); |
||||
|
for (X509Certificate cert : chain) { |
||||
|
try { |
||||
|
if (!skipValidityCheckForClientCert) { |
||||
|
cert.checkValidity(); |
||||
|
} |
||||
|
String strCert = SslUtil.getCertificateString(cert); |
||||
|
String sha3Hash = EncryptionUtil.getSha3Hash(strCert); |
||||
|
final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1]; |
||||
|
CountDownLatch latch = new CountDownLatch(1); |
||||
|
transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), |
||||
|
new TransportServiceCallback<>() { |
||||
|
@Override |
||||
|
public void onSuccess(ValidateDeviceCredentialsResponse msg) { |
||||
|
if (!StringUtils.isEmpty(msg.getCredentials())) { |
||||
|
deviceCredentialsResponse[0] = msg; |
||||
|
} |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onError(Throwable e) { |
||||
|
log.error(e.getMessage(), e); |
||||
|
latch.countDown(); |
||||
|
} |
||||
|
}); |
||||
|
latch.await(10, TimeUnit.SECONDS); |
||||
|
ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; |
||||
|
if (msg != null && strCert.equals(msg.getCredentials())) { |
||||
|
credentialsBody = msg.getCredentials(); |
||||
|
DeviceProfile deviceProfile = msg.getDeviceProfile(); |
||||
|
if (msg.hasDeviceInfo() && deviceProfile != null) { |
||||
|
TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID()); |
||||
|
tbCoapDtlsSessionInMemoryStorage.put(session.getSessionIdentifier().toString(), new TbCoapDtlsSessionInfo(sessionInfoProto, deviceProfile)); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
} catch (InterruptedException | |
||||
|
CertificateEncodingException | |
||||
|
CertificateExpiredException | |
||||
|
CertificateNotYetValidException e) { |
||||
|
log.error(e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
if (credentialsBody == null) { |
||||
|
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE, |
||||
|
session.getPeer()); |
||||
|
throw new HandshakeException("Certificate chain could not be validated", alert); |
||||
|
} else { |
||||
|
return new CertificateVerificationResult(cid, certpath, null); |
||||
|
} |
||||
|
} catch (HandshakeException e) { |
||||
|
log.trace("Certificate validation failed!", e); |
||||
|
return new CertificateVerificationResult(cid, e, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public List<X500Principal> getAcceptedIssuers() { |
||||
|
return CertPathUtil.toSubjects(null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void setResultHandler(HandshakeResultHandler resultHandler) { |
||||
|
// empty implementation
|
||||
|
} |
||||
|
|
||||
|
public ConcurrentMap<String, TbCoapDtlsSessionInfo> getTbCoapDtlsSessionIdsMap() { |
||||
|
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionIdMap(); |
||||
|
} |
||||
|
|
||||
|
public void evictTimeoutSessions() { |
||||
|
tbCoapDtlsSessionInMemoryStorage.evictTimeoutSessions(); |
||||
|
} |
||||
|
|
||||
|
public long getDtlsSessionReportTimeout() { |
||||
|
return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.coapserver; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
import java.util.concurrent.ConcurrentMap; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
public class TbCoapDtlsSessionInMemoryStorage { |
||||
|
|
||||
|
private final ConcurrentMap<String, TbCoapDtlsSessionInfo> dtlsSessionIdMap = new ConcurrentHashMap<>(); |
||||
|
private long dtlsSessionInactivityTimeout; |
||||
|
private long dtlsSessionReportTimeout; |
||||
|
|
||||
|
|
||||
|
public TbCoapDtlsSessionInMemoryStorage(long dtlsSessionInactivityTimeout, long dtlsSessionReportTimeout) { |
||||
|
this.dtlsSessionInactivityTimeout = dtlsSessionInactivityTimeout; |
||||
|
this.dtlsSessionReportTimeout = dtlsSessionReportTimeout; |
||||
|
} |
||||
|
|
||||
|
public void put(String dtlsSessionId, TbCoapDtlsSessionInfo dtlsSessionInfo) { |
||||
|
log.trace("DTLS session added to in-memory store: [{}] timestamp: [{}]", dtlsSessionId, dtlsSessionInfo.getLastActivityTime()); |
||||
|
dtlsSessionIdMap.putIfAbsent(dtlsSessionId, dtlsSessionInfo); |
||||
|
} |
||||
|
|
||||
|
public void evictTimeoutSessions() { |
||||
|
long expTime = System.currentTimeMillis() - dtlsSessionInactivityTimeout; |
||||
|
dtlsSessionIdMap.entrySet().removeIf(entry -> { |
||||
|
if (entry.getValue().getLastActivityTime() < expTime) { |
||||
|
log.trace("DTLS session was removed from in-memory store: [{}]", entry.getKey()); |
||||
|
return true; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,162 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.coapserver; |
||||
|
|
||||
|
import com.google.common.io.Resources; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.eclipse.californium.elements.util.SslContextUtil; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.thingsboard.server.common.transport.TransportService; |
||||
|
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.InetAddress; |
||||
|
import java.net.InetSocketAddress; |
||||
|
import java.net.UnknownHostException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.Optional; |
||||
|
|
||||
|
@Slf4j |
||||
|
@ConditionalOnExpression("'${transport.coap.enabled}'=='true'") |
||||
|
@ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) |
||||
|
@Component |
||||
|
public class TbCoapDtlsSettings { |
||||
|
|
||||
|
@Value("${transport.coap.dtls.bind_address}") |
||||
|
private String host; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.bind_port}") |
||||
|
private Integer port; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.mode}") |
||||
|
private String mode; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_store}") |
||||
|
private String keyStoreFile; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_store_password}") |
||||
|
private String keyStorePassword; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_password}") |
||||
|
private String keyPassword; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.key_alias}") |
||||
|
private String keyAlias; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.skip_validity_check_for_client_cert}") |
||||
|
private boolean skipValidityCheckForClientCert; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}") |
||||
|
private long dtlsSessionInactivityTimeout; |
||||
|
|
||||
|
@Value("${transport.coap.dtls.x509.dtls_session_report_timeout}") |
||||
|
private long dtlsSessionReportTimeout; |
||||
|
|
||||
|
@Autowired |
||||
|
private TransportService transportService; |
||||
|
|
||||
|
@Autowired |
||||
|
private TbServiceInfoProvider serviceInfoProvider; |
||||
|
|
||||
|
public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException { |
||||
|
Optional<SecurityMode> securityModeOpt = SecurityMode.parse(mode); |
||||
|
if (securityModeOpt.isEmpty()) { |
||||
|
log.warn("Incorrect configuration of securityMode {}", mode); |
||||
|
throw new RuntimeException("Failed to parse mode property: " + mode + "!"); |
||||
|
} else { |
||||
|
DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); |
||||
|
configBuilder.setAddress(getInetSocketAddress()); |
||||
|
String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath(); |
||||
|
SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); |
||||
|
SecurityMode securityMode = securityModeOpt.get(); |
||||
|
if (securityMode.equals(SecurityMode.NO_AUTH)) { |
||||
|
configBuilder.setClientAuthenticationRequired(false); |
||||
|
configBuilder.setServerOnly(true); |
||||
|
} else { |
||||
|
configBuilder.setAdvancedCertificateVerifier( |
||||
|
new TbCoapDtlsCertificateVerifier( |
||||
|
transportService, |
||||
|
serviceInfoProvider, |
||||
|
dtlsSessionInactivityTimeout, |
||||
|
dtlsSessionReportTimeout, |
||||
|
skipValidityCheckForClientCert |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), |
||||
|
Collections.singletonList(CertificateType.X_509)); |
||||
|
return configBuilder.build(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) { |
||||
|
try { |
||||
|
return SslContextUtil.loadCredentials(keyStoreFilePath, keyAlias, keyStorePassword.toCharArray(), |
||||
|
keyPassword.toCharArray()); |
||||
|
} catch (GeneralSecurityException | IOException e) { |
||||
|
throw new RuntimeException("Failed to load serverCredentials due to: ", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) { |
||||
|
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
||||
|
try { |
||||
|
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
||||
|
keyStoreFilePath, keyAlias, |
||||
|
keyStorePassword.toCharArray()); |
||||
|
trustBuilder.setTrustedCertificates(trustedCertificates); |
||||
|
if (trustBuilder.hasTrusts()) { |
||||
|
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
||||
|
} |
||||
|
} catch (GeneralSecurityException | IOException e) { |
||||
|
throw new RuntimeException("Failed to load trusted certificates due to: ", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private InetSocketAddress getInetSocketAddress() throws UnknownHostException { |
||||
|
InetAddress addr = InetAddress.getByName(host); |
||||
|
return new InetSocketAddress(addr, port); |
||||
|
} |
||||
|
|
||||
|
private enum SecurityMode { |
||||
|
X509, |
||||
|
NO_AUTH; |
||||
|
|
||||
|
static Optional<SecurityMode> parse(String name) { |
||||
|
SecurityMode mode = null; |
||||
|
if (name != null) { |
||||
|
for (SecurityMode securityMode : SecurityMode.values()) { |
||||
|
if (securityMode.name().equalsIgnoreCase(name)) { |
||||
|
mode = securityMode; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return Optional.ofNullable(mode); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.resource; |
||||
|
|
||||
|
import org.eclipse.leshan.core.model.InvalidDDFFileException; |
||||
|
import org.thingsboard.server.common.data.TbResource; |
||||
|
import org.thingsboard.server.common.data.TbResourceInfo; |
||||
|
import org.thingsboard.server.common.data.ResourceType; |
||||
|
import org.thingsboard.server.common.data.id.TbResourceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.lwm2m.LwM2mObject; |
||||
|
import org.thingsboard.server.common.data.page.PageData; |
||||
|
import org.thingsboard.server.common.data.page.PageLink; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
public interface TbResourceService { |
||||
|
TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException; |
||||
|
|
||||
|
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); |
||||
|
|
||||
|
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId); |
||||
|
|
||||
|
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); |
||||
|
|
||||
|
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); |
||||
|
|
||||
|
PageData<TbResourceInfo> findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); |
||||
|
|
||||
|
List<LwM2mObject> findLwM2mObject(TenantId tenantId, |
||||
|
String sortOrder, |
||||
|
String sortProperty, |
||||
|
String[] objectIds); |
||||
|
|
||||
|
List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, |
||||
|
String sortProperty, |
||||
|
String sortOrder, |
||||
|
PageLink pageLink); |
||||
|
|
||||
|
void deleteResource(TenantId tenantId, TbResourceId resourceId); |
||||
|
|
||||
|
void deleteResourcesByTenantId(TenantId tenantId); |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.id.TbResourceId; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class TbResource extends TbResourceInfo { |
||||
|
|
||||
|
private static final long serialVersionUID = 7379609705527272306L; |
||||
|
|
||||
|
private String fileName; |
||||
|
|
||||
|
private String data; |
||||
|
|
||||
|
public TbResource() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public TbResource(TbResourceId id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public TbResource(TbResourceInfo resourceInfo) { |
||||
|
super(resourceInfo); |
||||
|
} |
||||
|
|
||||
|
public TbResource(TbResource resource) { |
||||
|
super(resource); |
||||
|
this.data = resource.getData(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
StringBuilder builder = new StringBuilder(); |
||||
|
builder.append("Resource [tenantId="); |
||||
|
builder.append(getTenantId()); |
||||
|
builder.append(", id="); |
||||
|
builder.append(getUuidId()); |
||||
|
builder.append(", createdTime="); |
||||
|
builder.append(createdTime); |
||||
|
builder.append(", title="); |
||||
|
builder.append(getTitle()); |
||||
|
builder.append(", resourceType="); |
||||
|
builder.append(getResourceType()); |
||||
|
builder.append(", resourceKey="); |
||||
|
builder.append(getResourceKey()); |
||||
|
builder.append(", fileName="); |
||||
|
builder.append(fileName); |
||||
|
builder.append(", data="); |
||||
|
builder.append(data); |
||||
|
builder.append("]"); |
||||
|
return builder.toString(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.common.data.id.TbResourceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class TbResourceInfo extends SearchTextBased<TbResourceId> implements HasTenantId { |
||||
|
|
||||
|
private TenantId tenantId; |
||||
|
private String title; |
||||
|
private ResourceType resourceType; |
||||
|
private String resourceKey; |
||||
|
private String searchText; |
||||
|
|
||||
|
public TbResourceInfo() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public TbResourceInfo(TbResourceId id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
public TbResourceInfo(TbResourceInfo resourceInfo) { |
||||
|
super(resourceInfo); |
||||
|
this.tenantId = resourceInfo.getTenantId(); |
||||
|
this.title = resourceInfo.getTitle(); |
||||
|
this.resourceType = resourceInfo.getResourceType(); |
||||
|
this.resourceKey = resourceInfo.getResourceKey(); |
||||
|
this.searchText = resourceInfo.getSearchText(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getSearchText() { |
||||
|
return searchText != null ? searchText : title; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
StringBuilder builder = new StringBuilder(); |
||||
|
builder.append("ResourceInfo [tenantId="); |
||||
|
builder.append(tenantId); |
||||
|
builder.append(", id="); |
||||
|
builder.append(getUuidId()); |
||||
|
builder.append(", createdTime="); |
||||
|
builder.append(createdTime); |
||||
|
builder.append(", title="); |
||||
|
builder.append(title); |
||||
|
builder.append(", resourceType="); |
||||
|
builder.append(resourceType); |
||||
|
builder.append(", resourceKey="); |
||||
|
builder.append(resourceKey); |
||||
|
builder.append("]"); |
||||
|
return builder.toString(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.id; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonCreator; |
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
public class TbResourceId extends UUIDBased implements EntityId { |
||||
|
|
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
@JsonCreator |
||||
|
public TbResourceId(@JsonProperty("id") UUID id) { |
||||
|
super(id); |
||||
|
} |
||||
|
|
||||
|
@JsonIgnore |
||||
|
@Override |
||||
|
public EntityType getEntityType() { |
||||
|
return EntityType.TB_RESOURCE; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.lwm2m; |
||||
|
|
||||
|
public interface LwM2mConstants { |
||||
|
|
||||
|
String LWM2M_SEPARATOR_PATH = "/"; |
||||
|
String LWM2M_SEPARATOR_KEY = "_"; |
||||
|
String LWM2M_SEPARATOR_SEARCH_TEXT = ":"; |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.validation; |
||||
|
|
||||
|
import javax.validation.Constraint; |
||||
|
import javax.validation.Payload; |
||||
|
import java.lang.annotation.ElementType; |
||||
|
import java.lang.annotation.Retention; |
||||
|
import java.lang.annotation.RetentionPolicy; |
||||
|
import java.lang.annotation.Target; |
||||
|
|
||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||
|
@Target(ElementType.FIELD) |
||||
|
@Constraint(validatedBy = {}) |
||||
|
public @interface NoXss { |
||||
|
String message() default "field value is malformed"; |
||||
|
|
||||
|
Class<?>[] groups() default {}; |
||||
|
|
||||
|
Class<? extends Payload>[] payload() default {}; |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.transport.coap.client; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapClient; |
||||
|
import org.eclipse.californium.core.CoapResponse; |
||||
|
import org.eclipse.californium.core.Utils; |
||||
|
import org.eclipse.californium.elements.DtlsEndpointContext; |
||||
|
import org.eclipse.californium.elements.EndpointContext; |
||||
|
import org.eclipse.californium.elements.exception.ConnectorException; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.security.Principal; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class NoSecClient { |
||||
|
|
||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1); |
||||
|
private CoapClient coapClient; |
||||
|
|
||||
|
public NoSecClient(String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys)); |
||||
|
this.coapClient = new CoapClient(uri); |
||||
|
} |
||||
|
|
||||
|
public void test() { |
||||
|
executor.submit(() -> { |
||||
|
try { |
||||
|
while (!Thread.interrupted()) { |
||||
|
CoapResponse response = null; |
||||
|
try { |
||||
|
response = coapClient.get(); |
||||
|
} catch (ConnectorException | IOException e) { |
||||
|
System.err.println("Error occurred while sending request: " + e); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
if (response != null) { |
||||
|
|
||||
|
System.out.println(response.getCode() + " - " + response.getCode().name()); |
||||
|
System.out.println(response.getOptions()); |
||||
|
System.out.println(response.getResponseText()); |
||||
|
System.out.println(); |
||||
|
System.out.println("ADVANCED:"); |
||||
|
EndpointContext context = response.advanced().getSourceContext(); |
||||
|
Principal identity = context.getPeerIdentity(); |
||||
|
if (identity != null) { |
||||
|
System.out.println(context.getPeerIdentity()); |
||||
|
} else { |
||||
|
System.out.println("anonymous"); |
||||
|
} |
||||
|
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
||||
|
System.out.println(Utils.prettyPrint(response)); |
||||
|
} else { |
||||
|
System.out.println("No response received."); |
||||
|
} |
||||
|
Thread.sleep(5000); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("Error occurred while sending COAP requests."); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) { |
||||
|
return "coap://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) throws URISyntaxException { |
||||
|
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.NoSecClient " + |
||||
|
"host port accessToken clientKeys sharedKeys"); |
||||
|
|
||||
|
String host = args[0]; |
||||
|
int port = Integer.parseInt(args[1]); |
||||
|
String accessToken = args[2]; |
||||
|
String clientKeys = args[3]; |
||||
|
String sharedKeys = args[4]; |
||||
|
|
||||
|
NoSecClient client = new NoSecClient(host, port, accessToken, clientKeys, sharedKeys); |
||||
|
client.test(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.transport.coap.client; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapClient; |
||||
|
import org.eclipse.californium.core.CoapResponse; |
||||
|
import org.eclipse.californium.core.Utils; |
||||
|
import org.eclipse.californium.core.network.CoapEndpoint; |
||||
|
import org.eclipse.californium.elements.DtlsEndpointContext; |
||||
|
import org.eclipse.californium.elements.EndpointContext; |
||||
|
import org.eclipse.californium.elements.exception.ConnectorException; |
||||
|
import org.eclipse.californium.elements.util.SslContextUtil; |
||||
|
import org.eclipse.californium.scandium.DTLSConnector; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.Principal; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class SecureClientNoAuth { |
||||
|
|
||||
|
private final DTLSConnector dtlsConnector; |
||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1); |
||||
|
private CoapClient coapClient; |
||||
|
|
||||
|
public SecureClientNoAuth(DTLSConnector dtlsConnector, String host, int port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
this.dtlsConnector = dtlsConnector; |
||||
|
this.coapClient = getCoapClient(host, port, accessToken, clientKeys, sharedKeys); |
||||
|
} |
||||
|
|
||||
|
public void test() { |
||||
|
executor.submit(() -> { |
||||
|
try { |
||||
|
while (!Thread.interrupted()) { |
||||
|
CoapResponse response = null; |
||||
|
try { |
||||
|
response = coapClient.get(); |
||||
|
} catch (ConnectorException | IOException e) { |
||||
|
System.err.println("Error occurred while sending request: " + e); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
if (response != null) { |
||||
|
|
||||
|
System.out.println(response.getCode() + " - " + response.getCode().name()); |
||||
|
System.out.println(response.getOptions()); |
||||
|
System.out.println(response.getResponseText()); |
||||
|
System.out.println(); |
||||
|
System.out.println("ADVANCED:"); |
||||
|
EndpointContext context = response.advanced().getSourceContext(); |
||||
|
Principal identity = context.getPeerIdentity(); |
||||
|
if (identity != null) { |
||||
|
System.out.println(context.getPeerIdentity()); |
||||
|
} else { |
||||
|
System.out.println("anonymous"); |
||||
|
} |
||||
|
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
||||
|
System.out.println(Utils.prettyPrint(response)); |
||||
|
} else { |
||||
|
System.out.println("No response received."); |
||||
|
} |
||||
|
Thread.sleep(5000); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("Error occurred while sending COAP requests."); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private CoapClient getCoapClient(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
URI uri = new URI(getFutureUrl(host, port, accessToken, clientKeys, sharedKeys)); |
||||
|
CoapClient client = new CoapClient(uri); |
||||
|
CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); |
||||
|
builder.setConnector(dtlsConnector); |
||||
|
|
||||
|
client.setEndpoint(builder.build()); |
||||
|
return client; |
||||
|
} |
||||
|
|
||||
|
private String getFutureUrl(String host, Integer port, String accessToken, String clientKeys, String sharedKeys) { |
||||
|
return "coaps://" + host + ":" + port + "/api/v1/" + accessToken + "/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) throws URISyntaxException { |
||||
|
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientNoAuth " + |
||||
|
"host port accessToken keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys"); |
||||
|
|
||||
|
String host = args[0]; |
||||
|
int port = Integer.parseInt(args[1]); |
||||
|
String accessToken = args[2]; |
||||
|
String clientKeys = args[7]; |
||||
|
String sharedKeys = args[8]; |
||||
|
|
||||
|
String keyStoreUriPath = args[3]; |
||||
|
String keyStoreAlias = args[4]; |
||||
|
String trustedAliasPattern = args[5]; |
||||
|
String keyStorePassword = args[6]; |
||||
|
|
||||
|
|
||||
|
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); |
||||
|
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword); |
||||
|
DTLSConnector dtlsConnector = new DTLSConnector(builder.build()); |
||||
|
SecureClientNoAuth client = new SecureClientNoAuth(dtlsConnector, host, port, accessToken, clientKeys, sharedKeys); |
||||
|
client.test(); |
||||
|
} |
||||
|
|
||||
|
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) { |
||||
|
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
||||
|
try { |
||||
|
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( |
||||
|
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray()); |
||||
|
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
||||
|
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray()); |
||||
|
trustBuilder.setTrustedCertificates(trustedCertificates); |
||||
|
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
||||
|
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509)); |
||||
|
} catch (GeneralSecurityException e) { |
||||
|
System.err.println("certificates are invalid!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} catch (IOException e) { |
||||
|
System.err.println("certificates are missing!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,144 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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.transport.coap.client; |
||||
|
|
||||
|
import org.eclipse.californium.core.CoapClient; |
||||
|
import org.eclipse.californium.core.CoapResponse; |
||||
|
import org.eclipse.californium.core.Utils; |
||||
|
import org.eclipse.californium.core.network.CoapEndpoint; |
||||
|
import org.eclipse.californium.elements.DtlsEndpointContext; |
||||
|
import org.eclipse.californium.elements.EndpointContext; |
||||
|
import org.eclipse.californium.elements.exception.ConnectorException; |
||||
|
import org.eclipse.californium.elements.util.SslContextUtil; |
||||
|
import org.eclipse.californium.scandium.DTLSConnector; |
||||
|
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; |
||||
|
import org.eclipse.californium.scandium.dtls.CertificateType; |
||||
|
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.net.URI; |
||||
|
import java.net.URISyntaxException; |
||||
|
import java.security.GeneralSecurityException; |
||||
|
import java.security.Principal; |
||||
|
import java.security.cert.Certificate; |
||||
|
import java.util.Collections; |
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
public class SecureClientX509 { |
||||
|
|
||||
|
private final DTLSConnector dtlsConnector; |
||||
|
private ExecutorService executor = Executors.newFixedThreadPool(1); |
||||
|
private CoapClient coapClient; |
||||
|
|
||||
|
public SecureClientX509(DTLSConnector dtlsConnector, String host, int port, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
this.dtlsConnector = dtlsConnector; |
||||
|
this.coapClient = getCoapClient(host, port, clientKeys, sharedKeys); |
||||
|
} |
||||
|
|
||||
|
public void test() { |
||||
|
executor.submit(() -> { |
||||
|
try { |
||||
|
while (!Thread.interrupted()) { |
||||
|
CoapResponse response = null; |
||||
|
try { |
||||
|
response = coapClient.get(); |
||||
|
} catch (ConnectorException | IOException e) { |
||||
|
System.err.println("Error occurred while sending request: " + e); |
||||
|
System.exit(-1); |
||||
|
} |
||||
|
if (response != null) { |
||||
|
|
||||
|
System.out.println(response.getCode() + " - " + response.getCode().name()); |
||||
|
System.out.println(response.getOptions()); |
||||
|
System.out.println(response.getResponseText()); |
||||
|
System.out.println(); |
||||
|
System.out.println("ADVANCED:"); |
||||
|
EndpointContext context = response.advanced().getSourceContext(); |
||||
|
Principal identity = context.getPeerIdentity(); |
||||
|
if (identity != null) { |
||||
|
System.out.println(context.getPeerIdentity()); |
||||
|
} else { |
||||
|
System.out.println("anonymous"); |
||||
|
} |
||||
|
System.out.println(context.get(DtlsEndpointContext.KEY_CIPHER)); |
||||
|
System.out.println(Utils.prettyPrint(response)); |
||||
|
} else { |
||||
|
System.out.println("No response received."); |
||||
|
} |
||||
|
Thread.sleep(5000); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("Error occurred while sending COAP requests."); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private CoapClient getCoapClient(String host, Integer port, String clientKeys, String sharedKeys) throws URISyntaxException { |
||||
|
URI uri = new URI(getFutureUrl(host, port, clientKeys, sharedKeys)); |
||||
|
CoapClient client = new CoapClient(uri); |
||||
|
CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); |
||||
|
builder.setConnector(dtlsConnector); |
||||
|
|
||||
|
client.setEndpoint(builder.build()); |
||||
|
return client; |
||||
|
} |
||||
|
|
||||
|
private String getFutureUrl(String host, Integer port, String clientKeys, String sharedKeys) { |
||||
|
return "coaps://" + host + ":" + port + "/api/v1/attributes?clientKeys=" + clientKeys + "&sharedKeys=" + sharedKeys; |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) throws URISyntaxException { |
||||
|
System.out.println("Usage: java -cp ... org.thingsboard.server.transport.coap.client.SecureClientX509 " + |
||||
|
"host port keyStoreUriPath keyStoreAlias trustedAliasPattern clientKeys sharedKeys"); |
||||
|
|
||||
|
String host = args[0]; |
||||
|
int port = Integer.parseInt(args[1]); |
||||
|
String clientKeys = args[6]; |
||||
|
String sharedKeys = args[7]; |
||||
|
|
||||
|
String keyStoreUriPath = args[2]; |
||||
|
String keyStoreAlias = args[3]; |
||||
|
String trustedAliasPattern = args[4]; |
||||
|
String keyStorePassword = args[5]; |
||||
|
|
||||
|
|
||||
|
DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(); |
||||
|
setupCredentials(builder, keyStoreUriPath, keyStoreAlias, trustedAliasPattern, keyStorePassword); |
||||
|
DTLSConnector dtlsConnector = new DTLSConnector(builder.build()); |
||||
|
SecureClientX509 client = new SecureClientX509(dtlsConnector, host, port, clientKeys, sharedKeys); |
||||
|
client.test(); |
||||
|
} |
||||
|
|
||||
|
private static void setupCredentials(DtlsConnectorConfig.Builder config, String keyStoreUriPath, String keyStoreAlias, String trustedAliasPattern, String keyStorePassword) { |
||||
|
StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); |
||||
|
try { |
||||
|
SslContextUtil.Credentials serverCredentials = SslContextUtil.loadCredentials( |
||||
|
keyStoreUriPath, keyStoreAlias, keyStorePassword.toCharArray(), keyStorePassword.toCharArray()); |
||||
|
Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( |
||||
|
keyStoreUriPath, trustedAliasPattern, keyStorePassword.toCharArray()); |
||||
|
trustBuilder.setTrustedCertificates(trustedCertificates); |
||||
|
config.setAdvancedCertificateVerifier(trustBuilder.build()); |
||||
|
config.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), Collections.singletonList(CertificateType.X_509)); |
||||
|
} catch (GeneralSecurityException e) { |
||||
|
System.err.println("certificates are invalid!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} catch (IOException e) { |
||||
|
System.err.println("certificates are missing!"); |
||||
|
throw new IllegalArgumentException(e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,251 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2021 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.transport.lwm2m.server.store; |
|
||||
|
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.eclipse.leshan.core.util.Hex; |
|
||||
import org.eclipse.leshan.server.registration.Registration; |
|
||||
import org.eclipse.leshan.server.security.InMemorySecurityStore; |
|
||||
import org.eclipse.leshan.server.security.SecurityInfo; |
|
||||
import org.eclipse.leshan.server.security.SecurityStoreListener; |
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
|
||||
import org.thingsboard.server.common.data.DeviceProfile; |
|
||||
import org.thingsboard.server.gen.transport.TransportProtos; |
|
||||
import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; |
|
||||
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; |
|
||||
import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; |
|
||||
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; |
|
||||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; |
|
||||
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; |
|
||||
import org.thingsboard.server.transport.lwm2m.utils.TypeServer; |
|
||||
|
|
||||
import java.util.Collection; |
|
||||
import java.util.List; |
|
||||
import java.util.Map; |
|
||||
import java.util.UUID; |
|
||||
import java.util.concurrent.ConcurrentHashMap; |
|
||||
import java.util.concurrent.locks.Lock; |
|
||||
import java.util.concurrent.locks.ReadWriteLock; |
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; |
|
||||
|
|
||||
@Slf4j |
|
||||
//@Service("LwM2mInMemorySecurityStore")
|
|
||||
//@TbLwM2mTransportComponent
|
|
||||
@Deprecated |
|
||||
public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { |
|
||||
private static final boolean INFOS_ARE_COMPROMISED = false; |
|
||||
|
|
||||
// lock for the two maps
|
|
||||
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); |
|
||||
private final Lock readLock = readWriteLock.readLock(); |
|
||||
private final Lock writeLock = readWriteLock.writeLock(); |
|
||||
private final Map<String /** registrationId */, LwM2mClient> sessions = new ConcurrentHashMap<>(); |
|
||||
private Map<UUID /** profileUUid */, LwM2mClientProfile> profiles = new ConcurrentHashMap<>(); |
|
||||
private SecurityStoreListener listener; |
|
||||
|
|
||||
@Autowired |
|
||||
LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; |
|
||||
|
|
||||
/** |
|
||||
* Start after DefaultAuthorizer or LwM2mPskStore |
|
||||
* @param endPoint - |
|
||||
* @return SecurityInfo |
|
||||
*/ |
|
||||
@Override |
|
||||
public SecurityInfo getByEndpoint(String endPoint) { |
|
||||
readLock.lock(); |
|
||||
try { |
|
||||
String registrationId = this.getRegistrationId(endPoint, null); |
|
||||
return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? |
|
||||
sessions.get(registrationId).getSecurityInfo() : this.addLwM2MClientToSession(endPoint); |
|
||||
} finally { |
|
||||
readLock.unlock(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Start after LwM2mPskStore |
|
||||
* @param identity - |
|
||||
* @return SecurityInfo |
|
||||
*/ |
|
||||
@Override |
|
||||
public SecurityInfo getByIdentity(String identity) { |
|
||||
readLock.lock(); |
|
||||
try { |
|
||||
String integrationId = this.getRegistrationId(null, identity); |
|
||||
return (integrationId != null) ? sessions.get(integrationId).getSecurityInfo() : this.addLwM2MClientToSession(identity); |
|
||||
} finally { |
|
||||
readLock.unlock(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Collection<SecurityInfo> getAll() { |
|
||||
readLock.lock(); |
|
||||
try { |
|
||||
return this.sessions.values().stream().map(LwM2mClient::getSecurityInfo).collect(Collectors.toUnmodifiableList()); |
|
||||
} finally { |
|
||||
readLock.unlock(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Removed registration Client from sessions and listener |
|
||||
* @param registrationId if Client |
|
||||
*/ |
|
||||
public void delRemoveSessionAndListener(String registrationId) { |
|
||||
writeLock.lock(); |
|
||||
try { |
|
||||
LwM2mClient lwM2MClient = (sessions.get(registrationId) != null) ? sessions.get(registrationId) : null; |
|
||||
if (lwM2MClient != null) { |
|
||||
if (listener != null) { |
|
||||
listener.securityInfoRemoved(INFOS_ARE_COMPROMISED, lwM2MClient.getSecurityInfo()); |
|
||||
} |
|
||||
sessions.remove(registrationId); |
|
||||
} |
|
||||
} finally { |
|
||||
writeLock.unlock(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void setListener(SecurityStoreListener listener) { |
|
||||
this.listener = listener; |
|
||||
} |
|
||||
|
|
||||
public LwM2mClient getLwM2MClient(String endPoint, String identity) { |
|
||||
Map.Entry<String, LwM2mClient> modelClients = endPoint != null ? |
|
||||
this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndpoint())).findAny().orElse(null) : |
|
||||
this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).findAny().orElse(null); |
|
||||
return modelClients != null ? modelClients.getValue() : null; |
|
||||
} |
|
||||
|
|
||||
public LwM2mClient getLwM2MClientWithReg(Registration registration, String registrationId) { |
|
||||
return registrationId != null ? |
|
||||
this.sessions.get(registrationId) : |
|
||||
this.sessions.containsKey(registration.getId()) ? |
|
||||
this.sessions.get(registration.getId()) : |
|
||||
this.sessions.get(registration.getEndpoint()); |
|
||||
} |
|
||||
|
|
||||
public LwM2mClient getLwM2MClient(TransportProtos.SessionInfoProto sessionInfo) { |
|
||||
return this.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Update in sessions (LwM2MClient for key registration_Id) after starting registration LwM2MClient in LwM2MTransportServiceImpl |
|
||||
* Remove from sessions LwM2MClient with key registration_Endpoint |
|
||||
* @param registration - |
|
||||
* @return LwM2MClient after adding it to session |
|
||||
*/ |
|
||||
public LwM2mClient updateInSessionsLwM2MClient(Registration registration) { |
|
||||
writeLock.lock(); |
|
||||
try { |
|
||||
if (this.sessions.get(registration.getEndpoint()) == null) { |
|
||||
this.addLwM2MClientToSession(registration.getEndpoint()); |
|
||||
} |
|
||||
LwM2mClient lwM2MClient = this.sessions.get(registration.getEndpoint()); |
|
||||
lwM2MClient.setRegistration(registration); |
|
||||
// lwM2MClient.getAttributes().putAll(registration.getAdditionalRegistrationAttributes());
|
|
||||
this.sessions.remove(registration.getEndpoint()); |
|
||||
this.sessions.put(registration.getId(), lwM2MClient); |
|
||||
return lwM2MClient; |
|
||||
} finally { |
|
||||
writeLock.unlock(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private String getRegistrationId(String endPoint, String identity) { |
|
||||
List<String> registrationIds = (endPoint != null) ? |
|
||||
this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndpoint())).map(Map.Entry::getKey).collect(Collectors.toList()) : |
|
||||
this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).map(Map.Entry::getKey).collect(Collectors.toList()); |
|
||||
return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; |
|
||||
} |
|
||||
|
|
||||
public Registration getByRegistration(String registrationId) { |
|
||||
return this.sessions.get(registrationId).getRegistration(); |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Add new LwM2MClient to session |
|
||||
* @param identity- |
|
||||
* @return SecurityInfo. If error - SecurityInfoError |
|
||||
* and log: |
|
||||
* - FORBIDDEN - if there is no authorization |
|
||||
* - profileUuid - if the device does not have a profile |
|
||||
* - device - if the thingsboard does not have a device with a name equal to the identity |
|
||||
*/ |
|
||||
private SecurityInfo addLwM2MClientToSession(String identity) { |
|
||||
ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.CLIENT); |
|
||||
if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { |
|
||||
UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; |
|
||||
if (store.getSecurityInfo() != null && profileUuid != null) { |
|
||||
String endpoint = store.getSecurityInfo().getEndpoint(); |
|
||||
sessions.put(endpoint, new LwM2mClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid, UUID.randomUUID())); |
|
||||
} else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) { |
|
||||
sessions.put(identity, new LwM2mClient(identity, null, null, store.getMsg(), profileUuid, UUID.randomUUID())); |
|
||||
} else { |
|
||||
log.error("Registration failed: FORBIDDEN/profileUuid/device [{}] , endpointId: [{}]", profileUuid, identity); |
|
||||
/** |
|
||||
* Return Error securityInfo |
|
||||
*/ |
|
||||
byte[] preSharedKey = Hex.decodeHex("0A0B".toCharArray()); |
|
||||
SecurityInfo infoError = SecurityInfo.newPreSharedKeyInfo("error", "error_identity", preSharedKey); |
|
||||
return infoError; |
|
||||
} |
|
||||
} |
|
||||
return store.getSecurityInfo(); |
|
||||
} |
|
||||
|
|
||||
public Map<String, LwM2mClient> getSession(UUID sessionUuId) { |
|
||||
return this.sessions.entrySet().stream() |
|
||||
.filter(e -> e.getValue().getSessionId().equals(sessionUuId)) |
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
|
||||
} |
|
||||
|
|
||||
public Map<String, LwM2mClient> getSessions() { |
|
||||
return this.sessions; |
|
||||
} |
|
||||
|
|
||||
public Map<UUID, LwM2mClientProfile> getProfiles() { |
|
||||
return this.profiles; |
|
||||
} |
|
||||
|
|
||||
public LwM2mClientProfile getProfile(UUID profileUuId) { |
|
||||
return this.profiles.get(profileUuId); |
|
||||
} |
|
||||
|
|
||||
public LwM2mClientProfile getProfile(String registrationId) { |
|
||||
UUID profileUUid = this.getSessions().get(registrationId).getProfileId(); |
|
||||
return this.getProfiles().get(profileUUid); |
|
||||
} |
|
||||
|
|
||||
public Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles) { |
|
||||
return this.profiles = profiles; |
|
||||
} |
|
||||
|
|
||||
public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { |
|
||||
LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandler.getLwM2MClientProfileFromThingsboard(deviceProfile); |
|
||||
if (lwM2MClientProfile != null) { |
|
||||
profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); |
|
||||
return true; |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue