Browse Source

[Fix] add resource full name check (#15757)

* add resource full name check

* fix UT

* fix query params error
dev_wenjun_refactorMaster
caishunfeng 8 months ago committed by GitHub
parent
commit
bcf1b67255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 114
      dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ResourcesServiceImpl.java
  2. 178
      dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ResourcesServiceTest.java
  3. 4
      dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts

114
dolphinscheduler-api/src/main/java/org/apache/dolphinscheduler/api/service/impl/ResourcesServiceImpl.java

@ -127,12 +127,6 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, "")) {
log.error("current user does not have permission");
putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION);
return result;
}
String userResRootPath = ResourceType.UDF.equals(type) ? storageOperate.getUdfDir(tenantCode) String userResRootPath = ResourceType.UDF.equals(type) ? storageOperate.getUdfDir(tenantCode)
: storageOperate.getResDir(tenantCode); : storageOperate.getResDir(tenantCode);
String fullName = !currentDir.contains(userResRootPath) ? userResRootPath + name : currentDir + name; String fullName = !currentDir.contains(userResRootPath) ? userResRootPath + name : currentDir + name;
@ -178,12 +172,6 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, "")) {
log.error("current user does not have permission");
putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION);
return result;
}
result = verifyFile(name, type, file); result = verifyFile(name, type, file);
if (!result.getCode().equals(Status.SUCCESS.getCode())) { if (!result.getCode().equals(Status.SUCCESS.getCode())) {
return result; return result;
@ -464,16 +452,15 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
String baseDir = isAdmin(loginUser) ? storageOperate.getDir(ResourceType.ALL, tenantCode) checkFullName(tenantCode, fullName);
: storageOperate.getDir(type, tenantCode);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode) if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) {
|| (StringUtils.isNotBlank(fullName) && !StringUtils.startsWith(fullName, baseDir))) {
log.error("current user does not have permission"); log.error("current user does not have permission");
putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION); putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION);
return result; return result;
} }
List<StorageEntity> resourcesList = new ArrayList<>(); List<StorageEntity> resourcesList;
try { try {
resourcesList = queryStorageEntityList(loginUser, fullName, type, tenantCode, false); resourcesList = queryStorageEntityList(loginUser, fullName, type, tenantCode, false);
} catch (ServiceException e) { } catch (ServiceException e) {
@ -645,46 +632,33 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
checkFullName(tenantCode, fullName);
String defaultPath = ""; String baseDir = storageOperate.getDir(type, tenantCode);
List<StorageEntity> resourcesList = new ArrayList<>();
List<StorageEntity> resourcesList = new ArrayList<>();
if (StringUtils.isBlank(fullName)) { if (StringUtils.isBlank(fullName)) {
if (isAdmin(loginUser)) { if (isAdmin(loginUser)) {
List<User> userList = userMapper.selectList(null); List<User> userList = userMapper.selectList(null);
Set<String> visitedTenantEntityCode = new HashSet<>(); Set<String> visitedTenantEntityCode = new HashSet<>();
for (User userEntity : userList) { for (User userEntity : userList) {
String tenantEntityCode = getTenantCode(userEntity); String tenantEntityCode = getTenantCode(userEntity);
if (!visitedTenantEntityCode.contains(tenantEntityCode)) { if (!visitedTenantEntityCode.contains(tenantEntityCode)) {
defaultPath = storageOperate.getResDir(tenantEntityCode); baseDir = storageOperate.getDir(type, tenantEntityCode);
if (type.equals(ResourceType.UDF)) { resourcesList.addAll(storageOperate.listFilesStatusRecursively(baseDir, baseDir,
defaultPath = storageOperate.getUdfDir(tenantEntityCode);
}
resourcesList.addAll(storageOperate.listFilesStatusRecursively(defaultPath, defaultPath,
tenantEntityCode, type)); tenantEntityCode, type));
visitedTenantEntityCode.add(tenantEntityCode); visitedTenantEntityCode.add(tenantEntityCode);
} }
} }
} else { } else {
defaultPath = storageOperate.getResDir(tenantCode); resourcesList = storageOperate.listFilesStatusRecursively(baseDir, baseDir, tenantCode, type);
if (type.equals(ResourceType.UDF)) {
defaultPath = storageOperate.getUdfDir(tenantCode);
}
resourcesList = storageOperate.listFilesStatusRecursively(defaultPath, defaultPath, tenantCode, type);
} }
} else { } else {
defaultPath = storageOperate.getResDir(tenantCode); resourcesList = storageOperate.listFilesStatusRecursively(fullName, baseDir, tenantCode, type);
if (type.equals(ResourceType.UDF)) {
defaultPath = storageOperate.getUdfDir(tenantCode);
}
resourcesList = storageOperate.listFilesStatusRecursively(fullName, defaultPath, tenantCode, type);
} }
Visitor resourceTreeVisitor = new ResourceTreeVisitor(resourcesList); Visitor resourceTreeVisitor = new ResourceTreeVisitor(resourcesList);
result.put(Constants.DATA_LIST, resourceTreeVisitor.visit(defaultPath).getChildren()); result.put(Constants.DATA_LIST, resourceTreeVisitor.visit(baseDir).getChildren());
putMsg(result, Status.SUCCESS); putMsg(result, Status.SUCCESS);
return result; return result;
@ -761,6 +735,7 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
checkFullName(tenantCode, fullName);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) { if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) {
log.error("current user does not have permission"); log.error("current user does not have permission");
@ -768,10 +743,11 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
return result; return result;
} }
String defaultPath = storageOperate.getResDir(tenantCode); String baseDir = storageOperate.getResDir(tenantCode);
StorageEntity resource; StorageEntity resource;
try { try {
resource = storageOperate.getFileStatus(fullName, defaultPath, resTenantCode, null); resource = storageOperate.getFileStatus(fullName, baseDir, resTenantCode, null);
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage() + " Resource path: {}", fullName, e); log.error(e.getMessage() + " Resource path: {}", fullName, e);
putMsg(result, Status.RESOURCE_NOT_EXIST); putMsg(result, Status.RESOURCE_NOT_EXIST);
@ -786,7 +762,7 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
// recursively delete a folder // recursively delete a folder
List<String> allChildren = List<String> allChildren =
storageOperate.listFilesStatusRecursively(fullName, defaultPath, resTenantCode, resource.getType()) storageOperate.listFilesStatusRecursively(fullName, baseDir, resTenantCode, resource.getType())
.stream().map(storageEntity -> storageEntity.getFullName()).collect(Collectors.toList()); .stream().map(storageEntity -> storageEntity.getFullName()).collect(Collectors.toList());
String[] allChildrenFullNameArray = allChildren.stream().toArray(String[]::new); String[] allChildrenFullNameArray = allChildren.stream().toArray(String[]::new);
@ -863,11 +839,7 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
return result; return result;
} }
String defaultPath = storageOperate.getResDir(resTenantCode); String defaultPath = storageOperate.getDir(type, resTenantCode);
if (type.equals(ResourceType.UDF)) {
defaultPath = storageOperate.getUdfDir(resTenantCode);
}
StorageEntity file; StorageEntity file;
try { try {
file = storageOperate.getFileStatus(defaultPath + fileName, defaultPath, resTenantCode, type); file = storageOperate.getFileStatus(defaultPath + fileName, defaultPath, resTenantCode, type);
@ -904,6 +876,7 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
checkFullName(tenantCode, fullName);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) { if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) {
log.error("current user does not have permission"); log.error("current user does not have permission");
@ -924,7 +897,7 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
} }
List<String> content = new ArrayList<>(); List<String> content;
try { try {
if (storageOperate.exists(fullName)) { if (storageOperate.exists(fullName)) {
content = storageOperate.vimFile(tenantCode, fullName, skipLineNum, limit); content = storageOperate.vimFile(tenantCode, fullName, skipLineNum, limit);
@ -977,12 +950,6 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, "")) {
log.error("current user does not have permission");
putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION);
return result;
}
if (FileUtils.directoryTraversal(fileName)) { if (FileUtils.directoryTraversal(fileName)) {
log.warn("File name verify failed, fileName:{}.", RegexUtils.escapeNRT(fileName)); log.warn("File name verify failed, fileName:{}.", RegexUtils.escapeNRT(fileName));
putMsg(result, Status.VERIFY_PARAMETER_NAME_FAILED); putMsg(result, Status.VERIFY_PARAMETER_NAME_FAILED);
@ -1003,13 +970,8 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
String name = fileName.trim() + "." + nameSuffix; String name = fileName.trim() + "." + nameSuffix;
String fullName = "";
String userResRootPath = storageOperate.getResDir(tenantCode); String userResRootPath = storageOperate.getResDir(tenantCode);
if (!currentDir.contains(userResRootPath)) { String fullName = currentDir.contains(userResRootPath) ? currentDir + name : userResRootPath + name;
fullName = userResRootPath + name;
} else {
fullName = currentDir + name;
}
result = verifyResourceName(fullName, type, loginUser); result = verifyResourceName(fullName, type, loginUser);
if (!result.getCode().equals(Status.SUCCESS.getCode())) { if (!result.getCode().equals(Status.SUCCESS.getCode())) {
@ -1063,11 +1025,9 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
putMsg(result, Status.USER_NOT_EXIST, loginUser.getId()); putMsg(result, Status.USER_NOT_EXIST, loginUser.getId());
return result; return result;
} }
if (!fullName.startsWith(storageOperate.getResDir(resTenantCode))) {
throw new ServiceException("Resource file: " + fullName + " is illegal");
}
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
checkFullName(tenantCode, fullName);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) { if (!isUserTenantValid(isAdmin(loginUser), tenantCode, resTenantCode)) {
log.error("current user does not have permission"); log.error("current user does not have permission");
@ -1178,6 +1138,7 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
checkFullName(tenantCode, fullName);
String[] aliasArr = fullName.split("/"); String[] aliasArr = fullName.split("/");
String alias = aliasArr[aliasArr.length - 1]; String alias = aliasArr[aliasArr.length - 1];
@ -1280,12 +1241,6 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
String tenantCode = getTenantCode(user); String tenantCode = getTenantCode(user);
if (!isUserTenantValid(isAdmin(loginUser), tenantCode, "")) {
log.error("current user does not have permission");
putMsg(result, Status.NO_CURRENT_OPERATING_PERMISSION);
return result;
}
String baseDir = isAdmin(loginUser) ? storageOperate.getDir(ResourceType.ALL, tenantCode) String baseDir = isAdmin(loginUser) ? storageOperate.getDir(ResourceType.ALL, tenantCode)
: storageOperate.getDir(type, tenantCode); : storageOperate.getDir(type, tenantCode);
@ -1306,16 +1261,14 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
*/ */
private boolean isUserTenantValid(boolean isAdmin, String userTenantCode, private boolean isUserTenantValid(boolean isAdmin, String userTenantCode,
String resTenantCode) throws ServiceException { String resTenantCode) throws ServiceException {
if (!isAdmin) { if (isAdmin) {
resTenantCode = resTenantCode == null ? "" : resTenantCode; return true;
if (!StringUtils.isBlank(resTenantCode) && !resTenantCode.equals(userTenantCode)) {
// if an ordinary user directly send a query API with a different tenantCode and fullName "",
// still he/she does not have read permission.
return false;
}
} }
if (StringUtils.isEmpty(resTenantCode)) {
return true; // TODO: resource tenant code will be empty when query resources list, need to be optimized
return true;
}
return resTenantCode.equals(userTenantCode);
} }
private String getTenantCode(User user) { private String getTenantCode(User user) {
@ -1325,4 +1278,11 @@ public class ResourcesServiceImpl extends BaseServiceImpl implements ResourcesSe
} }
return tenant.getTenantCode(); return tenant.getTenantCode();
} }
private void checkFullName(String userTenantCode, String fullName) {
String baseDir = storageOperate.getDir(ResourceType.ALL, userTenantCode);
if (StringUtils.isNotBlank(fullName) && !StringUtils.startsWith(fullName, baseDir)) {
throw new ServiceException("Resource file: " + fullName + " is illegal");
}
}
} }

