fix(测试跟踪): 缺陷平台请求转发添加白名单

This commit is contained in:
chenjianxing 2023-01-13 11:31:08 +08:00 committed by jianxing
parent 0b14520dac
commit 567fed9375
10 changed files with 98 additions and 46 deletions

View File

@ -25,9 +25,11 @@ public class ResourceController {
return resourceService.getMdImage(fileName);
}
@GetMapping(value = "/md/get/url")
public ResponseEntity<byte[]> getFileByUrl(@RequestParam ("url") String url, @RequestParam (value = "platform", required = false) String platform) {
return resourceService.getMdImageByUrl(url, platform);
@GetMapping(value = "/md/get/path")
public ResponseEntity<byte[]> getFileByPath(@RequestParam ("path") String path,
@RequestParam (value = "platform") String platform,
@RequestParam (value = "workspaceId") String workspaceId) {
return resourceService.getMdImageByPath(path, platform, workspaceId);
}
@GetMapping(value = "/ui/get")

View File

@ -3,15 +3,12 @@ package io.metersphere.service;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.MdUploadRequest;
import io.metersphere.i18n.Translator;
import io.metersphere.track.issue.IssueFactory;
import io.metersphere.track.request.testcase.IssuesRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
@ -99,21 +96,14 @@ public class ResourceService {
* http 代理
* 如果当前访问地址是 https直接访问 http 的图片资源
* 由于浏览器的安全机制http 会被转成 https
* @param url
* @param path
* @param platform
* @return
*/
public ResponseEntity<byte[]> getMdImageByUrl(String url, String platform) {
if (url.contains("md/get/url")) {
MSException.throwException(Translator.get("invalid_parameter"));
}
if (StringUtils.isNotBlank(platform)) {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setProjectId(SessionUtils.getCurrentProjectId());
issuesRequest.setWorkspaceId(SessionUtils.getCurrentWorkspaceId());
return IssueFactory.createPlatform(platform, issuesRequest)
.proxyForGet(url, byte[].class);
}
return restTemplate.exchange(url, HttpMethod.GET, null, byte[].class);
public ResponseEntity<byte[]> getMdImageByPath(String path, String platform, String workspaceId) {
IssuesRequest issuesRequest = new IssuesRequest();
issuesRequest.setWorkspaceId(workspaceId);
return IssueFactory.createPlatform(platform, issuesRequest)
.proxyForGet(path, byte[].class);
}
}

View File

@ -38,6 +38,8 @@ import org.springframework.util.MultiValueMap;
import java.io.File;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
@ -70,6 +72,8 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
protected AttachmentService attachmentService;
protected AttachmentModuleRelationMapper attachmentModuleRelationMapper;
public static final String PROXY_PATH = "/resource/md/get/path?platform=%s&workspaceId=%s&path=%s";
public String getKey() {
return key;
}
@ -101,6 +105,10 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
this.attachmentModuleRelationMapper = CommonBeanFactory.getBean(AttachmentModuleRelationMapper.class);
}
protected String getProxyPath(String path) {
return String.format(PROXY_PATH, this.key, this.workspaceId, URLEncoder.encode(path, StandardCharsets.UTF_8));
}
protected String getPlatformConfig(String platform) {
IntegrationRequest request = new IntegrationRequest();
if (StringUtils.isBlank(workspaceId)) {
@ -355,7 +363,7 @@ public abstract class AbstractIssuePlatform implements IssuesPlatform {
while (matcher.find()) {
try {
String path = matcher.group(2);
if (!path.contains("/resource/md/get/url")) {
if (!path.contains("/resource/md/get/url") && !path.contains("/resource/md/get/path")) {
if (path.contains("/resource/md/get/")) { // 兼容旧数据
String name = path.substring(path.indexOf("/resource/md/get/") + 17);
files.add(new File(FileUtils.MD_IMAGE_DIR + "/" + name));

View File

@ -111,10 +111,10 @@ public interface IssuesPlatform {
/**
* Get请求的代理
* @param url
* @param path
* @return
*/
ResponseEntity proxyForGet(String url, Class responseEntityClazz);
ResponseEntity proxyForGet(String path, Class responseEntityClazz);
/**
* 同步MS缺陷附件到第三方平台

View File

@ -30,9 +30,9 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@ -126,10 +126,13 @@ public class JiraPlatform extends AbstractIssuePlatform {
// 解析标签内容
String name = getHyperLinkPathForImg("\\!\\[(.*?)\\]", StringEscapeUtils.unescapeJava(splitStr));
String path = getHyperLinkPathForImg("\\|(.*?)\\)", splitStr);
path = "/resource/md/get/url?platform=Jira&url=" + URLEncoder.encode(path, StandardCharsets.UTF_8);
// 解析标签内容为图片超链接格式进行替换
description = description.replace(splitStr, "\n\n![" + name + "](" + path + ")");
try {
path = getProxyPath(new URI(path).getPath());
// 解析标签内容为图片超链接格式进行替换
description = description.replace(splitStr, "\n\n![" + name + "](" + path + ")");
} catch (URISyntaxException e) {
LogUtil.error(e);
}
}
description = description.replace(splitStr, StringEscapeUtils.unescapeJava(splitStr.replace("MS附件", "")));
}
@ -154,14 +157,17 @@ public class JiraPlatform extends AbstractIssuePlatform {
JSONObject attachment = attachments.getJSONObject(i);
String filename = attachment.getString("filename");
String content = attachment.getString("content");
content = "/resource/md/get/url?platform=Jira&url=" + URLEncoder.encode(content, StandardCharsets.UTF_8);
if (StringUtils.contains(attachment.getString("mimeType"), "image")) {
String contentUrl = "![" + filename + "](" + content + ")";
fileContentMap.put(filename, contentUrl);
} else {
String contentUrl = "附件[" + filename + "]下载地址:" + content;
fileContentMap.put(filename, contentUrl);
try {
content = getProxyPath(new URI(content).getPath());
if (StringUtils.contains(attachment.get("mimeType").toString(), "image")) {
String contentUrl = "![" + filename + "](" + content + ")";
fileContentMap.put(filename, contentUrl);
} else {
String contentUrl = "附件[" + filename + "]下载地址:" + content;
fileContentMap.put(filename, contentUrl);
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}
@ -851,8 +857,8 @@ public class JiraPlatform extends AbstractIssuePlatform {
return false;
}
public ResponseEntity proxyForGet(String url, Class responseEntityClazz) {
return jiraClientV2.proxyForGet(url, responseEntityClazz);
public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
return jiraClientV2.proxyForGet(path, responseEntityClazz);
}
@Override

View File

@ -453,7 +453,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
while (matcher.find()) {
// get file name
String originSubUrl = matcher.group(1);
if (originSubUrl.contains("/url?url=")) {
if (originSubUrl.contains("/url?url=") || originSubUrl.contains("/path?")) {
String path = URLDecoder.decode(originSubUrl, StandardCharsets.UTF_8);
String fileName;
if (path.indexOf("fileID") > 0) {
@ -525,11 +525,18 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
}
} else {
name = name.replaceAll("&amp;", "&");
path = zentaoClient.getBaseUrl() + path.replaceAll("&amp;", "&");
path = path.replaceAll("&amp;", "&");
}
// 专业版格式有差异解析完会出现两个 /pro去掉一个
path.replace("/pro/pro", "/pro");
path = "/resource/md/get/url?url=" + URLEncoder.encode(path, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
for (String item : path.split("&")) {
// 去掉多余的参数
if (!StringUtils.containsAny(item, "platform", "workspaceId")) {
stringBuilder.append(item);
stringBuilder.append("&");
}
}
path = getProxyPath(stringBuilder.toString());
// 图片与描述信息之间需换行否则无法预览图片
result = "\n\n![" + name + "](" + path + ")";
}
@ -637,4 +644,9 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
}
return platformStatusDTOS;
}
@Override
public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
return zentaoClient.proxyForGet(path, responseEntityClazz);
}
}

View File

@ -6,6 +6,7 @@ import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.EncryptUtils;
import io.metersphere.commons.utils.EnvProxySelector;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
@ -17,6 +18,8 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
@ -70,4 +73,16 @@ public abstract class BaseClient {
protected Object getResultForObject(Class clazz,ResponseEntity<String> response) {
return JSONObject.parseObject(getResult(response), clazz);
}
public void validateProxyUrl(String url, String ...path) {
try {
if (!StringUtils.containsAny(new URI(url).getPath(), path)) {
// 只允许访问图片
MSException.throwException("illegal path");
}
} catch (URISyntaxException e) {
LogUtil.error(e);
MSException.throwException("illegal path");
}
}
}

View File

@ -294,8 +294,10 @@ public abstract class JiraAbstractClient extends BaseClient {
}
}
public ResponseEntity proxyForGet(String url, Class responseEntityClazz) {
LogUtil.info("jira proxyForGet: " + url);
public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
LogUtil.info("jira proxyForGet: " + path);
String url = this.ENDPOINT + path;
validateProxyUrl(url, "/secure/attachment", "/attachment/content");
return restTemplate.exchange(url, HttpMethod.GET, getAuthHttpEntity(), responseEntityClazz);
}
}

View File

@ -15,6 +15,8 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
public abstract class ZentaoClient extends BaseClient {
@ -248,4 +250,19 @@ public abstract class ZentaoClient extends BaseClient {
null, byte[].class, fileId, sessionId);
return response.getBody();
}
public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
LogUtil.info("zentao proxyForGet: " + path);
String url = this.ENDPOINT + path;
try {
if (!StringUtils.containsAny(new URI(url).getPath(), "/index.php", "/file-read-")) {
// 只允许访问图片
MSException.throwException("illegal path");
}
} catch (URISyntaxException e) {
LogUtil.error(e);
MSException.throwException("illegal path");
}
return restTemplate.exchange(url, HttpMethod.GET, null, responseEntityClazz);
}
}

View File

@ -15,7 +15,7 @@ public class ZentaoGetClient extends ZentaoClient {
private static final String STORY_GET="&module=story&methodName=getProductStories&params=productID={key}&t=json&zentaosid=";
private static final String USER_GET="&module=user&methodName=getList&t=json&zentaosid=";
private static final String BUILDS_GET="&module=build&methodName=getProductBuildPairs&productID={0}&zentaosid={1}";
private static final String FILE_UPLOAD="&module=file&methodName=saveUpload&params=objectType={1},objectID={2},zentaosid={3}";
private static final String FILE_UPLOAD="&module=file&methodName=saveUpload&params=objectType={1},objectID={2}&t=json&zentaosid={3}";
private static final String FILE_DELETE="/?m=file&f=delete&t=json&fileID={1}&confirm=yes&zentaosid={2}";
private static final String FILE_DOWNLOAD="/?m=file&f=download&t=json&fileID={1}&mouse=click&zentaosid={2}";
private static final String CREATE_META_DATA="?m=bug&f=create&productID={0}&t=json&zentaosid={1}";