feat(系统设置): 增加SSL证书认证

This commit is contained in:
fit2-zhao 2021-05-08 20:29:47 +08:00 committed by fit2-zhao
parent c8b3d4573c
commit 02e4299454
23 changed files with 1492 additions and 14 deletions

View File

@ -2,7 +2,10 @@ package io.metersphere.api.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.api.dto.ApiTestEnvironmentDTO;
import io.metersphere.api.dto.ssl.KeyStoreEntry;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.api.service.CommandService;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
@ -12,6 +15,7 @@ import io.metersphere.service.CheckPermissionService;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
@ -25,6 +29,8 @@ public class ApiTestEnvironmentController {
ApiTestEnvironmentService apiTestEnvironmentService;
@Resource
private CheckPermissionService checkPermissionService;
@Resource
private CommandService commandService;
@GetMapping("/list/{projectId}")
public List<ApiTestEnvironmentWithBLOBs> list(@PathVariable String projectId) {
@ -34,6 +40,7 @@ public class ApiTestEnvironmentController {
/**
* 查询指定项目和指定名称的环境
*
* @param goPage
* @param pageSize
* @param environmentRequest
@ -54,16 +61,23 @@ public class ApiTestEnvironmentController {
return apiTestEnvironmentService.get(id);
}
@PostMapping(value = "/get/entry")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER,}, logical = Logical.OR)
public List<KeyStoreEntry> getEntry(@RequestPart("request") String password, @RequestPart(value = "file") MultipartFile sslFiles) {
return commandService.get(password, sslFiles);
}
@PostMapping("/add")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER,}, logical = Logical.OR)
public String add(@RequestBody ApiTestEnvironmentWithBLOBs apiTestEnvironmentWithBLOBs) {
return apiTestEnvironmentService.add(apiTestEnvironmentWithBLOBs);
public String create(@RequestPart("request") ApiTestEnvironmentDTO apiTestEnvironmentWithBLOBs, @RequestPart(value = "files") List<MultipartFile> sslFiles) {
return apiTestEnvironmentService.add(apiTestEnvironmentWithBLOBs, sslFiles);
}
@PostMapping(value = "/update")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.TEST_USER,}, logical = Logical.OR)
public void update(@RequestBody ApiTestEnvironmentWithBLOBs apiTestEnvironment) {
apiTestEnvironmentService.update(apiTestEnvironment);
public void update(@RequestPart("request") ApiTestEnvironmentDTO apiTestEnvironment, @RequestPart(value = "files") List<MultipartFile> sslFiles) {
apiTestEnvironmentService.update(apiTestEnvironment, sslFiles);
}
@GetMapping("/delete/{id}")

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import lombok.Data;
import java.util.List;
@Data
public class ApiTestEnvironmentDTO extends ApiTestEnvironmentWithBLOBs {
private List<String> uploadIds;
}

View File

@ -2,10 +2,12 @@ package io.metersphere.api.dto.definition.request;
import io.metersphere.api.dto.definition.request.variable.ScenarioVariable;
import io.metersphere.api.dto.scenario.environment.EnvironmentConfig;
import io.metersphere.api.dto.ssl.MsKeyStore;
import io.metersphere.commons.utils.ScriptEngineUtils;
import lombok.Data;
import org.apache.jmeter.config.Arguments;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -15,6 +17,10 @@ public class ParameterConfig {
* 环境配置
*/
private Map<String, EnvironmentConfig> config;
/**
* 缓存同一批请求的认证信息
*/
private Map<String, MsKeyStore> keyStoreMap = new HashMap<>();
/**
* 公共场景参数
*/

View File

@ -14,8 +14,11 @@ import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.HttpConfig;
import io.metersphere.api.dto.scenario.HttpConfigCondition;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.ssl.KeyStoreFile;
import io.metersphere.api.dto.ssl.MsKeyStore;
import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiTestCaseService;
import io.metersphere.api.service.CommandService;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import io.metersphere.base.domain.ApiTestCaseWithBLOBs;
@ -26,6 +29,7 @@ import io.metersphere.commons.constants.MsTestElementConstants;
import io.metersphere.commons.constants.RunModeConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.ScriptEngineUtils;
import io.metersphere.track.service.TestPlanApiCaseService;
@ -34,6 +38,7 @@ import lombok.EqualsAndHashCode;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.KeystoreConfig;
import org.apache.jmeter.protocol.http.control.Header;
import org.apache.jmeter.protocol.http.control.HeaderManager;
import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
@ -110,6 +115,9 @@ public class MsHTTPSamplerProxy extends MsTestElement {
@JSONField(ordinal = 37)
private Boolean isRefEnvironment;
@JSONField(ordinal = 38)
private String alias;
private void setRefElement() {
try {
ApiDefinitionService apiDefinitionService = CommonBeanFactory.getBean(ApiDefinitionService.class);
@ -346,14 +354,63 @@ public class MsHTTPSamplerProxy extends MsTestElement {
MsDNSCacheManager.addEnvironmentVariables(httpSamplerTree, this.getName(), config.getConfig().get(this.getProjectId()));
MsDNSCacheManager.addEnvironmentDNS(httpSamplerTree, this.getName(), config.getConfig().get(this.getProjectId()));
}
if (this.authManager != null) {
this.authManager.setAuth(tree, this.authManager, sampler);
}
// 加载SSL认证
if (config != null && config.isEffective(this.getProjectId()) && config.getConfig().get(this.getProjectId()).getSslConfig() != null) {
if (CollectionUtils.isNotEmpty(config.getConfig().get(this.getProjectId()).getSslConfig().getFiles())) {
MsKeyStore msKeyStore = config.getKeyStoreMap().get(this.getProjectId());
CommandService commandService = CommonBeanFactory.getBean(CommandService.class);
if (msKeyStore == null) {
msKeyStore = new MsKeyStore();
if (config.getConfig().get(this.getProjectId()).getSslConfig().getFiles().size() == 1) {
// 加载认证文件
KeyStoreFile file = config.getConfig().get(this.getProjectId()).getSslConfig().getFiles().get(0);
msKeyStore.setPath(FileUtils.BODY_FILE_DIR + "/ssl/" + file.getId() + "_" + file.getName());
msKeyStore.setPassword(file.getPassword());
} else {
// 合并多个认证文件
msKeyStore.setPath(FileUtils.BODY_FILE_DIR + "/ssl/tmp." + this.getId() + ".jks");
msKeyStore.setPassword("ms123...");
commandService.mergeKeyStore(msKeyStore.getPath(), config.getConfig().get(this.getProjectId()).getSslConfig());
}
}
if (StringUtils.isEmpty(this.alias)) {
this.alias = config.getConfig().get(this.getProjectId()).getSslConfig().getDefaultAlias();
}
if (StringUtils.isNotEmpty(this.alias)) {
String aliasVar = UUID.randomUUID().toString();
this.addArguments(httpSamplerTree, aliasVar, this.alias.trim());
// 校验 keystore
commandService.checkKeyStore(msKeyStore.getPassword(), msKeyStore.getPath());
KeystoreConfig keystoreConfig = new KeystoreConfig();
keystoreConfig.setEnabled(true);
keystoreConfig.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() + "-KeyStore" : "KeyStore");
keystoreConfig.setProperty(TestElement.TEST_CLASS, KeystoreConfig.class.getName());
keystoreConfig.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI"));
keystoreConfig.setProperty("clientCertAliasVarName", aliasVar);
keystoreConfig.setProperty("endIndex", -1);
keystoreConfig.setProperty("preload", true);
keystoreConfig.setProperty("startIndex", 0);
keystoreConfig.setProperty("MS-KEYSTORE-FILE-PATH", msKeyStore.getPath());
keystoreConfig.setProperty("MS-KEYSTORE-FILE-PASSWORD", msKeyStore.getPassword());
httpSamplerTree.add(keystoreConfig);
config.getKeyStoreMap().put(this.getProjectId(), new MsKeyStore(msKeyStore.getPath(), msKeyStore.getPassword()));
}
}
}
if (CollectionUtils.isNotEmpty(hashTree)) {
for (MsTestElement el : hashTree) {
el.toHashTree(httpSamplerTree, el.getHashTree(), config);
}
}
if (this.authManager != null) {
this.authManager.setAuth(tree, this.authManager, sampler);
}
}
// 兼容旧数据
@ -586,6 +643,16 @@ public class MsHTTPSamplerProxy extends MsTestElement {
return null;
}
private void addArguments(HashTree tree, String key, String value) {
Arguments arguments = new Arguments();
arguments.setEnabled(true);
arguments.setName(StringUtils.isNotEmpty(this.getName()) ? this.getName() + "-KeyStoreAlias" : "KeyStoreAlias");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
arguments.addArgument(key, value, "=");
tree.add(arguments);
}
private boolean isRest() {
return this.getRest().stream().filter(KeyValue::isEnable).filter(KeyValue::isValid).toArray().length > 0;
}

View File

@ -3,6 +3,7 @@ package io.metersphere.api.dto.scenario.environment;
import io.metersphere.api.dto.scenario.DatabaseConfig;
import io.metersphere.api.dto.scenario.HttpConfig;
import io.metersphere.api.dto.scenario.TCPConfig;
import io.metersphere.api.dto.ssl.KeyStoreConfig;
import lombok.Data;
import java.util.ArrayList;
@ -14,11 +15,13 @@ public class EnvironmentConfig {
private HttpConfig httpConfig;
private List<DatabaseConfig> databaseConfigs;
private TCPConfig tcpConfig;
private KeyStoreConfig sslConfig;
public EnvironmentConfig() {
this.commonConfig = new CommonConfig();
this.httpConfig = new HttpConfig();
this.databaseConfigs = new ArrayList<>();
this.tcpConfig = new TCPConfig();
this.sslConfig = new KeyStoreConfig();
}
}

View File

@ -0,0 +1,28 @@
package io.metersphere.api.dto.ssl;
import lombok.Data;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class KeyStoreConfig {
private List<KeyStoreEntry> entrys;
private List<KeyStoreFile> files;
public String getDefaultAlias() {
if (CollectionUtils.isNotEmpty(entrys)) {
List<KeyStoreEntry> entryList = this.entrys.stream().filter(KeyStoreEntry::isDefault).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(entryList)) {
if (StringUtils.isNotEmpty(entryList.get(0).getNewAsName())) {
return entryList.get(0).getNewAsName();
} else {
return entryList.get(0).getOriginalAsName();
}
}
}
return null;
}
}

View File

@ -0,0 +1,15 @@
package io.metersphere.api.dto.ssl;
import lombok.Data;
@Data
public class KeyStoreEntry {
private String id;
private String originalAsName;
private String newAsName;
private String type;
private String password;
private String sourceName;
private String sourceId;
private boolean isDefault;
}

View File

@ -0,0 +1,15 @@
package io.metersphere.api.dto.ssl;
import io.metersphere.api.dto.scenario.request.BodyFile;
import lombok.Data;
@Data
public class KeyStoreFile {
private String id;
private String name;
private String type;
private String updateTime;
private String password;
private BodyFile file;
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.ssl;
import lombok.Data;
@Data
public class MsKeyStore {
private String id;
private String password;
private String path;
public MsKeyStore() {
}
public MsKeyStore(String path, String password) {
this.password = password;
this.path = path;
}
}

View File

@ -2,11 +2,13 @@ package io.metersphere.api.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.ApiTestEnvironmentDTO;
import io.metersphere.api.dto.mockconfig.MockConfigStaticData;
import io.metersphere.base.domain.ApiTestEnvironmentExample;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.base.mapper.ApiTestEnvironmentMapper;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.controller.request.EnvironmentRequest;
import io.metersphere.dto.BaseSystemConfigDTO;
@ -16,6 +18,7 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.*;
@ -72,6 +75,19 @@ public class ApiTestEnvironmentService {
return apiTestEnvironmentWithBLOBs.getId();
}
public String add(ApiTestEnvironmentDTO request, List<MultipartFile> sslFiles) {
request.setId(UUID.randomUUID().toString());
checkEnvironmentExist(request);
FileUtils.createFiles(request.getUploadIds(), sslFiles, FileUtils.BODY_FILE_DIR + "/ssl");
apiTestEnvironmentMapper.insert(request);
return request.getId();
}
public void update(ApiTestEnvironmentDTO apiTestEnvironment,List<MultipartFile> sslFiles) {
checkEnvironmentExist(apiTestEnvironment);
FileUtils.createFiles(apiTestEnvironment.getUploadIds(), sslFiles, FileUtils.BODY_FILE_DIR + "/ssl");
apiTestEnvironmentMapper.updateByPrimaryKeyWithBLOBs(apiTestEnvironment);
}
private void checkEnvironmentExist(ApiTestEnvironmentWithBLOBs environment) {
if (environment.getName() != null) {
ApiTestEnvironmentExample example = new ApiTestEnvironmentExample();

View File

@ -0,0 +1,213 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.ssl.KeyStoreConfig;
import io.metersphere.api.dto.ssl.KeyStoreEntry;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.FileUtils;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.jorphan.exec.SystemCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.*;
@Service
public class CommandService {
public List<KeyStoreEntry> get(String password, MultipartFile file) {
try {
String path = FileUtils.createFile(file);
// 执行验证指令
if (StringUtils.isNotEmpty(password)) {
password = JSON.parseObject(password, String.class);
}
String keytoolArgs[] = {"keytool", "-rfc", "-list", "-keystore", path, "-storepass", password};
Process p = new ProcessBuilder(keytoolArgs).start();
List<KeyStoreEntry> dtoList = new LinkedList<>();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line = null;
KeyStoreEntry dto = null;
while ((line = br.readLine()) != null) {
if (line.contains("keystore password was incorrect")) {
MSException.throwException("认证密码错误,请重新输入密码");
}
if (line.startsWith("别名")) {
if (dto != null) {
dtoList.add(dto);
}
dto = new KeyStoreEntry();
dto.setOriginalAsName(line.split(":")[1]);
}
if (line.startsWith("条目类型")) {
dto.setType(line.split(":")[1]);
}
}
if (dto != null) {
dtoList.add(dto);
}
}
FileUtils.deleteFile(path);
return dtoList;
} catch (Exception e) {
LogUtil.error(e.getMessage());
MSException.throwException(e.getMessage());
}
return null;
}
public void createKeyStore(String alias, String path) {
try {
File f = new File(path);
if (f.exists()) {
f.delete();
}
List<String> arguments = new ArrayList();
arguments.add("keytool");
arguments.add("-genkeypair");
arguments.add("-alias");
arguments.add(alias);
arguments.add("-dname");
arguments.add("CN=localhost,OU=cn,O=cn,L=cn,ST=cn,C=cn");
arguments.add("-keyalg");
arguments.add("RSA");
arguments.add("-keystore");
arguments.add(f.getName());
arguments.add("-storepass");
arguments.add("ms123...");
arguments.add("-keypass");
arguments.add("ms123...");
arguments.add("-validity");
arguments.add(Integer.toString(1024));
SystemCommand nativeCommand = new SystemCommand(f.getParentFile(), (Map) null);
nativeCommand.run(arguments);
} catch (Exception e) {
MSException.throwException(e.getMessage());
}
}
public void mergeKeyStore(String newKeyStore, KeyStoreConfig sslConfig) {
try {
// 创建零时keyStore
this.createKeyStore("ms-run", newKeyStore);
// 修改别名
Map<String, List<KeyStoreEntry>> entryMap = new HashMap<>();
if (sslConfig != null && CollectionUtils.isNotEmpty(sslConfig.getEntrys())) {
sslConfig.getEntrys().forEach(item -> {
if (entryMap.containsKey(item.getSourceId())) {
entryMap.get(item.getSourceId()).add(item);
} else {
List<KeyStoreEntry> list = new ArrayList<>();
list.add(item);
entryMap.put(item.getSourceId(), list);
}
});
}
if (sslConfig != null && CollectionUtils.isNotEmpty(sslConfig.getFiles())) {
sslConfig.getFiles().forEach(item -> {
List<KeyStoreEntry> entries = entryMap.get(item.getId());
if (CollectionUtils.isNotEmpty(entries)) {
entries.forEach(entry -> {
File srcFile = new File(FileUtils.BODY_FILE_DIR + "/ssl/" + item.getId() + "_" + item.getName());
try {
// 开始合并
File destFile = new File(newKeyStore);
List<String> arguments = new ArrayList();
arguments.add("keytool");
arguments.add("-genkeypair");
arguments.add("-importkeystore");
arguments.add("-srckeystore");
arguments.add(srcFile.getName());
arguments.add("-srcstorepass");
arguments.add(item.getPassword());
arguments.add("-srcalias");
arguments.add(entry.getOriginalAsName().trim());
arguments.add("-srckeypass");
arguments.add(entry.getPassword());
arguments.add("-destkeystore");
arguments.add(destFile.getName());
arguments.add("-deststorepass");
arguments.add("ms123...");
arguments.add("-destalias");
arguments.add(StringUtils.isNotEmpty(entry.getNewAsName()) ? entry.getNewAsName().trim() : entry.getOriginalAsName().trim());
arguments.add("-destkeypass");
arguments.add("ms123...");
SystemCommand nativeCommand = new SystemCommand(destFile.getParentFile(), (Map) null);
int exitVal = nativeCommand.run(arguments);
if (exitVal > 0) {
MSException.throwException("合并条目:【" + entry.getOriginalAsName() + " 】失败");
}
} catch (Exception e) {
LogUtil.error(e.getMessage());
}
});
}
});
}
} catch (Exception e) {
LogUtil.error(e.getMessage());
MSException.throwException(e.getMessage());
}
}
public void keypasswd(File file, String storepass, String alias, String keypass) {
// 统一密码
try {
List<String> arguments = new ArrayList();
arguments.add("keytool");
arguments.add("-genkeypair");
arguments.add("-keypasswd");
arguments.add("-keystore");
arguments.add(file.getName());
arguments.add("-storepass");
arguments.add(storepass);
arguments.add("-alias");
arguments.add(alias.trim());
arguments.add("-keypass");
arguments.add(keypass);
arguments.add("-new");
arguments.add("ms123...");
SystemCommand nativeCommand = new SystemCommand(file.getParentFile(), (Map) null);
int exitVal = nativeCommand.run(arguments);
if (exitVal > 0) {
MSException.throwException("别名:【" + alias + " 】密码修改失败");
}
} catch (Exception e) {
MSException.throwException(e.getMessage());
}
}
public boolean checkKeyStore(String password, String path) {
try {
String keytoolArgs[] = {"keytool", "-rfc", "-list", "-keystore", path, "-storepass", password};
Process p = new ProcessBuilder(keytoolArgs).start();
try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line = null;
KeyStoreEntry dto = null;
while ((line = br.readLine()) != null) {
if (line.contains("keystore password was incorrect")) {
MSException.throwException("认证密码错误,请重新输入密码");
}
if (line.contains("Exception")) {
MSException.throwException("认证文件加载失败,请检查认证文件");
}
}
}
return true;
} catch (Exception e) {
LogUtil.error(e.getMessage());
MSException.throwException(e.getMessage());
return false;
}
}
}

View File

@ -3,27 +3,37 @@ package io.metersphere.commons.utils;
import io.metersphere.commons.exception.MSException;
import io.metersphere.i18n.Translator;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.util.FileUtil;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.List;
import java.util.UUID;
public class FileUtils {
public static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
public static void createBodyFiles(List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
private static void create(List<String> bodyUploadIds, List<MultipartFile> bodyFiles, String path) {
String filePath = BODY_FILE_DIR;
if (StringUtils.isNotEmpty(path)) {
filePath = path;
}
if (CollectionUtils.isNotEmpty(bodyUploadIds) && CollectionUtils.isNotEmpty(bodyFiles)) {
File testDir = new File(BODY_FILE_DIR);
File testDir = new File(filePath);
if (!testDir.exists()) {
testDir.mkdirs();
}
for (int i = 0; i < bodyUploadIds.size(); i++) {
MultipartFile item = bodyFiles.get(i);
File file = new File(BODY_FILE_DIR + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
File file = new File(filePath + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
try (InputStream in = item.getInputStream(); OutputStream out = new FileOutputStream(file)) {
file.createNewFile();
FileUtil.copyStream(in, out);
final int MAX = 4096;
byte[] buf = new byte[MAX];
for (int bytesRead = in.read(buf, 0, MAX); bytesRead != -1; bytesRead = in.read(buf, 0, MAX)) {
out.write(buf, 0, bytesRead);
}
} catch (IOException e) {
LogUtil.error(e);
MSException.throwException(Translator.get("upload_fail"));
@ -32,6 +42,33 @@ public class FileUtils {
}
}
public static void createBodyFiles(List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
FileUtils.create(bodyUploadIds, bodyFiles, null);
}
public static void createFiles(List<String> bodyUploadIds, List<MultipartFile> bodyFiles, String path) {
FileUtils.create(bodyUploadIds, bodyFiles, path);
}
public static String createFile(MultipartFile bodyFile) {
File file = new File("/opt/metersphere/data/body/tmp" + UUID.randomUUID().toString() + "_" + bodyFile.getOriginalFilename());
try (InputStream in = bodyFile.getInputStream(); OutputStream out = new FileOutputStream(file)) {
file.createNewFile();
FileUtil.copyStream(in, out);
} catch (IOException e) {
LogUtil.error(e);
MSException.throwException(Translator.get("upload_fail"));
}
return file.getPath();
}
public static void delFile(String path) {
File file = new File(path);
if (file.exists()) {
file.delete();
}
}
public static String uploadFile(MultipartFile uploadFile, String path, String name) {
if (uploadFile == null) {
return null;
@ -53,7 +90,7 @@ public class FileUtils {
}
public static String uploadFile(MultipartFile uploadFile, String path) {
return uploadFile(uploadFile, path, uploadFile.getOriginalFilename());
return uploadFile(uploadFile, path, uploadFile.getOriginalFilename());
}
public static void deleteFile(String path) {

View File

@ -0,0 +1,174 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.apache.jmeter.config;
import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.TestElementMetadata;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.SSLManager;
import org.apache.jorphan.util.JMeterStopTestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Configure Keystore
*/
@TestElementMetadata(labelResource = "displayName")
public class KeystoreConfig extends ConfigTestElement implements TestBean, TestStateListener {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(KeystoreConfig.class);
private static final String KEY_STORE_START_INDEX = "https.keyStoreStartIndex"; // $NON-NLS-1$
private static final String KEY_STORE_END_INDEX = "https.keyStoreEndIndex"; // $NON-NLS-1$
private String startIndex;
private String endIndex;
private String preload;
private String clientCertAliasVarName;
public KeystoreConfig() {
super();
}
@Override
public void testEnded() {
testEnded(null);
}
@Override
public void testEnded(String host) {
log.info("Destroying Keystore");
SSLManager.getInstance().destroyKeystore();
}
@Override
public void testStarted() {
testStarted(null);
}
@Override
public void testStarted(String host) {
String reuseSSLContext = JMeterUtils.getProperty("https.use.cached.ssl.context");
if (StringUtils.isEmpty(reuseSSLContext) || "true".equals(reuseSSLContext)) {
log.warn("https.use.cached.ssl.context property must be set to false to ensure Multiple Certificates are used");
}
int startIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_START_INDEX, 0);
int endIndexAsInt = JMeterUtils.getPropDefault(KEY_STORE_END_INDEX, -1);
if (!StringUtils.isEmpty(this.startIndex)) {
try {
startIndexAsInt = Integer.parseInt(this.startIndex);
} catch (NumberFormatException e) {
log.warn("Failed parsing startIndex: {}, will default to: {}, error message: {}", this.startIndex,
startIndexAsInt, e, e);
}
}
if (!StringUtils.isEmpty(this.endIndex)) {
try {
endIndexAsInt = Integer.parseInt(this.endIndex);
} catch (NumberFormatException e) {
log.warn("Failed parsing endIndex: {}, will default to: {}, error message: {}", this.endIndex,
endIndexAsInt, e, e);
}
}
if (endIndexAsInt != -1 && startIndexAsInt > endIndexAsInt) {
throw new JMeterStopTestException("Keystore Config error : Alias start index must be lower than Alias end index");
}
log.info(
"Configuring Keystore with (preload: '{}', startIndex: {}, endIndex: {}, clientCertAliasVarName: '{}')",
preload, startIndexAsInt, endIndexAsInt, clientCertAliasVarName);
// 加载认证文件
String path = this.getPropertyAsString("MS-KEYSTORE-FILE-PATH");
String password = this.getPropertyAsString("MS-KEYSTORE-FILE-PASSWORD");
InputStream in = null;
try {
in = new FileInputStream(new File(path));
} catch (IOException e) {
log.error(e.getMessage());
}
SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload),
startIndexAsInt,
endIndexAsInt,
clientCertAliasVarName, in, password);
}
/**
* @return the endIndex
*/
public String getEndIndex() {
return endIndex;
}
/**
* @param endIndex the endIndex to set
*/
public void setEndIndex(String endIndex) {
this.endIndex = endIndex;
}
/**
* @return the startIndex
*/
public String getStartIndex() {
return startIndex;
}
/**
* @param startIndex the startIndex to set
*/
public void setStartIndex(String startIndex) {
this.startIndex = startIndex;
}
/**
* @return the preload
*/
public String getPreload() {
return preload;
}
/**
* @param preload the preload to set
*/
public void setPreload(String preload) {
this.preload = preload;
}
/**
* @return the clientCertAliasVarName
*/
public String getClientCertAliasVarName() {
return clientCertAliasVarName;
}
/**
* @param clientCertAliasVarName the clientCertAliasVarName to set
*/
public void setClientCertAliasVarName(String clientCertAliasVarName) {
this.clientCertAliasVarName = clientCertAliasVarName;
}
}

View File

@ -0,0 +1,378 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.apache.jmeter.util;
import org.apache.commons.lang3.Validate;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.util.keystore.JmeterKeyStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.Locale;
/**
* The SSLManager handles the KeyStore information for JMeter. Basically, it
* handles all the logic for loading and initializing all the JSSE parameters
* and selecting the alias to authenticate against if it is available.
* SSLManager will try to automatically select the client certificate for you,
* but if it can't make a decision, it will pop open a dialog asking you for
* more information.
* <p>
* TODO? - N.B. does not currently allow the selection of a client certificate.
*
*/
public abstract class SSLManager {
private static final Logger log = LoggerFactory.getLogger(SSLManager.class);
private static final String SSL_TRUST_STORE = "javax.net.ssl.trustStore";// $NON-NLS-1$
private static final String KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; // $NON-NLS-1$ NOSONAR no hard coded password
public static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; // $NON-NLS-1$
private static final String JAVAX_NET_SSL_KEY_STORE_TYPE = "javax.net.ssl.keyStoreType"; // $NON-NLS-1$
private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$
/** Singleton instance of the manager */
private static SSLManager manager;
private static final boolean IS_SSL_SUPPORTED = true;
/** Cache the KeyStore instance */
private JmeterKeyStore keyStore;
/** Cache the TrustStore instance - null if no truststore name was provided */
private KeyStore trustStore = null;
// Have we yet tried to load the truststore?
private volatile boolean truststoreLoaded=false;
/** Have the password available */
protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD);
private int keystoreAliasStartIndex;
private int keystoreAliasEndIndex;
private String clientCertAliasVarName;
/**
* Resets the SSLManager so that we can create a new one with a new keystore
*/
public static synchronized void reset() {
SSLManager.manager = null;
}
public abstract void setContext(HttpURLConnection conn);
/**
* Default implementation of setting the Provider
*
* @param provider
* the provider to use
*/
protected void setProvider(Provider provider) {
if (null != provider) {
Security.addProvider(provider);
}
}
protected synchronized JmeterKeyStore getKeyStore() {
if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
try {
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
log.info("KeyStore created OK");
} catch (Exception e) {
this.keyStore = null;
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e);
}
try {
// The string 'NONE' is used for the keystore location when using PKCS11
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE
if ("NONE".equalsIgnoreCase(fileName)) {
retryLoadKeys(null, false);
log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount());
} else {
File initStore = new File(fileName);
if (fileName.length() > 0 && initStore.exists()) {
retryLoadKeys(initStore, true);
if (log.isInfoEnabled()) {
log.info("Total of {} aliases loaded OK from keystore {}",
keyStore.getAliasCount(), fileName);
}
} else {
log.warn("Keystore file not found, loading empty keystore");
this.defaultpw = ""; // Ensure not null
this.keyStore.load(null, "");
}
}
} catch (Exception e) {
log.error("Problem loading keystore: {}", e.getMessage(), e);
}
if (log.isDebugEnabled()) {
log.debug("JmeterKeyStore type: {}", this.keyStore.getClass());
}
}
return this.keyStore;
}
/**
* Opens and initializes the KeyStore. If the password for the KeyStore is
* not set, this method will prompt you to enter it. Unfortunately, there is
* no PasswordEntryField available from JOptionPane.
*
* @return the configured {@link JmeterKeyStore}
*/
protected synchronized JmeterKeyStore getKeyStore(InputStream is,String password) {
if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
try {
this.keyStore = JmeterKeyStore.getInstance(fileType, keystoreAliasStartIndex, keystoreAliasEndIndex, clientCertAliasVarName);
log.info("KeyStore created OK");
} catch (Exception e) {
this.keyStore = null;
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e);
}
try {
// The string 'NONE' is used for the keystore location when using PKCS11
// https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#JSSE
if ("NONE".equalsIgnoreCase(fileName)) {
retryLoadKeys(null, false);
log.info("Total of {} aliases loaded OK from PKCS11", keyStore.getAliasCount());
} else {
File initStore = new File(fileName);
if (fileName.length() > 0 && initStore.exists()) {
retryLoadKeys(initStore, true);
if (log.isInfoEnabled()) {
log.info("Total of {} aliases loaded OK from keystore {}",
keyStore.getAliasCount(), fileName);
}
} else {
log.warn("Keystore file not found, loading empty keystore");
this.defaultpw = ""; // Ensure not null
this.keyStore.load(is, password);
}
}
} catch (Exception e) {
log.error("Problem loading keystore: {}", e.getMessage(), e);
}
if (log.isDebugEnabled()) {
log.debug("JmeterKeyStore type: {}", this.keyStore.getClass());
}
}
return this.keyStore;
}
private void retryLoadKeys(File initStore, boolean allowEmptyPassword) throws NoSuchAlgorithmException,
CertificateException, IOException, KeyStoreException, UnrecoverableKeyException {
for (int i = 0; i < 3; i++) {
String password = getPassword();
if (!allowEmptyPassword) {
Validate.notNull(password, "Password for keystore must not be null");
}
try {
if (initStore == null) {
this.keyStore.load(null, password);
} else {
try (InputStream fis = new FileInputStream(initStore)) {
this.keyStore.load(fis, password);
}
}
return;
} catch (IOException e) {
log.debug("Could not load keystore. Wrong password for keystore?", e);
}
this.defaultpw = null;
}
}
/*
* The password can be defined as a property; this dialogue is provided to allow it
* to be entered at run-time.
*/
private String getPassword() {
String password = this.defaultpw;
if (null == password) {
final GuiPackage guiInstance = GuiPackage.getInstance();
// if (guiInstance != null) {
// JPanel panel = new JPanel(new MigLayout("fillx, wrap 2", "[][fill, grow]"));
// JLabel passwordLabel = new JLabel("Password: ");
// JPasswordField pwf = new JPasswordField(64);
// pwf.setEchoChar('*');
// passwordLabel.setLabelFor(pwf);
// panel.add(passwordLabel);
// panel.add(pwf);
// int choice = JOptionPane.showConfirmDialog(guiInstance.getMainFrame(), panel,
// JMeterUtils.getResString("ssl_pass_prompt"), JOptionPane.OK_CANCEL_OPTION,
// JOptionPane.PLAIN_MESSAGE);
// if (choice == JOptionPane.OK_OPTION) {
// char[] pwchars = pwf.getPassword();
// this.defaultpw = new String(pwchars);
// Arrays.fill(pwchars, '*');
// }
// System.setProperty(KEY_STORE_PASSWORD, this.defaultpw);
// password = this.defaultpw;
// }
} else {
log.warn("No password provided, and no GUI present so cannot prompt");
}
return password;
}
/**
* Opens and initializes the TrustStore.
*
* There are 3 possibilities:
* <ul>
* <li>no truststore name provided, in which case the default Java truststore
* should be used</li>
* <li>truststore name is provided, and loads OK</li>
* <li>truststore name is provided, but is not found or does not load OK, in
* which case an empty
* truststore is created</li>
* </ul>
* If the KeyStore object cannot be created, then this is currently treated the
* same as if no truststore name was provided.
*
* @return
* {@code null} when Java truststore should be used.
* Otherwise the truststore, which may be empty if the file could not be
* loaded.
*
*/
protected KeyStore getTrustStore() {
if (!truststoreLoaded) {
truststoreLoaded=true;// we've tried ...
String fileName = System.getProperty(SSL_TRUST_STORE);
if (fileName == null) {
return null;
}
log.info("TrustStore Location: {}", fileName);
try {
this.trustStore = KeyStore.getInstance("JKS");
log.info("TrustStore created OK, Type: JKS");
} catch (Exception e) {
this.trustStore = null;
throw new RuntimeException("Problem creating truststore: "+e.getMessage(), e);
}
try {
File initStore = new File(fileName);
if (initStore.exists()) {
try (InputStream fis = new FileInputStream(initStore)) {
this.trustStore.load(fis, null);
log.info("Truststore loaded OK from file");
}
} else {
log.warn("Truststore file not found, loading empty truststore");
this.trustStore.load(null, null);
}
} catch (Exception e) {
throw new RuntimeException("Can't load TrustStore: " + e.getMessage(), e);
}
}
return this.trustStore;
}
/**
* Protected Constructor to remove the possibility of directly instantiating
* this object. Create the SSLContext, and wrap all the X509KeyManagers with
* our X509KeyManager so that we can choose our alias.
*/
protected SSLManager() {
}
/**
* Static accessor for the SSLManager object. The SSLManager is a singleton.
*
* @return the singleton {@link SSLManager}
*/
public static synchronized SSLManager getInstance() {
if (null == SSLManager.manager) {
SSLManager.manager = new JsseSSLManager(null);
}
return SSLManager.manager;
}
/**
* Test whether SSL is supported or not.
*
* @return flag whether SSL is supported
*/
public static boolean isSSLSupported() {
return SSLManager.IS_SSL_SUPPORTED;
}
/**
* Configure Keystore
*
* @param preload
* flag whether the keystore should be opened within this method,
* or the opening should be delayed
* @param startIndex
* first index to consider for a key
* @param endIndex
* last index to consider for a key
* @param clientCertAliasVarName
* name of the default key, if empty the first key will be used
* as default key
*/
public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName,InputStream is,String password) {
this.keystoreAliasStartIndex = startIndex;
this.keystoreAliasEndIndex = endIndex;
this.clientCertAliasVarName = clientCertAliasVarName;
if(preload) {
keyStore = getKeyStore(is,password);
}
}
/**
* Destroy Keystore
*/
public synchronized void destroyKeystore() {
keyStore=null;
}
}

View File

@ -14,6 +14,14 @@
<el-input-number size="small" :disabled="isReadOnly" v-model="request.responseTimeout" :placeholder="$t('commons.millisecond')" :max="1000*10000000" :min="0"/>
</span>
</el-row>
<el-row style="margin: 20px">
<span style="margin-right: 10px">
认证别名:
</span>
<span style="margin-right: 10px">
<el-input size="small" style="width: 350px" v-model="request.alias"/>
</span>
</el-row>
<el-row style="margin: 20px">
<span style="margin-right: 10px">
<el-checkbox class="follow-redirects-item" v-model="request.followRedirects">{{$t('api_test.request.follow_redirects')}}</el-checkbox>

View File

@ -755,6 +755,7 @@ export class KeyValue extends BaseConfig {
this.files = undefined;
this.enable = undefined;
this.uuid = undefined;
this.time = undefined;
this.contentType = undefined;
this.set(options);
}

View File

@ -23,6 +23,9 @@
<el-tab-pane :label="$t('api_test.environment.tcp_config')" name="tcp">
<ms-tcp-config :config="environment.config.tcpConfig"/>
</el-tab-pane>
<el-tab-pane :label="$t('commons.ssl.config')" name="ssl">
<ms-environment-s-s-l-config :project-id="projectId" :ssl-config="environment.config.sslConfig"/>
</el-tab-pane>
</el-tabs>
<div class="environment-footer">
@ -44,7 +47,10 @@
import MsDatabaseConfig from "../request/database/DatabaseConfig";
import MsEnvironmentHttpConfig from "./EnvironmentHttpConfig";
import MsEnvironmentCommonConfig from "./EnvironmentCommonConfig";
import MsEnvironmentSSLConfig from "./EnvironmentSSLConfig";
import MsTcpConfig from "@/business/components/api/test/components/request/tcp/TcpConfig";
import {getUUID} from "@/common/js/utils";
export default {
name: "EnvironmentEdit",
@ -52,6 +58,7 @@
MsTcpConfig,
MsEnvironmentCommonConfig,
MsEnvironmentHttpConfig,
MsEnvironmentSSLConfig,
MsDatabaseConfig, MsApiHostTable, MsDialogFooter, MsApiKeyValue, MsApiScenarioVariables
},
props: {
@ -97,13 +104,32 @@
});
return isValidate;
},
geFiles(obj) {
let uploadFiles = [];
obj.uploadIds = [];
if (obj.config && obj.config.sslConfig && obj.config.sslConfig.files) {
obj.config.sslConfig.files.forEach(item => {
if (item.file && item.file.size > 0) {
if (!item.id) {
item.name = item.file.name;
item.id = getUUID();
}
obj.uploadIds.push(item.id);
uploadFiles.push(item.file);
}
})
}
return uploadFiles;
},
_save(environment) {
let bodyFiles = this.geFiles(environment);
let param = this.buildParam(environment);
let url = '/api/environment/add';
if (param.id) {
url = '/api/environment/update';
}
this.result = this.$post(url, param, response => {
this.$fileUpload(url, null, bodyFiles, param, response => {
//this.result = this.$post(url, param, response => {
if (!param.id) {
environment.id = response.data;
}

View File

@ -0,0 +1,198 @@
<template>
<div>
<div style="float: right;">
<el-button size="mini" @click="open">{{$t('test_track.case.import.click_upload')}}</el-button>
</div>
<div class="tip">{{ this.$t('commons.ssl.files') }}
</div>
<div class="ms-border">
<el-table :data="sslConfig.files" highlight-current-row>
<el-table-column prop="name" :label="$t('load_test.file_name')" show-overflow-tooltip width="180"/>
<el-table-column prop="type" :label="$t('api_test.definition.request.esb_table.type')" show-overflow-tooltip min-width="100px"/>
<el-table-column prop="password" show-overflow-tooltip min-width="120px" :label="$t('commons.password')">
<template v-slot:default="{row}">
<el-input size="small" v-model="row.password" clearable show-password/>
</template>
</el-table-column>
<el-table-column prop="updateTime" show-overflow-tooltip min-width="120px" :label="$t('load_test.last_modify_time')">
<template v-slot:default="{row}">
<span>{{ row.updateTime | timestampFormatDate }}</span>
</template>
</el-table-column>
<el-table-column :label="$t('commons.operating')" width="100px">
<template v-slot:default="{row}">
<ms-table-operator-button :tip="$t('commons.update')" icon="el-icon-edit"
type="primary" @exec="edit(row)"/>
<ms-table-operator-button :tip="$t('api_test.automation.remove')"
icon="el-icon-delete" @exec="remove(row)" type="danger" v-tester/>
</template>
</el-table-column>
</el-table>
</div>
<p class="tip">{{ this.$t('commons.ssl.entry') }} </p>
<div class="ms-border">
<el-table :data="sslConfig.entrys" highlight-current-row v-if="!loading">
<el-table-column prop="originalAsName" :label="$t('commons.ssl.original_as_name')" show-overflow-tooltip width="180"/>
<el-table-column prop="newAsName" :label="$t('commons.ssl.new_as_name')" show-overflow-tooltip min-width="100px">
<template v-slot:default="{row}">
<el-input size="mini" v-model="row.newAsName"></el-input>
</template>
</el-table-column>
<el-table-column prop="type" show-overflow-tooltip min-width="120px" :label="$t('api_test.definition.request.esb_table.type')"/>
<el-table-column prop="password" show-overflow-tooltip min-width="120px" :label="$t('commons.password')">
<template v-slot:default="{row}">
<el-input size="mini" v-model="row.password" show-password></el-input>
</template>
</el-table-column>
<el-table-column prop="sourceName" show-overflow-tooltip min-width="120px" :label="$t('commons.ssl.source')"/>
<el-table-column :label="$t('commons.ssl.default')" width="100px">
<template v-slot:default="{row}">
<el-checkbox v-model="row.default" @change="changeCheck(row)"/>
</template>
</el-table-column>
</el-table>
</div>
<ms-s-s-l-file-upload :config="fileConfig" :sslConfig="sslConfig" :callback="addConfig" ref="sslConfigUpload"/>
</div>
</template>
<script>
import {SSLConfig} from "../../model/EnvironmentModel";
import MsApiKeyValue from "../ApiKeyValue";
import {REQUEST_HEADERS} from "@/common/js/constants";
import MsSelectTree from "../../../../common/select-tree/SelectTree";
import MsTableOperatorButton from "@/business/components/common/components/MsTableOperatorButton";
import {getUUID} from "@/common/js/utils";
import MsSSLFileUpload from "./SSLFileUpload";
export default {
name: "MsEnvironmentSSLConfig",
components: {MsApiKeyValue, MsSelectTree, MsTableOperatorButton, MsSSLFileUpload},
props: {
sslConfig: new SSLConfig(),
projectId: String,
},
created() {
},
data() {
return {
loading: false,
fileConfig: {},
};
},
watch: {
projectId() {
},
},
methods: {
open() {
this.$refs.sslConfigUpload.open();
},
addConfig(config, file) {
let sslFile = {id: config.id, name: file.name, type: file.type, updateTime: new Date().getTime(), password: config.password, file: file};
if (!sslFile.type && sslFile.name) {
let type = sslFile.name.substr(sslFile.name.lastIndexOf(".") + 1);
sslFile.type = type;
}
if (file.size > 0) {
this.getEntry(sslFile);
}
},
edit(row) {
this.$refs.sslConfigUpload.open(row);
},
reload() {
this.loading = true
this.$nextTick(() => {
this.loading = false
});
},
getEntry(sslFile) {
let url = '/api/environment/get/entry';
this.$fileUpload(url, sslFile.file, null, sslFile.password, response => {
let data = response.data;
if (data) {
if (!sslFile.id) {
sslFile.id = getUUID();
data.forEach(item => {
if (item) {
item.id = getUUID();
item.sourceId = sslFile.id;
item.sourceName = sslFile.name;
}
item.password = "";
item.default = false;
this.sslConfig.entrys.unshift(item);
})
this.sslConfig.files.unshift(sslFile);
} else {
//
this.remove(sslFile);
data.forEach(item => {
if (item) {
item.id = getUUID();
item.sourceId = sslFile.id;
item.sourceName = sslFile.name;
}
item.password = "";
item.default = false;
this.sslConfig.entrys.unshift(item);
})
this.sslConfig.files.unshift(sslFile);
}
}
});
},
remove(row) {
const index = this.sslConfig.files.findIndex((d) => d.id === row.id);
this.sslConfig.files.splice(index, 1);
//
if (this.sslConfig.entrys) {
let removeKeys = [];
this.sslConfig.entrys.forEach(item => {
if (item && item.sourceId === row.id) {
const index = this.sslConfig.entrys.findIndex((d) => d.sourceId === row.id);
removeKeys.push(index);
}
});
removeKeys.forEach(index => {
if (index !== -1) {
this.sslConfig.entrys.splice(index, 1);
}
})
}
},
changeCheck(row) {
if (row.default) {
this.sslConfig.entrys.forEach(item => {
if (item && item.sourceId !== row.id) {
item.default = false;
}
});
row.default = true;
}
},
}
}
</script>
<style scoped>
/deep/ .el-form-item {
margin-bottom: 15px;
}
.ms-el-form-item__content >>> .el-form-item__content {
line-height: 20px;
}
.tip {
padding: 3px 5px;
font-size: 16px;
border-radius: 4px;
border-left: 4px solid #783887;
}
</style>

View File

@ -0,0 +1,207 @@
<template>
<el-dialog
:title="$t('test_track.case.import.import_file')"
:visible.sync="dialogVisible"
append-to-body
destroy-on-close
width="500px"
:before-close="handleClose">
<el-form :model="currentConfig" label-width="100px" v-loading="result.loading" ref="form">
<el-row>
<el-form-item :label="$t('commons.password')" prop="password">
<el-input size="small" v-model="currentConfig.password" clearable show-password/>
</el-form-item>
<el-form-item>
<el-upload
class="jar-upload"
drag
action="#"
:http-request="upload"
:limit="1"
:beforeUpload="uploadValidate"
:on-remove="handleRemove"
:on-exceed="handleExceed"
:file-list="fileList"
ref="fileUpload">
<i class="el-icon-upload"></i>
<div class="el-upload__text" v-html="$t('load_test.upload_tips')"></div>
<div class="el-upload__tip" slot="tip">{{$t('api_test.api_import.file_size_limit')}}支持p12,jks,pfx格式</div>
</el-upload>
</el-form-item>
<el-col>
<div class="buttons">
<el-button type="primary" size="small" @click="save('add')">{{$t('commons.save')}}</el-button>
</div>
</el-col>
</el-row>
</el-form>
</el-dialog>
</template>
<script>
import {getUUID} from "@/common/js/utils";
export default {
name: "SSLFileUpload",
data() {
return {
visible: false,
dialogVisible: false,
result: {},
currentConfig: {
password: '',
fileName: '',
},
rules: {
name: [
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 60, message: this.$t('commons.input_limit', [1, 60]), trigger: 'blur'}
],
description: [
{max: 250, message: this.$t('commons.input_limit', [1, 250]), trigger: 'blur'}
],
},
fileList: []
}
},
props: {
readOnly: {
type: Boolean,
default: false
},
config: {
type: Object,
default() {
return {};
}
},
sslConfig: {},
callback: {
type: Function
},
},
watch: {
config() {
this.currentConfig = {
id: '',
name: '',
fileName: ''
};
if (this.config.fileName) {
this.fileList = [{name: this.config.fileName}];
} else {
this.fileList = [];
}
Object.assign(this.currentConfig, this.config);
}
},
mounted() {
Object.assign(this.currentConfig, this.config);
},
methods: {
upload(file) {
this.fileList.push(file.file)
},
handleExceed(files, fileList) {
this.$warning(this.$t('test_track.case.import.upload_limit_count'));
},
handleRemove(file, fileList) {
this.fileList = [];
},
uploadValidate(file, fileList) {
let suffix = file.name.substring(file.name.lastIndexOf('.') + 1);
if (suffix !== 'jks' && suffix !== 'p12' && suffix !== 'pfx') {
this.$warning(this.$t('api_test.api_import.suffixFormatErr'));
return false;
}
if (file.size / 1024 / 1024 > 30) {
this.$warning(this.$t('jar_config.upload_limit_size'));
return false;
}
if (this.sslConfig.files) {
let isFlag = false;
this.sslConfig.files.forEach(item => {
if (item && item.name === file.name) {
isFlag = true;
}
})
if (isFlag) {
this.$warning("文件已经存在!");
return false;
}
}
return true;
},
save(type) {
this.$refs['form'].validate((valid) => {
if (valid) {
if (this.fileList <= 0) {
this.$warning(this.$t('commons.please_upload'));
return;
}
if (this.callback) {
this.dialogVisible = false;
this.callback(this.currentConfig, this.fileList[0]);
}
} else {
return false;
}
});
},
clear() {
this.currentConfig.password = "";
this.currentConfig.id = "";
this.fileList = [];
},
open(row) {
this.clear();
if (row) {
this.currentConfig.password = row.password;
this.currentConfig.id = row.id;
this.fileList.push({name: row.name});
}
this.dialogVisible = true;
},
handleClose() {
this.dialogVisible = false;
}
}
}
</script>
<style scoped>
.el-divider {
height: 200px;
}
.jar-upload {
text-align: center;
margin: auto 0;
}
.jar-upload >>> .el-upload {
width: 100%;
max-width: 350px;
}
.jar-upload >>> .el-upload-dragger {
width: 100%;
}
.el-form {
border: solid #E1E1E1 1px;
margin: 10px 0;
padding: 30px 20px;
border-radius: 3px;
}
.buttons {
margin-top: 10px;
margin-bottom: -10px;
float: right;
}
</style>

View File

@ -20,6 +20,21 @@ export class Environment extends BaseConfig {
}
}
export class SSLConfig extends BaseConfig {
constructor(options = {}) {
super();
this.entrys = [];
this.files = [];
this.set(options);
this.sets({files: KeyValue}, options);
this.sets({entrys: KeyValue}, options);
}
initOptions(options = {}) {
return options;
}
}
export class Config extends BaseConfig {
constructor(options = {}) {
super();
@ -27,7 +42,7 @@ export class Config extends BaseConfig {
this.httpConfig = undefined;
this.databaseConfigs = [];
this.tcpConfig = undefined;
this.sslConfig = {};
this.set(options);
this.sets({databaseConfigs: DatabaseConfig}, options);
}
@ -35,6 +50,7 @@ export class Config extends BaseConfig {
initOptions(options = {}) {
this.commonConfig = new CommonConfig(options.commonConfig);
this.httpConfig = new HttpConfig(options.httpConfig);
this.sslConfig = new SSLConfig(options.sslConfig);
options.databaseConfigs = options.databaseConfigs || [];
options.tcpConfig = new TCPConfig(options.tcpConfig);
return options;

View File

@ -159,6 +159,15 @@ export default {
table: {
select_tip: "Item {0} data is selected"
},
ssl: {
config: "Config",
files: "Files",
entry: "Entry",
original_as_name: "Original as name",
new_as_name: "New name",
source: "Source",
default: "Default"
},
date: {
select_date: 'Select date',
start_date: 'Start date',

View File

@ -160,6 +160,15 @@ export default {
table: {
select_tip: "已选中 {0} 条数据"
},
ssl: {
config: "证书配置",
files: "证书文件",
entry: "证书条目",
original_as_name: "原有别名",
new_as_name: "新别名",
source: "来源",
default: "是否默认"
},
date: {
select_date: '选择日期',
start_date: '开始日期',

View File

@ -160,6 +160,15 @@ export default {
table: {
select_tip: "已選中 {0} 條數據"
},
ssl: {
config: "證書配置",
files: "證書文件",
entry: "證書條目",
original_as_name: "原有別名",
new_as_name: "新别名",
source: "來源",
default: "是否默認"
},
date: {
select_date: '選擇日期',
start_date: '開始日期',