178
dolphinscheduler-api/src/test/java/org/apache/dolphinscheduler/api/service/ResourcesServiceTest.java

@ -27,7 +27,6 @@ import org.apache.dolphinscheduler.api.dto.resources.ResourceComponent;
import org.apache.dolphinscheduler.api.enums.Status; import org.apache.dolphinscheduler.api.enums.Status;
import org.apache.dolphinscheduler.api.exceptions.ServiceException; import org.apache.dolphinscheduler.api.exceptions.ServiceException;
import org.apache.dolphinscheduler.api.permission.ResourcePermissionCheckService; import org.apache.dolphinscheduler.api.permission.ResourcePermissionCheckService;
import org.apache.dolphinscheduler.api.service.impl.BaseServiceImpl;
import org.apache.dolphinscheduler.api.service.impl.ResourcesServiceImpl; import org.apache.dolphinscheduler.api.service.impl.ResourcesServiceImpl;
import org.apache.dolphinscheduler.api.utils.PageInfo; import org.apache.dolphinscheduler.api.utils.PageInfo;
import org.apache.dolphinscheduler.api.utils.Result; import org.apache.dolphinscheduler.api.utils.Result;
@ -36,7 +35,6 @@ import org.apache.dolphinscheduler.common.enums.UserType;
import org.apache.dolphinscheduler.common.utils.FileUtils; import org.apache.dolphinscheduler.common.utils.FileUtils;
import org.apache.dolphinscheduler.common.utils.PropertyUtils; import org.apache.dolphinscheduler.common.utils.PropertyUtils;
import org.apache.dolphinscheduler.dao.entity.Tenant; import org.apache.dolphinscheduler.dao.entity.Tenant;
import org.apache.dolphinscheduler.dao.entity.UdfFunc;
import org.apache.dolphinscheduler.dao.entity.User; import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper; import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper;
import org.apache.dolphinscheduler.dao.mapper.TenantMapper; import org.apache.dolphinscheduler.dao.mapper.TenantMapper;
@ -56,11 +54,9 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@ -89,6 +85,8 @@ public class ResourcesServiceTest {
private static final Logger logger = LoggerFactory.getLogger(ResourcesServiceTest.class); private static final Logger logger = LoggerFactory.getLogger(ResourcesServiceTest.class);
private static final String tenantCode = "123";
@InjectMocks @InjectMocks
private ResourcesServiceImpl resourcesService; private ResourcesServiceImpl resourcesService;
@ -110,10 +108,6 @@ public class ResourcesServiceTest {
@Mock @Mock
private ResourcePermissionCheckService resourcePermissionCheckService; private ResourcePermissionCheckService resourcePermissionCheckService;
private static final Logger serviceLogger = LoggerFactory.getLogger(BaseServiceImpl.class);
private static final Logger resourceLogger = LoggerFactory.getLogger(ResourcesServiceImpl.class);
private MockedStatic<FileUtils> mockedStaticFileUtils; private MockedStatic<FileUtils> mockedStaticFileUtils;
private MockedStatic<Files> mockedStaticFiles; private MockedStatic<Files> mockedStaticFiles;
@ -126,7 +120,7 @@ public class ResourcesServiceTest {
private MockedStatic<java.nio.file.Files> filesMockedStatic; private MockedStatic<java.nio.file.Files> filesMockedStatic;
private Throwable exception; private Exception exception;
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
@ -196,7 +190,7 @@ public class ResourcesServiceTest {
mockMultipartFile = new MockMultipartFile(tooLongFileName, tooLongFileName, "pdf", "test".getBytes()); mockMultipartFile = new MockMultipartFile(tooLongFileName, tooLongFileName, "pdf", "test".getBytes());
when(Files.getFileExtension(tooLongFileName)).thenReturn("pdf"); when(Files.getFileExtension(tooLongFileName)).thenReturn("pdf");
// '/databasePath/tenantCode/RESOURCE/' // '/databasePath/tenantCode/RESOURCE/'
when(storageOperate.getResDir("123")).thenReturn("/dolphinscheduler/123/resources/"); when(storageOperate.getResDir(tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
result = resourcesService.uploadResource(user, tooLongFileName, ResourceType.FILE, mockMultipartFile, "/"); result = resourcesService.uploadResource(user, tooLongFileName, ResourceType.FILE, mockMultipartFile, "/");
logger.info(result.toString()); logger.info(result.toString());
assertEquals(Status.RESOURCE_FULL_NAME_TOO_LONG_ERROR.getMsg(), result.getMsg()); assertEquals(Status.RESOURCE_FULL_NAME_TOO_LONG_ERROR.getMsg(), result.getMsg());
@ -213,7 +207,7 @@ public class ResourcesServiceTest {
user.setTenantId(1); user.setTenantId(1);
when(tenantMapper.queryById(1)).thenReturn(getTenant()); when(tenantMapper.queryById(1)).thenReturn(getTenant());
when(userMapper.selectById(user.getId())).thenReturn(getUser()); when(userMapper.selectById(user.getId())).thenReturn(getUser());
when(storageOperate.getResDir("123")).thenReturn("/dolphinscheduler/123/resources/"); when(storageOperate.getResDir(tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
try { try {
when(storageOperate.exists("/dolphinscheduler/123/resources/directoryTest")).thenReturn(true); when(storageOperate.exists("/dolphinscheduler/123/resources/directoryTest")).thenReturn(true);
} catch (IOException e) { } catch (IOException e) {
@ -233,7 +227,7 @@ public class ResourcesServiceTest {
when(userMapper.selectById(user.getId())).thenReturn(getUser()); when(userMapper.selectById(user.getId())).thenReturn(getUser());
when(tenantMapper.queryById(1)).thenReturn(getTenant()); when(tenantMapper.queryById(1)).thenReturn(getTenant());
when(storageOperate.getResDir("123")).thenReturn("/dolphinscheduler/123/resources/"); when(storageOperate.getResDir(tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
// USER_NO_OPERATION_PERM // USER_NO_OPERATION_PERM
user.setUserType(UserType.GENERAL_USER); user.setUserType(UserType.GENERAL_USER);
@ -242,7 +236,7 @@ public class ResourcesServiceTest {
tenantWNoPermission.setTenantCode("321"); tenantWNoPermission.setTenantCode("321");
when(tenantMapper.queryById(1)).thenReturn(tenantWNoPermission); when(tenantMapper.queryById(1)).thenReturn(tenantWNoPermission);
Result result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest", Result result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest",
"123", "ResourcesServiceTest", ResourceType.FILE, null); tenantCode, "ResourcesServiceTest", ResourceType.FILE, null);
logger.info(result.toString()); logger.info(result.toString());
assertEquals(Status.NO_CURRENT_OPERATING_PERMISSION.getMsg(), result.getMsg()); assertEquals(Status.NO_CURRENT_OPERATING_PERMISSION.getMsg(), result.getMsg());
@ -256,10 +250,10 @@ public class ResourcesServiceTest {
try { try {
when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest", when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest",
"/dolphinscheduler/123/resources/", "123", ResourceType.FILE)) "/dolphinscheduler/123/resources/", tenantCode, ResourceType.FILE))
.thenReturn(getStorageEntityResource()); .thenReturn(getStorageEntityResource());
result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest", result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest",
"123", "ResourcesServiceTest", ResourceType.FILE, null); tenantCode, "ResourcesServiceTest", ResourceType.FILE, null);
logger.info(result.toString()); logger.info(result.toString());
assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); assertEquals(Status.SUCCESS.getMsg(), result.getMsg());
} catch (Exception e) { } catch (Exception e) {
@ -278,14 +272,14 @@ public class ResourcesServiceTest {
try { try {
when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest1.jar", when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest1.jar",
"/dolphinscheduler/123/resources/", "123", ResourceType.UDF)) "/dolphinscheduler/123/resources/", tenantCode, ResourceType.UDF))
.thenReturn(getStorageEntityUdfResource()); .thenReturn(getStorageEntityUdfResource());
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage() + " Resource path: {}", logger.error(e.getMessage() + " Resource path: {}",
"/dolphinscheduler/123/resources/ResourcesServiceTest1.jar", e); "/dolphinscheduler/123/resources/ResourcesServiceTest1.jar", e);
} }
result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest1.jar", result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest1.jar",
"123", "ResourcesServiceTest2.jar", ResourceType.UDF, null); tenantCode, "ResourcesServiceTest2.jar", ResourceType.UDF, null);
logger.info(result.toString()); logger.info(result.toString());
assertEquals(Status.RESOURCE_EXIST.getMsg(), result.getMsg()); assertEquals(Status.RESOURCE_EXIST.getMsg(), result.getMsg());
@ -298,7 +292,7 @@ public class ResourcesServiceTest {
when(tenantMapper.queryById(1)).thenReturn(getTenant()); when(tenantMapper.queryById(1)).thenReturn(getTenant());
result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest1.jar", result = resourcesService.updateResource(user, "/dolphinscheduler/123/resources/ResourcesServiceTest1.jar",
"123", "ResourcesServiceTest1.jar", ResourceType.UDF, null); tenantCode, "ResourcesServiceTest1.jar", ResourceType.UDF, null);
logger.info(result.toString()); logger.info(result.toString());
assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); assertEquals(Status.SUCCESS.getMsg(), result.getMsg());
} }
@ -317,11 +311,11 @@ public class ResourcesServiceTest {
when(userMapper.selectList(null)).thenReturn(mockUserList); when(userMapper.selectList(null)).thenReturn(mockUserList);
when(userMapper.selectById(getUser().getId())).thenReturn(getUser()); when(userMapper.selectById(getUser().getId())).thenReturn(getUser());
when(tenantMapper.queryById(getUser().getTenantId())).thenReturn(getTenant()); when(tenantMapper.queryById(getUser().getTenantId())).thenReturn(getTenant());
when(storageOperate.getResDir("123")).thenReturn("/dolphinscheduler/123/resources/"); when(storageOperate.getResDir(tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
try { try {
when(storageOperate.listFilesStatus("/dolphinscheduler/123/resources/", "/dolphinscheduler/123/resources/", when(storageOperate.listFilesStatus("/dolphinscheduler/123/resources/", "/dolphinscheduler/123/resources/",
"123", ResourceType.FILE)).thenReturn(mockResList); tenantCode, ResourceType.FILE)).thenReturn(mockResList);
} catch (Exception e) { } catch (Exception e) {
logger.error("QueryResourceListPaging Error"); logger.error("QueryResourceListPaging Error");
} }
@ -335,30 +329,30 @@ public class ResourcesServiceTest {
@Test @Test
public void testQueryResourceList() { public void testQueryResourceList() {
User loginUser = new User(); User loginUser = getUser();
loginUser.setId(0);
loginUser.setUserType(UserType.ADMIN_USER);
when(userMapper.selectList(null)).thenReturn(Arrays.asList(loginUser)); when(userMapper.selectList(null)).thenReturn(Collections.singletonList(loginUser));
when(userMapper.selectById(loginUser.getId())).thenReturn(loginUser); when(userMapper.selectById(loginUser.getId())).thenReturn(loginUser);
when(tenantMapper.queryById(Mockito.anyInt())).thenReturn(getTenant()); when(tenantMapper.queryById(Mockito.anyInt())).thenReturn(getTenant());
when(storageOperate.getResDir("123")).thenReturn("/dolphinscheduler/123/resources/"); when(storageOperate.getDir(ResourceType.ALL, tenantCode)).thenReturn("/dolphinscheduler");
when(storageOperate.getDir(ResourceType.FILE, tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
when(storageOperate.getResDir(tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
when(storageOperate.listFilesStatusRecursively("/dolphinscheduler/123/resources/", when(storageOperate.listFilesStatusRecursively("/dolphinscheduler/123/resources/",
"/dolphinscheduler/123/resources/", "123", ResourceType.FILE)) "/dolphinscheduler/123/resources/", tenantCode, ResourceType.FILE))
.thenReturn(Arrays.asList(getStorageEntityResource())); .thenReturn(Collections.singletonList(getStorageEntityResource()));
Map<String, Object> result = resourcesService.queryResourceList(loginUser, ResourceType.FILE, ""); Map<String, Object> result =
logger.info(result.toString()); resourcesService.queryResourceList(loginUser, ResourceType.FILE, "/dolphinscheduler/123/resources/");
assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
List<ResourceComponent> resourceList = (List<ResourceComponent>) result.get(Constants.DATA_LIST); List<ResourceComponent> resourceList = (List<ResourceComponent>) result.get(Constants.DATA_LIST);
Assertions.assertTrue(CollectionUtils.isNotEmpty(resourceList)); Assertions.assertTrue(CollectionUtils.isNotEmpty(resourceList));
// test udf // test udf
when(storageOperate.getUdfDir("123")).thenReturn("/dolphinscheduler/123/udfs/"); when(storageOperate.getDir(ResourceType.UDF, tenantCode)).thenReturn("/dolphinscheduler/123/udfs/");
when(storageOperate.getUdfDir(tenantCode)).thenReturn("/dolphinscheduler/123/udfs/");
when(storageOperate.listFilesStatusRecursively("/dolphinscheduler/123/udfs/", "/dolphinscheduler/123/udfs/", when(storageOperate.listFilesStatusRecursively("/dolphinscheduler/123/udfs/", "/dolphinscheduler/123/udfs/",
"123", ResourceType.UDF)).thenReturn(Arrays.asList(getStorageEntityUdfResource())); tenantCode, ResourceType.UDF)).thenReturn(Arrays.asList(getStorageEntityUdfResource()));
loginUser.setUserType(UserType.GENERAL_USER); loginUser.setUserType(UserType.GENERAL_USER);
result = resourcesService.queryResourceList(loginUser, ResourceType.UDF, ""); result = resourcesService.queryResourceList(loginUser, ResourceType.UDF, "/dolphinscheduler/123/udfs/");
logger.info(result.toString());
assertEquals(Status.SUCCESS, result.get(Constants.STATUS)); assertEquals(Status.SUCCESS, result.get(Constants.STATUS));
resourceList = (List<ResourceComponent>) result.get(Constants.DATA_LIST); resourceList = (List<ResourceComponent>) result.get(Constants.DATA_LIST);
Assertions.assertTrue(CollectionUtils.isNotEmpty(resourceList)); Assertions.assertTrue(CollectionUtils.isNotEmpty(resourceList));
@ -379,18 +373,18 @@ public class ResourcesServiceTest {
// RESOURCE_NOT_EXIST // RESOURCE_NOT_EXIST
when(tenantMapper.queryById(Mockito.anyInt())).thenReturn(getTenant()); when(tenantMapper.queryById(Mockito.anyInt())).thenReturn(getTenant());
when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest", null, "123", null)) when(storageOperate.getDir(ResourceType.ALL, tenantCode)).thenReturn("/dolphinscheduler");
.thenReturn(getStorageEntityResource()); when(storageOperate.getResDir(getTenant().getTenantCode())).thenReturn("/dolphinscheduler/123/resources/");
Result result = resourcesService.delete(loginUser, "/dolphinscheduler/123/resources/ResNotExist", "123"); when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest",
logger.info(result.toString()); "/dolphinscheduler/123/resources/", tenantCode, null))
.thenReturn(getStorageEntityResource());
Result result = resourcesService.delete(loginUser, "/dolphinscheduler/123/resources/ResNotExist", tenantCode);
assertEquals(Status.RESOURCE_NOT_EXIST.getMsg(), result.getMsg()); assertEquals(Status.RESOURCE_NOT_EXIST.getMsg(), result.getMsg());
// SUCCESS // SUCCESS
loginUser.setTenantId(1); loginUser.setTenantId(1);
result = resourcesService.delete(loginUser, "/dolphinscheduler/123/resources/ResourcesServiceTest", "123"); result = resourcesService.delete(loginUser, "/dolphinscheduler/123/resources/ResourcesServiceTest", tenantCode);
logger.info(result.toString());
assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); assertEquals(Status.SUCCESS.getMsg(), result.getMsg());
} }
@Test @Test
@ -421,18 +415,16 @@ public class ResourcesServiceTest {
} }
@Test @Test
public void testReadResource() { public void testReadResource() throws IOException {
// RESOURCE_NOT_EXIST // RESOURCE_NOT_EXIST
when(userMapper.selectById(getUser().getId())).thenReturn(getUser()); when(userMapper.selectById(getUser().getId())).thenReturn(getUser());
when(tenantMapper.queryById(getUser().getTenantId())).thenReturn(getTenant()); when(tenantMapper.queryById(getUser().getTenantId())).thenReturn(getTenant());
Result result = resourcesService.readResource(getUser(), "", "", 1, 10); Result result = resourcesService.readResource(getUser(), "", "", 1, 10);
logger.info(result.toString());
assertEquals(Status.RESOURCE_FILE_NOT_EXIST.getCode(), (int) result.getCode()); assertEquals(Status.RESOURCE_FILE_NOT_EXIST.getCode(), (int) result.getCode());
// RESOURCE_SUFFIX_NOT_SUPPORT_VIEW // RESOURCE_SUFFIX_NOT_SUPPORT_VIEW
when(FileUtils.getResourceViewSuffixes()).thenReturn("class"); when(FileUtils.getResourceViewSuffixes()).thenReturn("class");
result = resourcesService.readResource(getUser(), "", "", 1, 10); result = resourcesService.readResource(getUser(), "", "", 1, 10);
logger.info(result.toString());
assertEquals(Status.RESOURCE_SUFFIX_NOT_SUPPORT_VIEW.getMsg(), result.getMsg()); assertEquals(Status.RESOURCE_SUFFIX_NOT_SUPPORT_VIEW.getMsg(), result.getMsg());
// USER_NOT_EXIST // USER_NOT_EXIST
@ -440,7 +432,6 @@ public class ResourcesServiceTest {
when(FileUtils.getResourceViewSuffixes()).thenReturn("jar"); when(FileUtils.getResourceViewSuffixes()).thenReturn("jar");
when(Files.getFileExtension("ResourcesServiceTest.jar")).thenReturn("jar"); when(Files.getFileExtension("ResourcesServiceTest.jar")).thenReturn("jar");
result = resourcesService.readResource(getUser(), "", "", 1, 10); result = resourcesService.readResource(getUser(), "", "", 1, 10);
logger.info(result.toString());
assertEquals(Status.USER_NOT_EXIST.getCode(), (int) result.getCode()); assertEquals(Status.USER_NOT_EXIST.getCode(), (int) result.getCode());
// TENANT_NOT_EXIST // TENANT_NOT_EXIST
@ -449,17 +440,16 @@ public class ResourcesServiceTest {
Assertions.assertThrows(ServiceException.class, () -> resourcesService.readResource(getUser(), "", "", 1, 10)); Assertions.assertThrows(ServiceException.class, () -> resourcesService.readResource(getUser(), "", "", 1, 10));
// SUCCESS // SUCCESS
when(FileUtils.getResourceViewSuffixes()).thenReturn("jar,sh");
when(storageOperate.getDir(ResourceType.ALL, tenantCode)).thenReturn("/dolphinscheduler");
when(storageOperate.getResDir(getTenant().getTenantCode())).thenReturn("/dolphinscheduler/123/resources/");
when(userMapper.selectById(getUser().getId())).thenReturn(getUser()); when(userMapper.selectById(getUser().getId())).thenReturn(getUser());
when(tenantMapper.queryById(getUser().getTenantId())).thenReturn(getTenant()); when(tenantMapper.queryById(getUser().getTenantId())).thenReturn(getTenant());
try { when(storageOperate.exists(Mockito.any())).thenReturn(true);
when(storageOperate.exists(Mockito.any())).thenReturn(true); when(storageOperate.vimFile(Mockito.any(), Mockito.any(), eq(1), eq(10))).thenReturn(getContent());
when(storageOperate.vimFile(Mockito.any(), Mockito.any(), eq(1), eq(10))).thenReturn(getContent()); when(Files.getFileExtension("/dolphinscheduler/123/resources/test.jar")).thenReturn("jar");
} catch (IOException e) { result = resourcesService.readResource(getUser(), "/dolphinscheduler/123/resources/test.jar", tenantCode, 1,
logger.error("storage error", e); 10);
}
when(Files.getFileExtension("test.jar")).thenReturn("jar");
result = resourcesService.readResource(getUser(), "test.jar", "", 1, 10);
logger.info(result.toString());
assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); assertEquals(Status.SUCCESS.getMsg(), result.getMsg());
} }
@ -494,29 +484,32 @@ public class ResourcesServiceTest {
when(storageOperate.getResDir(Mockito.anyString())).thenReturn("/tmp"); when(storageOperate.getResDir(Mockito.anyString())).thenReturn("/tmp");
ServiceException serviceException = ServiceException serviceException =
Assertions.assertThrows(ServiceException.class, () -> resourcesService.updateResourceContent(getUser(), Assertions.assertThrows(ServiceException.class, () -> resourcesService.updateResourceContent(getUser(),
"/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "123", "content")); "/dolphinscheduler/123/resources/ResourcesServiceTest.jar", tenantCode, "content"));
assertTrue(serviceException.getMessage() assertTrue(serviceException.getMessage()
.contains("Resource file: /dolphinscheduler/123/resources/ResourcesServiceTest.jar is illegal")); .contains("Resource file: /dolphinscheduler/123/resources/ResourcesServiceTest.jar is illegal"));
// RESOURCE_NOT_EXIST // RESOURCE_NOT_EXIST
when(storageOperate.getDir(ResourceType.ALL, tenantCode)).thenReturn("/dolphinscheduler");
when(storageOperate.getResDir(Mockito.anyString())).thenReturn("/dolphinscheduler/123/resources"); when(storageOperate.getResDir(Mockito.anyString())).thenReturn("/dolphinscheduler/123/resources");
when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "", "123", when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "", tenantCode,
ResourceType.FILE)).thenReturn(null); ResourceType.FILE)).thenReturn(null);
Result result = resourcesService.updateResourceContent(getUser(), Result result = resourcesService.updateResourceContent(getUser(),
"/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "123", "content"); "/dolphinscheduler/123/resources/ResourcesServiceTest.jar", tenantCode, "content");
assertEquals(Status.RESOURCE_NOT_EXIST.getMsg(), result.getMsg()); assertEquals(Status.RESOURCE_NOT_EXIST.getMsg(), result.getMsg());
// RESOURCE_SUFFIX_NOT_SUPPORT_VIEW // RESOURCE_SUFFIX_NOT_SUPPORT_VIEW
when(FileUtils.getResourceViewSuffixes()).thenReturn("class"); when(FileUtils.getResourceViewSuffixes()).thenReturn("class");
when(storageOperate.getFileStatus("/dolphinscheduler/123/resources", "", "123", ResourceType.FILE)) when(storageOperate.getFileStatus("/dolphinscheduler/123/resources", "", tenantCode, ResourceType.FILE))
.thenReturn(getStorageEntityResource()); .thenReturn(getStorageEntityResource());
result = resourcesService.updateResourceContent(getUser(), "/dolphinscheduler/123/resources", "123", "content"); result = resourcesService.updateResourceContent(getUser(), "/dolphinscheduler/123/resources", tenantCode,
"content");
assertEquals(Status.RESOURCE_SUFFIX_NOT_SUPPORT_VIEW.getMsg(), result.getMsg()); assertEquals(Status.RESOURCE_SUFFIX_NOT_SUPPORT_VIEW.getMsg(), result.getMsg());
// USER_NOT_EXIST // USER_NOT_EXIST
when(userMapper.selectById(getUser().getId())).thenReturn(null); when(userMapper.selectById(getUser().getId())).thenReturn(null);
result = resourcesService.updateResourceContent(getUser(), "/dolphinscheduler/123/resources/123.class", "123", result = resourcesService.updateResourceContent(getUser(), "/dolphinscheduler/123/resources/123.class",
tenantCode,
"content"); "content");
Assertions.assertTrue(Status.USER_NOT_EXIST.getCode() == result.getCode()); Assertions.assertTrue(Status.USER_NOT_EXIST.getCode() == result.getCode());
@ -524,10 +517,10 @@ public class ResourcesServiceTest {
when(userMapper.selectById(getUser().getId())).thenReturn(getUser()); when(userMapper.selectById(getUser().getId())).thenReturn(getUser());
when(tenantMapper.queryById(1)).thenReturn(null); when(tenantMapper.queryById(1)).thenReturn(null);
Assertions.assertThrows(ServiceException.class, () -> resourcesService.updateResourceContent(getUser(), Assertions.assertThrows(ServiceException.class, () -> resourcesService.updateResourceContent(getUser(),
"/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "123", "content")); "/dolphinscheduler/123/resources/ResourcesServiceTest.jar", tenantCode, "content"));
// SUCCESS // SUCCESS
when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "", "123", when(storageOperate.getFileStatus("/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "", tenantCode,
ResourceType.FILE)).thenReturn(getStorageEntityResource()); ResourceType.FILE)).thenReturn(getStorageEntityResource());
when(Files.getFileExtension(Mockito.anyString())).thenReturn("jar"); when(Files.getFileExtension(Mockito.anyString())).thenReturn("jar");
@ -537,7 +530,7 @@ public class ResourcesServiceTest {
when(FileUtils.getUploadFilename(Mockito.anyString(), Mockito.anyString())).thenReturn("test"); when(FileUtils.getUploadFilename(Mockito.anyString(), Mockito.anyString())).thenReturn("test");
when(FileUtils.writeContent2File(Mockito.anyString(), Mockito.anyString())).thenReturn(true); when(FileUtils.writeContent2File(Mockito.anyString(), Mockito.anyString())).thenReturn(true);
result = resourcesService.updateResourceContent(getUser(), result = resourcesService.updateResourceContent(getUser(),
"/dolphinscheduler/123/resources/ResourcesServiceTest.jar", "123", "content"); "/dolphinscheduler/123/resources/ResourcesServiceTest.jar", tenantCode, "content");
logger.info(result.toString()); logger.info(result.toString());
assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); assertEquals(Status.SUCCESS.getMsg(), result.getMsg());
} }
@ -628,7 +621,7 @@ public class ResourcesServiceTest {
User user = getUser(); User user = getUser();
when(userMapper.selectById(user.getId())).thenReturn(getUser()); when(userMapper.selectById(user.getId())).thenReturn(getUser());
when(tenantMapper.queryById(user.getTenantId())).thenReturn(getTenant()); when(tenantMapper.queryById(user.getTenantId())).thenReturn(getTenant());
when(storageOperate.getDir(ResourceType.FILE, "123")).thenReturn("/dolphinscheduler/123/resources/"); when(storageOperate.getDir(ResourceType.FILE, tenantCode)).thenReturn("/dolphinscheduler/123/resources/");
try { try {
when(storageOperate.getFileStatus(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(), when(storageOperate.getFileStatus(Mockito.anyString(), Mockito.anyString(), Mockito.anyString(),
Mockito.any())).thenReturn(getStorageEntityResource()); Mockito.any())).thenReturn(getStorageEntityResource());
@ -640,28 +633,29 @@ public class ResourcesServiceTest {
assertEquals(Status.SUCCESS.getMsg(), result.getMsg()); assertEquals(Status.SUCCESS.getMsg(), result.getMsg());
} }
private Set<Integer> getSetIds() {
Set<Integer> resources = new HashSet<>();
resources.add(1);
return resources;
}
private Tenant getTenant() { private Tenant getTenant() {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTenantCode("123"); tenant.setTenantCode(tenantCode);
return tenant; return tenant;
} }
private User getUser() {
User user = new User();
user.setId(1);
user.setUserType(UserType.GENERAL_USER);
user.setTenantId(1);
user.setTenantCode(tenantCode);
return user;
}
private StorageEntity getStorageEntityResource() { private StorageEntity getStorageEntityResource() {
StorageEntity entity = new StorageEntity(); StorageEntity entity = new StorageEntity();
entity.setAlias("ResourcesServiceTest"); entity.setAlias("ResourcesServiceTest");
entity.setFileName("ResourcesServiceTest"); entity.setFileName("ResourcesServiceTest");
entity.setDirectory(false); entity.setDirectory(false);
entity.setUserName("123"); entity.setUserName(tenantCode);
entity.setType(ResourceType.FILE); entity.setType(ResourceType.FILE);
entity.setFullName("/dolphinscheduler/123/resources/ResourcesServiceTest"); entity.setFullName("/dolphinscheduler/123/resources/ResourcesServiceTest");
return entity; return entity;
} }
@ -670,49 +664,13 @@ public class ResourcesServiceTest {
entity.setAlias("ResourcesServiceTest1.jar"); entity.setAlias("ResourcesServiceTest1.jar");
entity.setFileName("ResourcesServiceTest1.jar"); entity.setFileName("ResourcesServiceTest1.jar");
entity.setDirectory(false); entity.setDirectory(false);
entity.setUserName("123"); entity.setUserName(tenantCode);
entity.setType(ResourceType.UDF); entity.setType(ResourceType.UDF);
entity.setFullName("/dolphinscheduler/123/resources/ResourcesServiceTest1.jar"); entity.setFullName("/dolphinscheduler/123/resources/ResourcesServiceTest1.jar");
return entity; return entity;
} }
private UdfFunc getUdfFunc() {
UdfFunc udfFunc = new UdfFunc();
udfFunc.setId(1);
return udfFunc;
}
private UdfFunc getUdfFunc(int udfId) {
UdfFunc udfFunc = new UdfFunc();
udfFunc.setId(udfId);
return udfFunc;
}
private List<UdfFunc> getUdfFuncList() {
List<UdfFunc> udfFuncs = new ArrayList<>();
udfFuncs.add(getUdfFunc(1));
udfFuncs.add(getUdfFunc(2));
udfFuncs.add(getUdfFunc(3));
return udfFuncs;
}
private List<UdfFunc> getSingleUdfFuncList() {
return Collections.singletonList(getUdfFunc(3));
}
private User getUser() {
User user = new User();
user.setId(1);
user.setUserType(UserType.GENERAL_USER);
user.setTenantId(1);
user.setTenantCode("tenantCode");
return user;
}
private List<String> getContent() { private List<String> getContent() {
List<String> contentList = new ArrayList<>(); List<String> contentList = new ArrayList<>();
contentList.add("test"); contentList.add("test");

4
dolphinscheduler-ui/src/views/resource/components/resource/table/use-table.ts

@ -284,8 +284,8 @@ export const useDetailPageStore = defineStore('detailPage', {
setFullName(fullName: string) { setFullName(fullName: string) {
this.fullName = fullName this.fullName = fullName
}, },
setTenantCode(fullName: string) { setTenantCode(tenantCode: string) {
this.fullName = fullName this.tenantCode = tenantCode
}, },
setSearchValue(searchValue: string) { setSearchValue(searchValue: string) {
this.searchValue = searchValue this.searchValue = searchValue

Loading…
Cancel
Save