ssh 编辑文件准备

This commit is contained in:
bwcx_jzy 2021-09-16 23:16:08 +08:00
parent cf2ba2e088
commit 85b9ff705a
10 changed files with 236 additions and 210 deletions

View File

@ -1,12 +1,10 @@
package io.jpom.controller.manage;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
@ -36,7 +34,6 @@ import javax.annotation.Resource;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -61,28 +58,21 @@ public class ProjectFileControl extends BaseAgentController {
public String getFileList(String id, String path) {
// 查询项目路径
ProjectInfoModel pim = projectInfoService.getItem(id);
if (pim == null) {
return JsonMessage.getString(500, "查询失败:项目不存在");
}
Assert.notNull(pim, "查询失败:项目不存在");
String lib = pim.allLib();
File fileDir = FileUtil.file(lib, StrUtil.emptyToDefault(path, FileUtil.FILE_SEPARATOR));
if (!fileDir.exists()) {
return JsonMessage.getString(500, "目录不存在");
}
Assert.state(FileUtil.exist(fileDir), "目录不存在");
//
File[] filesAll = fileDir.listFiles();
if (filesAll == null) {
return JsonMessage.getString(500, "目录是空");
}
Assert.notEmpty(filesAll, "目录是空");
JSONArray arrayFile = FileUtils.parseInfo(filesAll, false, lib);
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
List<String> allowEditSuffix = whitelist.getAllowEditSuffix();
if (CollUtil.isNotEmpty(allowEditSuffix)) {
for (Object o : arrayFile) {
JSONObject jsonObject = (JSONObject) o;
String filename = jsonObject.getString("filename");
jsonObject.put("textFileEdit", this.checkSilentFileSuffix(filename));
}
for (Object o : arrayFile) {
JSONObject jsonObject = (JSONObject) o;
String filename = jsonObject.getString("filename");
jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(whitelist.getAllowEditSuffix(), filename));
}
return JsonMessage.getString(200, "查询成功", arrayFile);
}
@ -119,7 +109,7 @@ public class ProjectFileControl extends BaseAgentController {
try {
CompressionFileUtil.unCompress(file, lib);
} finally {
if (!file.delete()) {
if (!FileUtil.del(file)) {
DefaultSystemLog.getLog().error("删除文件失败:" + file.getPath());
}
}
@ -228,63 +218,6 @@ public class ProjectFileControl extends BaseAgentController {
}
}
/**
* 静默判断是否可以编辑对应的文件
*
* @param filename 文件名
* @return true 可以编辑
*/
private boolean checkSilentFileSuffix(String filename) {
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
if (whitelist == null) {
return false;
}
List<String> allowEditSuffix = whitelist.getAllowEditSuffix();
if (CollUtil.isEmpty(allowEditSuffix)) {
return false;
}
Charset charset = this.parserFileSuffixMap(allowEditSuffix, filename);
return charset != null;
}
private Charset parserFileSuffixMap(List<String> allowEditSuffix, String filename) {
Map<String, Charset> map = CollStreamUtil.toMap(allowEditSuffix, s -> {
List<String> split = StrUtil.split(s, StrUtil.AT);
return CollUtil.getFirst(split);
}, s -> {
List<String> split = StrUtil.split(s, StrUtil.AT);
if (split.size() > 1) {
String last = CollUtil.getLast(split);
return CharsetUtil.charset(last);
} else {
return CharsetUtil.defaultCharset();
}
});
Set<Map.Entry<String, Charset>> entries = map.entrySet();
for (Map.Entry<String, Charset> entry : entries) {
if (StrUtil.endWithIgnoreCase(filename, StrUtil.DOT + entry.getKey())) {
return entry.getValue();
}
}
return null;
}
/**
* 获取文件可以编辑的 文件编码格式
*
* @param filename 文件名
* @return charset 不能编辑情况会抛出异常
*/
private Charset checkFileSuffix(String filename) {
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
List<String> allowEditSuffix = whitelist.getAllowEditSuffix();
Assert.notEmpty(allowEditSuffix, "没有配置可允许编辑的后缀");
Charset charset = this.parserFileSuffixMap(allowEditSuffix, filename);
Assert.notNull(charset, "不允许编辑的文件后缀");
return charset;
}
/**
* 读取文件内容 只能处理文本文件
*
@ -297,7 +230,8 @@ public class ProjectFileControl extends BaseAgentController {
ProjectInfoModel pim = getProjectInfoModel();
filePath = StrUtil.emptyToDefault(filePath, File.separator);
// 判断文件后缀
Charset charset = this.checkFileSuffix(filename);
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename);
File file = FileUtil.file(pim.allLib(), filePath, filename);
String ymlString = FileUtil.readString(file, charset);
return JsonMessage.getString(200, "", ymlString);
@ -316,7 +250,8 @@ public class ProjectFileControl extends BaseAgentController {
ProjectInfoModel pim = getProjectInfoModel();
filePath = StrUtil.emptyToDefault(filePath, File.separator);
// 判断文件后缀
Charset charset = this.checkFileSuffix(filename);
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename);
FileUtil.writeString(fileText, FileUtil.file(pim.allLib(), filePath, filename), charset);
return JsonMessage.getString(200, "文件写入成功");
}

View File

@ -47,8 +47,8 @@ public class WhitelistDirectoryController extends BaseJpomController {
//
List<String> certificateList = AgentWhitelist.parseToList(certificate, "证书路径白名单不能为空");
List<String> nList = AgentWhitelist.parseToList(nginx, "nginx路径白名单不能为空");
List<String> allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "运行编辑的文件后缀不能为空");
List<String> allowRemoteDownloadHostList = AgentWhitelist.parseToList(allowRemoteDownloadHost, "运行远程下载的 host 不能配置为空");
List<String> allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "允许编辑的文件后缀不能为空");
List<String> allowRemoteDownloadHostList = AgentWhitelist.parseToList(allowRemoteDownloadHost, "允许远程下载的 host 不能配置为空");
return save(list, certificateList, nList, allowEditSuffixList, allowRemoteDownloadHostList).toString();
}
//

View File

@ -17,114 +17,117 @@ import java.io.File;
*/
public class FileUtils {
private static JSONObject fileToJson(File file) {
JSONObject jsonObject = new JSONObject(6);
if (file.isDirectory()) {
jsonObject.put("isDirectory", true);
long sizeFile = FileUtil.size(file);
jsonObject.put("fileSize", FileUtil.readableFileSize(sizeFile));
} else {
jsonObject.put("fileSize", FileUtil.readableFileSize(file.length()));
}
jsonObject.put("filename", file.getName());
long mTime = file.lastModified();
jsonObject.put("modifyTimeLong", mTime);
jsonObject.put("modifyTime", DateUtil.date(mTime).toString());
return jsonObject;
}
private static JSONObject fileToJson(File file) {
JSONObject jsonObject = new JSONObject(6);
if (file.isDirectory()) {
jsonObject.put("isDirectory", true);
long sizeFile = FileUtil.size(file);
jsonObject.put("fileSize", FileUtil.readableFileSize(sizeFile));
} else {
jsonObject.put("fileSize", FileUtil.readableFileSize(file.length()));
}
jsonObject.put("filename", file.getName());
long mTime = file.lastModified();
jsonObject.put("modifyTimeLong", mTime);
jsonObject.put("modifyTime", DateUtil.date(mTime).toString());
return jsonObject;
}
/**
* 对文件信息解析排序
*
* @param files 文件数组
* @param time 是否安装时间排序
* @param startPath 开始路径
* @return 排序后的json
*/
public static JSONArray parseInfo(File[] files, boolean time, String startPath) {
int size = files.length;
JSONArray arrayFile = new JSONArray(size);
for (File file : files) {
JSONObject jsonObject = FileUtils.fileToJson(file);
//
if (startPath != null) {
String levelName = StringUtil.delStartPath(file, startPath, false);
jsonObject.put("levelName", levelName);
}
//
arrayFile.add(jsonObject);
}
arrayFile.sort((o1, o2) -> {
JSONObject jsonObject1 = (JSONObject) o1;
JSONObject jsonObject2 = (JSONObject) o2;
if (time) {
return jsonObject2.getLong("modifyTimeLong").compareTo(jsonObject1.getLong("modifyTimeLong"));
}
return jsonObject1.getString("filename").compareTo(jsonObject2.getString("filename"));
});
final int[] i = {0};
arrayFile.forEach(o -> {
JSONObject jsonObject = (JSONObject) o;
jsonObject.put("index", ++i[0]);
});
return arrayFile;
}
/**
* 对文件信息解析排序
*
* @param files 文件数组
* @param time 是否安装时间排序
* @param startPath 开始路径
* @return 排序后的json
*/
public static JSONArray parseInfo(File[] files, boolean time, String startPath) {
if (files == null) {
return new JSONArray();
}
int size = files.length;
JSONArray arrayFile = new JSONArray(size);
for (File file : files) {
JSONObject jsonObject = FileUtils.fileToJson(file);
//
if (startPath != null) {
String levelName = StringUtil.delStartPath(file, startPath, false);
jsonObject.put("levelName", levelName);
}
//
arrayFile.add(jsonObject);
}
arrayFile.sort((o1, o2) -> {
JSONObject jsonObject1 = (JSONObject) o1;
JSONObject jsonObject2 = (JSONObject) o2;
if (time) {
return jsonObject2.getLong("modifyTimeLong").compareTo(jsonObject1.getLong("modifyTimeLong"));
}
return jsonObject1.getString("filename").compareTo(jsonObject2.getString("filename"));
});
final int[] i = {0};
arrayFile.forEach(o -> {
JSONObject jsonObject = (JSONObject) o;
jsonObject.put("index", ++i[0]);
});
return arrayFile;
}
/**
* 判断路径是否满足jdk 条件
*
* @param path 路径
* @return 判断存在java文件
*/
public static boolean isJdkPath(String path) {
String fileName = getJdkJavaPath(path, false);
File newPath = new File(fileName);
return newPath.exists() && newPath.isFile();
}
/**
* 判断路径是否满足jdk 条件
*
* @param path 路径
* @return 判断存在java文件
*/
public static boolean isJdkPath(String path) {
String fileName = getJdkJavaPath(path, false);
File newPath = new File(fileName);
return newPath.exists() && newPath.isFile();
}
/**
* 获取java 文件路径
*
* @param path path
* @param w 是否使用javaw
* @return 完整路径
*/
public static String getJdkJavaPath(String path, boolean w) {
String fileName;
if (SystemUtil.getOsInfo().isWindows()) {
fileName = w ? "javaw.exe" : "java.exe";
} else {
fileName = w ? "javaw" : "java";
}
File newPath = FileUtil.file(path, "bin", fileName);
return FileUtil.getAbsolutePath(newPath);
}
/**
* 获取java 文件路径
*
* @param path path
* @param w 是否使用javaw
* @return 完整路径
*/
public static String getJdkJavaPath(String path, boolean w) {
String fileName;
if (SystemUtil.getOsInfo().isWindows()) {
fileName = w ? "javaw.exe" : "java.exe";
} else {
fileName = w ? "javaw" : "java";
}
File newPath = FileUtil.file(path, "bin", fileName);
return FileUtil.getAbsolutePath(newPath);
}
/**
* 获取jdk 版本
*
* @param path jdk 路径
* @return 获取成功返回版本号
*/
public static String getJdkVersion(String path) {
String newPath = getJdkJavaPath(path, false);
if (path.contains(StrUtil.SPACE)) {
newPath = String.format("\"%s\"", newPath);
}
String command = CommandUtil.execSystemCommand(newPath + " -version");
String[] split = StrUtil.splitToArray(command, StrUtil.LF);
if (split == null || split.length <= 0) {
return null;
}
String[] strings = StrUtil.splitToArray(split[0], "\"");
if (strings == null || strings.length <= 1) {
return null;
}
return strings[1];
}
/**
* 获取jdk 版本
*
* @param path jdk 路径
* @return 获取成功返回版本号
*/
public static String getJdkVersion(String path) {
String newPath = getJdkJavaPath(path, false);
if (path.contains(StrUtil.SPACE)) {
newPath = String.format("\"%s\"", newPath);
}
String command = CommandUtil.execSystemCommand(newPath + " -version");
String[] split = StrUtil.splitToArray(command, StrUtil.LF);
if (split == null || split.length <= 0) {
return null;
}
String[] strings = StrUtil.splitToArray(split[0], "\"");
if (strings == null || strings.length <= 1) {
return null;
}
return strings[1];
}
public static String getJarSeparator() {
return SystemUtil.getOsInfo().isWindows() ? ";" : ":";
}
public static String getJarSeparator() {
return SystemUtil.getOsInfo().isWindows() ? ";" : ":";
}
}

View File

@ -1,8 +1,10 @@
package io.jpom.model.data;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import com.alibaba.fastjson.JSONObject;
@ -12,10 +14,8 @@ import io.jpom.system.ExtConfigBean;
import org.springframework.util.Assert;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.nio.charset.Charset;
import java.util.*;
/**
* 白名单
@ -205,4 +205,60 @@ public class AgentWhitelist extends BaseJsonModel {
Assert.notEmpty(list, errorMsg);
return list;
}
/**
* 获取文件可以编辑的 文件编码格式
*
* @param filename 文件名
* @return charset 不能编辑情况会抛出异常
*/
public static Charset checkFileSuffix(List<String> allowEditSuffix, String filename) {
Assert.notEmpty(allowEditSuffix, "没有配置可允许编辑的后缀");
Charset charset = AgentWhitelist.parserFileSuffixMap(allowEditSuffix, filename);
Assert.notNull(charset, "不允许编辑的文件后缀");
return charset;
}
/**
* 静默判断是否可以编辑对应的文件
*
* @param filename 文件名
* @return true 可以编辑
*/
public static boolean checkSilentFileSuffix(List<String> allowEditSuffix, String filename) {
if (CollUtil.isEmpty(allowEditSuffix)) {
return false;
}
Charset charset = AgentWhitelist.parserFileSuffixMap(allowEditSuffix, filename);
return charset != null;
}
/**
* 根据文件名 可以配置列表 获取编码格式
*
* @param allowEditSuffix 允许编辑的配置
* @param filename 文件名
* @return 没有匹配到 返回 null没有配置编码格式即使用系统默认编码格式
*/
public static Charset parserFileSuffixMap(List<String> allowEditSuffix, String filename) {
Map<String, Charset> map = CollStreamUtil.toMap(allowEditSuffix, s -> {
List<String> split = StrUtil.split(s, StrUtil.AT);
return CollUtil.getFirst(split);
}, s -> {
List<String> split = StrUtil.split(s, StrUtil.AT);
if (split.size() > 1) {
String last = CollUtil.getLast(split);
return CharsetUtil.charset(last);
} else {
return CharsetUtil.defaultCharset();
}
});
Set<Map.Entry<String, Charset>> entries = map.entrySet();
for (Map.Entry<String, Charset> entry : entries) {
if (StrUtil.endWithIgnoreCase(filename, StrUtil.DOT + entry.getKey())) {
return entry.getValue();
}
}
return null;
}
}

View File

@ -16,6 +16,7 @@ import com.jcraft.jsch.Session;
import io.jpom.common.BaseServerController;
import io.jpom.common.interceptor.OptLog;
import io.jpom.model.BaseModel;
import io.jpom.model.data.AgentWhitelist;
import io.jpom.model.data.NodeModel;
import io.jpom.model.data.SshModel;
import io.jpom.model.log.SshTerminalExecuteLog;
@ -27,6 +28,7 @@ import io.jpom.service.dblog.SshTerminalExecuteLogService;
import io.jpom.service.node.ssh.SshService;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@ -112,21 +114,20 @@ public class SshController extends BaseServerController {
@ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "port错误") int port,
String charset, String fileDirs,
String id, String type, String notAllowedCommand) {
// 优先判断参数 如果是 password 在修改时可以不填写
if (connectType == SshModel.ConnectType.PASS && StrUtil.isEmpty(password) && "add".equals(type)) {
return JsonMessage.getString(405, "请填写登录密码");
}
if (connectType == SshModel.ConnectType.PUBKEY && StrUtil.isEmpty(privateKey)) {
return JsonMessage.getString(405, "请填写证书内容");
}
SshModel sshModel;
if ("edit".equals(type)) {
sshModel = sshService.getItem(id);
if (sshModel == null) {
return JsonMessage.getString(500, "不存在对应ssh");
// 优先判断参数 如果是 password 在修改时可以不填写
boolean add = "add".equals(type);
if (add) {
if (connectType == SshModel.ConnectType.PASS && StrUtil.isEmpty(password)) {
return JsonMessage.getString(405, "请填写登录密码");
}
if (connectType == SshModel.ConnectType.PUBKEY && StrUtil.isEmpty(privateKey)) {
return JsonMessage.getString(405, "请填写证书内容");
}
} else {
sshModel = new SshModel();
} else {
sshModel = sshService.getItem(id);
Assert.notNull(sshModel, "不存在对应ssh");
}
// 目录
if (StrUtil.isEmpty(fileDirs)) {
@ -152,7 +153,10 @@ public class SshController extends BaseServerController {
sshModel.setName(name);
sshModel.setNotAllowedCommand(notAllowedCommand);
sshModel.setConnectType(connectType);
// 获取允许编辑的后缀
String allowEditSuffix = getParameter("allowEditSuffix");
List<String> allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "允许编辑的文件后缀不能为空");
sshModel.setAllowEditSuffix(allowEditSuffixList);
try {
Charset.forName(charset);
sshModel.setCharset(charset);

View File

@ -19,6 +19,7 @@ import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import io.jpom.common.BaseServerController;
import io.jpom.model.data.AgentWhitelist;
import io.jpom.model.data.SshModel;
import io.jpom.plugin.ClassFeature;
import io.jpom.plugin.Feature;
@ -188,6 +189,7 @@ public class SshFileController extends BaseServerController {
private JSONArray listDir(SshModel sshModel, String path, String children) throws SftpException {
Session session = null;
ChannelSftp channel = null;
List<String> allowEditSuffix = sshModel.getAllowEditSuffix();
try {
session = SshService.getSession(sshModel);
channel = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP);
@ -201,28 +203,31 @@ public class SshFileController extends BaseServerController {
}
JSONArray jsonArray = new JSONArray();
for (ChannelSftp.LsEntry lsEntry : vector) {
if (StrUtil.DOT.equals(lsEntry.getFilename()) || StrUtil.DOUBLE_DOT.equals(lsEntry.getFilename())) {
String filename = lsEntry.getFilename();
if (StrUtil.DOT.equals(filename) || StrUtil.DOUBLE_DOT.equals(filename)) {
continue;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", lsEntry.getFilename());
jsonObject.put("name", filename);
jsonObject.put("id", IdUtil.fastSimpleUUID());
int mTime = lsEntry.getAttrs().getMTime();
String format = DateUtil.format(DateUtil.date(mTime * 1000L), DatePattern.NORM_DATETIME_MINUTE_PATTERN);
jsonObject.put("modifyTime", format);
if (lsEntry.getAttrs().isDir()) {
jsonObject.put("dir", true);
jsonObject.put("title", lsEntry.getFilename());
jsonObject.put("title", filename);
} else {
jsonObject.put("title", lsEntry.getFilename());
jsonObject.put("title", filename);
long fileSize = lsEntry.getAttrs().getSize();
jsonObject.put("size", FileUtil.readableFileSize(fileSize));
// 允许编辑
jsonObject.put("textFileEdit", AgentWhitelist.checkSilentFileSuffix(allowEditSuffix, filename));
}
//
if (StrUtil.isEmpty(children)) {
jsonObject.put("parentDir", lsEntry.getFilename());
jsonObject.put("parentDir", filename);
} else {
jsonObject.put("parentDir", FileUtil.normalize(StrUtil.format("{}/{}", children, lsEntry.getFilename())));
jsonObject.put("parentDir", FileUtil.normalize(StrUtil.format("{}/{}", children, filename)));
}
jsonArray.add(jsonObject);
}

View File

@ -46,6 +46,11 @@ public class SshModel extends BaseModel {
*/
private String notAllowedCommand;
/**
* 运行编辑的后缀文件
*/
private List<String> allowEditSuffix;
public String getNotAllowedCommand() {
return notAllowedCommand;
}
@ -158,6 +163,14 @@ public class SshModel extends BaseModel {
return charset;
}
public List<String> getAllowEditSuffix() {
return allowEditSuffix;
}
public void setAllowEditSuffix(List<String> allowEditSuffix) {
this.allowEditSuffix = allowEditSuffix;
}
/**
* 检查是否包含禁止命令
*

View File

@ -45,7 +45,8 @@ export function editSsh(params) {
privateKey: params.privateKey,
charset: params.charset,
fileDirs: params.fileDirs,
notAllowedCommand: params.notAllowedCommand
notAllowedCommand: params.notAllowedCommand,
allowEditSuffix: params.allowEditSuffix,
}
return axios({
url: '/node/ssh/save.json',

View File

@ -34,7 +34,7 @@
<span>{{ text }}</span>
</a-tooltip>
<template slot="operation" slot-scope="text, record">
<a-button type="primary" @click="handlePreview(record)">查看</a-button>
<a-button type="primary" :disabled="!record.textFileEdit" @click="handlePreview(record)">编辑</a-button>
<a-button type="primary" @click="handleDownload(record)">下载</a-button>
<a-button type="danger" @click="handleDelete(record)">删除</a-button>
</template>

View File

@ -85,6 +85,15 @@
<a-form-model-item label="禁止命令" prop="notAllowedCommand">
<a-textarea v-model="temp.notAllowedCommand" :auto-size="{ minRows: 3, maxRows: 5 }" placeholder="禁止命令是不允许在终端执行的名,多个逗号隔开" />
</a-form-model-item>
<a-form-model-item label="文件后缀" prop="suffix">
<a-input
v-model="temp.allowEditSuffix"
type="textarea"
:rows="5"
style="resize: none"
placeholder="请输入允许编辑文件的后缀及文件编码不设置编码则默认取系统编码示例设置编码txt@utf-8 不设置编码txt"
/>
</a-form-model-item>
</a-form-model>
</a-modal>
<!-- 安装节点 -->