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); return resourceService.getMdImage(fileName);
} }
@GetMapping(value = "/md/get/url") @GetMapping(value = "/md/get/path")
public ResponseEntity<byte[]> getFileByUrl(@RequestParam ("url") String url, @RequestParam (value = "platform", required = false) String platform) { public ResponseEntity<byte[]> getFileByPath(@RequestParam ("path") String path,
return resourceService.getMdImageByUrl(url, platform); @RequestParam (value = "platform") String platform,
@RequestParam (value = "workspaceId") String workspaceId) {
return resourceService.getMdImageByPath(path, platform, workspaceId);
} }
@GetMapping(value = "/ui/get") @GetMapping(value = "/ui/get")

View File

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

View File

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

View File

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

View File

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

View File

@ -453,7 +453,7 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
while (matcher.find()) { while (matcher.find()) {
// get file name // get file name
String originSubUrl = matcher.group(1); 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 path = URLDecoder.decode(originSubUrl, StandardCharsets.UTF_8);
String fileName; String fileName;
if (path.indexOf("fileID") > 0) { if (path.indexOf("fileID") > 0) {
@ -525,11 +525,18 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
} }
} else { } else {
name = name.replaceAll("&amp;", "&"); name = name.replaceAll("&amp;", "&");
path = zentaoClient.getBaseUrl() + path.replaceAll("&amp;", "&"); path = path.replaceAll("&amp;", "&");
} }
// 专业版格式有差异解析完会出现两个 /pro去掉一个 StringBuilder stringBuilder = new StringBuilder();
path.replace("/pro/pro", "/pro"); for (String item : path.split("&")) {
path = "/resource/md/get/url?url=" + URLEncoder.encode(path, StandardCharsets.UTF_8); // 去掉多余的参数
if (!StringUtils.containsAny(item, "platform", "workspaceId")) {
stringBuilder.append(item);
stringBuilder.append("&");
}
}
path = getProxyPath(stringBuilder.toString());
// 图片与描述信息之间需换行否则无法预览图片 // 图片与描述信息之间需换行否则无法预览图片
result = "\n\n![" + name + "](" + path + ")"; result = "\n\n![" + name + "](" + path + ")";
} }
@ -637,4 +644,9 @@ public class ZentaoPlatform extends AbstractIssuePlatform {
} }
return platformStatusDTOS; 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.EncryptUtils;
import io.metersphere.commons.utils.EnvProxySelector; import io.metersphere.commons.utils.EnvProxySelector;
import io.metersphere.commons.utils.LogUtil; 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.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
@ -17,6 +18,8 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays; import java.util.Arrays;
@ -70,4 +73,16 @@ public abstract class BaseClient {
protected Object getResultForObject(Class clazz,ResponseEntity<String> response) { protected Object getResultForObject(Class clazz,ResponseEntity<String> response) {
return JSONObject.parseObject(getResult(response), clazz); 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) { public ResponseEntity proxyForGet(String path, Class responseEntityClazz) {
LogUtil.info("jira proxyForGet: " + url); LogUtil.info("jira proxyForGet: " + path);
String url = this.ENDPOINT + path;
validateProxyUrl(url, "/secure/attachment", "/attachment/content");
return restTemplate.exchange(url, HttpMethod.GET, getAuthHttpEntity(), responseEntityClazz); 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 org.springframework.util.MultiValueMap;
import java.io.File; import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map; import java.util.Map;
public abstract class ZentaoClient extends BaseClient { public abstract class ZentaoClient extends BaseClient {
@ -248,4 +250,19 @@ public abstract class ZentaoClient extends BaseClient {
null, byte[].class, fileId, sessionId); null, byte[].class, fileId, sessionId);
return response.getBody(); 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 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 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 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_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 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}"; private static final String CREATE_META_DATA="?m=bug&f=create&productID={0}&t=json&zentaosid={1}";