Merge branch 'dev' into pr@dev_st_fix
This commit is contained in:
commit
7312a2624a
@ -1,5 +1,7 @@
|
||||
package io.dataease.commons.utils;
|
||||
|
||||
import io.dataease.commons.exception.DEException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
@ -41,6 +43,17 @@ public class DeFileUtils {
|
||||
if (dir.exists()) return ;
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
public static void validateFile(MultipartFile file) {
|
||||
String name = getFileNameNoEx(file.getOriginalFilename());
|
||||
if (StringUtils.contains(name, "./")) {
|
||||
DEException.throwException("file path invalid");
|
||||
}
|
||||
String suffix = getExtensionName(file.getOriginalFilename());
|
||||
if (!StringUtils.equalsIgnoreCase(suffix, "zip")) {
|
||||
DEException.throwException("please upload valid zip file");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 将文件名解析成文件的上传路径
|
||||
*/
|
||||
|
||||
@ -1,18 +1,6 @@
|
||||
package io.dataease.commons.wrapper;
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.dataease.commons.holder.ThreadLocalContextHolder;
|
||||
import io.dataease.commons.utils.CommonBeanFactory;
|
||||
@ -21,16 +9,30 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
private static final String defaultWhiteList = "/dataset/table/sqlPreview,/dataset/table/update,/dataset/field/multFieldValues,/dataset/field/linkMultFieldValues";
|
||||
|
||||
HttpServletRequest orgRequest = null;
|
||||
private Map<String, String[]> parameterMap;
|
||||
private final byte[] body; //用于保存读取body中数据
|
||||
|
||||
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) throws IOException{
|
||||
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
orgRequest = request;
|
||||
parameterMap = request.getParameterMap();
|
||||
@ -38,6 +40,7 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
}
|
||||
|
||||
// 重写几个HttpServletRequestWrapper中的方法
|
||||
|
||||
/**
|
||||
* 获取所有参数名
|
||||
*
|
||||
@ -159,7 +162,6 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* 防止xss跨脚本攻击(替换,根据实际情况调整)
|
||||
*/
|
||||
|
||||
@ -208,9 +210,9 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
return value;
|
||||
}
|
||||
|
||||
public static boolean checkSqlInjection(Object obj){
|
||||
public static boolean checkSqlInjection(Object obj) {
|
||||
HttpServletRequest request = ServletUtils.request();
|
||||
String url = request.getRequestURI().toString();
|
||||
String url = request.getRequestURI();
|
||||
|
||||
if (null == obj) return false;
|
||||
if (StringUtils.isEmpty(obj.toString())) return false;
|
||||
@ -219,14 +221,14 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
|
||||
if (StringUtils.isEmpty(orders)) return false;
|
||||
|
||||
String whiteLists = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.sqlinjection.whitelists", String.class, null);
|
||||
String whiteLists = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.sqlinjection.whitelists", String.class, defaultWhiteList);
|
||||
if (StringUtils.isNotEmpty(whiteLists)) {
|
||||
// 命中白名单 无需检测sql注入
|
||||
if (Arrays.stream(whiteLists.split(",")).anyMatch(item -> url.indexOf(item) != -1)) return false;
|
||||
}
|
||||
Pattern pattern= Pattern.compile("(.*\\=.*\\-\\-.*)|(.*(\\+).*)|(.*\\w+(%|\\$|#|&)\\w+.*)|(.*\\|\\|.*)|(.*\\s+(and|or)\\s+.*)" +
|
||||
Pattern pattern = Pattern.compile("(.*\\=.*\\-\\-.*)|(.*(\\+).*)|(.*\\w+(%|\\$|#|&)\\w+.*)|(.*\\|\\|.*)|(.*\\s+(and|or)\\s+.*)" +
|
||||
"|(.*\\b(select|update|union|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute|sleep|extractvalue|updatexml|substring|database|concat|rand|gtid_subset)\\b.*)");
|
||||
Matcher matcher=pattern.matcher(orders.toLowerCase());
|
||||
Matcher matcher = pattern.matcher(orders.toLowerCase());
|
||||
return matcher.find();
|
||||
}
|
||||
|
||||
@ -236,7 +238,7 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
|
||||
if (value != null) {
|
||||
boolean b = checkSqlInjection(value);
|
||||
if(b) {
|
||||
if (b) {
|
||||
ThreadLocalContextHolder.setData("包含SQL注入的参数,请检查参数!");
|
||||
return true;
|
||||
}
|
||||
@ -320,7 +322,7 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
return true;
|
||||
}
|
||||
} else if ((submitValues instanceof String[])) {
|
||||
for (String submitValue : (String[])submitValues){
|
||||
for (String submitValue : (String[]) submitValues) {
|
||||
if (checkXSSAndSql(submitValue)) {
|
||||
return true;
|
||||
}
|
||||
@ -332,7 +334,7 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
|
||||
private static String orders(String json) {
|
||||
if (StringUtils.isEmpty(json)) return null;
|
||||
try{
|
||||
try {
|
||||
Map<String, Object> map = new Gson().fromJson(json, Map.class);
|
||||
Object orders = map.get("orders");
|
||||
|
||||
@ -345,7 +347,7 @@ public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrappe
|
||||
return sort.toString();
|
||||
}
|
||||
return null;
|
||||
}catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -3,10 +3,11 @@ package io.dataease.controller.sys;
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.dataease.auth.annotation.SqlInjectValidator;
|
||||
import io.dataease.plugins.common.base.domain.MyPlugin;
|
||||
import io.dataease.commons.utils.DeFileUtils;
|
||||
import io.dataease.commons.utils.PageUtils;
|
||||
import io.dataease.commons.utils.Pager;
|
||||
import io.dataease.controller.sys.base.BaseGridRequest;
|
||||
import io.dataease.plugins.common.base.domain.MyPlugin;
|
||||
import io.dataease.service.sys.PluginService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
@ -41,6 +42,7 @@ public class SysPluginController {
|
||||
@PostMapping("upload")
|
||||
@RequiresPermissions("plugin:upload")
|
||||
public Map<String, Object> localUpload(@RequestParam("file") MultipartFile file) throws Exception {
|
||||
DeFileUtils.validateFile(file);
|
||||
return pluginService.localInstall(file);
|
||||
}
|
||||
|
||||
@ -54,7 +56,8 @@ public class SysPluginController {
|
||||
@ApiOperation("更新插件")
|
||||
@PostMapping("/update/{pluginId}")
|
||||
@RequiresPermissions("plugin:upload")
|
||||
public Map<String, Object> update(@PathVariable("pluginId") Long pluginId, @RequestParam("file") MultipartFile file) throws Exception{
|
||||
public Map<String, Object> update(@PathVariable("pluginId") Long pluginId, @RequestParam("file") MultipartFile file) throws Exception {
|
||||
DeFileUtils.validateFile(file);
|
||||
if (pluginService.uninstall(pluginId)) {
|
||||
return pluginService.localInstall(file);
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ import io.dataease.plugins.common.base.mapper.ChartViewMapper;
|
||||
import io.dataease.plugins.common.base.mapper.DatasetTableFieldMapper;
|
||||
import io.dataease.plugins.common.base.mapper.PanelViewMapper;
|
||||
import io.dataease.plugins.common.constants.DatasetType;
|
||||
import io.dataease.plugins.common.constants.DatasourceTypes;
|
||||
import io.dataease.plugins.common.constants.datasource.SQLConstants;
|
||||
import io.dataease.plugins.common.dto.chart.ChartCustomFilterItemDTO;
|
||||
import io.dataease.plugins.common.dto.chart.ChartFieldCompareDTO;
|
||||
@ -611,7 +612,7 @@ public class ChartViewService {
|
||||
xAxis.addAll(xAxisExt);
|
||||
}
|
||||
List<ChartViewFieldDTO> yAxis = gson.fromJson(view.getYAxis(), tokenType);
|
||||
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "chart-mix","bidirectional-bar")) {
|
||||
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "chart-mix", "bidirectional-bar")) {
|
||||
List<ChartViewFieldDTO> yAxisExt = gson.fromJson(view.getYAxisExt(), tokenType);
|
||||
yAxis.addAll(yAxisExt);
|
||||
}
|
||||
@ -1126,7 +1127,7 @@ public class ChartViewService {
|
||||
datasourceRequest.setTotalPageFlag(false);
|
||||
data = datasourceProvider.getData(datasourceRequest);
|
||||
if (CollectionUtils.isNotEmpty(assistFields)) {
|
||||
datasourceAssistRequest.setQuery(assistSQL(datasourceRequest.getQuery(), assistFields));
|
||||
datasourceAssistRequest.setQuery(assistSQL(datasourceRequest.getQuery(), assistFields, ds));
|
||||
logger.info(datasourceAssistRequest.getQuery());
|
||||
assistData = datasourceProvider.getData(datasourceAssistRequest);
|
||||
}
|
||||
@ -1158,7 +1159,7 @@ public class ChartViewService {
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isNotEmpty(assistFields)) {
|
||||
datasourceAssistRequest.setQuery(assistSQL(datasourceRequest.getQuery(), assistFields));
|
||||
datasourceAssistRequest.setQuery(assistSQL(datasourceRequest.getQuery(), assistFields, ds));
|
||||
logger.info(datasourceAssistRequest.getQuery());
|
||||
assistData = datasourceProvider.getData(datasourceAssistRequest);
|
||||
}
|
||||
@ -1405,14 +1406,15 @@ public class ChartViewService {
|
||||
return res;
|
||||
}
|
||||
|
||||
public String assistSQL(String sql, List<ChartViewFieldDTO> assistFields) {
|
||||
public String assistSQL(String sql, List<ChartViewFieldDTO> assistFields, Datasource ds) {
|
||||
DatasourceTypes datasourceType = DatasourceTypes.valueOf(ds.getType());
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (int i = 0; i < assistFields.size(); i++) {
|
||||
ChartViewFieldDTO dto = assistFields.get(i);
|
||||
if (i == (assistFields.size() - 1)) {
|
||||
stringBuilder.append(dto.getSummary() + "(" + dto.getOriginName() + ")");
|
||||
stringBuilder.append(dto.getSummary() + "(" + datasourceType.getKeywordPrefix() + dto.getOriginName() + datasourceType.getKeywordSuffix() + ")");
|
||||
} else {
|
||||
stringBuilder.append(dto.getSummary() + "(" + dto.getOriginName() + "),");
|
||||
stringBuilder.append(dto.getSummary() + "(" + datasourceType.getKeywordPrefix() + dto.getOriginName() + datasourceType.getKeywordSuffix() + "),");
|
||||
}
|
||||
}
|
||||
return "SELECT " + stringBuilder + " FROM (" + sql + ") tmp";
|
||||
|
||||
@ -68,7 +68,7 @@ spring.cache.ehcache.config=classpath:/ehcache/ehcache.xml
|
||||
pagehelper.PageRowBounds=true
|
||||
#excel\u7B49\u7528\u6237\u4E0A\u4F20\u6587\u4EF6\u8DEF\u5F84
|
||||
upload.file.path=/opt/dataease/data/kettle/
|
||||
dataease.sqlinjection.whitelists=/dataset/table/sqlPreview,/dataset/table/update,/dataset/field/multFieldValues,/dataset/field/linkMultFieldValues
|
||||
#dataease.sqlinjection.whitelists=/dataset/table/sqlPreview,/dataset/table/update,/dataset/field/multFieldValues,/dataset/field/linkMultFieldValues
|
||||
#\u5F00\u542F\u538B\u7F29 \u63D0\u9AD8\u54CD\u5E94\u901F\u5EA6 \u51CF\u5C11\u5E26\u5BBD\u538B\u529B
|
||||
server.compression.enabled=true
|
||||
server.compression.mime-types=application/javascript,text/css,application/json,application/xml,text/html,text/xml,text/plain
|
||||
|
||||
4
backend/src/main/resources/db/migration/V56__1.18.9.sql
Normal file
4
backend/src/main/resources/db/migration/V56__1.18.9.sql
Normal file
@ -0,0 +1,4 @@
|
||||
UPDATE `my_plugin`
|
||||
SET `version` = '1.18.9'
|
||||
where `plugin_id` > 0
|
||||
and `version` = '1.18.8';
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dataease",
|
||||
"version": "1.18.8",
|
||||
"version": "1.18.9",
|
||||
"description": "dataease front",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -151,13 +151,18 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
previewVisible: false,
|
||||
chart: null,
|
||||
seriesIdMap: {
|
||||
id: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
chart() {
|
||||
if (this.config.propValue?.viewId) {
|
||||
return JSON.parse(this.panelViewDetailsInfo[this.config.propValue.viewId])
|
||||
}
|
||||
return null
|
||||
},
|
||||
componentCanvasId() {
|
||||
if (this.config.type === 'view') {
|
||||
return 'user-view-' + this.config.propValue.viewId
|
||||
@ -216,7 +221,8 @@ export default {
|
||||
'mobileLayoutStatus',
|
||||
'curComponent',
|
||||
'previewCanvasScale',
|
||||
'componentGap'
|
||||
'componentGap',
|
||||
'panelViewDetailsInfo'
|
||||
])
|
||||
},
|
||||
mounted() {
|
||||
@ -286,8 +292,7 @@ export default {
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
...
|
||||
getStyle(style, ['top', 'left', 'width', 'height', 'rotate']),
|
||||
...getStyle(style, ['top', 'left', 'width', 'height', 'rotate']),
|
||||
position: 'relative'
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,7 +294,7 @@ export default {
|
||||
return this.curComponent.type === 'view' && this.terminal === 'pc' && this.curComponent.propValue.innerType && this.curComponent.propValue.innerType !== 'richTextView'
|
||||
},
|
||||
exportExcelShow() {
|
||||
return this.detailsShow && hasDataPermission('export', this.$store.state.panel.panelInfo.privileges)
|
||||
return this.detailsShow && hasDataPermission('export', this.$store.state.panel.panelInfo.privileges) && this.chart
|
||||
},
|
||||
enlargeShow() {
|
||||
return this.curComponent.type === 'view' && this.curComponent.propValue.innerType && this.curComponent.propValue.innerType !== 'richTextView' && !this.curComponent.propValue.innerType.includes('table')
|
||||
|
||||
@ -429,6 +429,9 @@ export function getCacheTree(treeName) {
|
||||
}
|
||||
|
||||
export function exportExcelDownload(chart, snapshot, width, height, loadingWrapper, callBack) {
|
||||
if (!chart.data?.data?.length) {
|
||||
return
|
||||
}
|
||||
const fields = JSON.parse(JSON.stringify(chart.data.fields))
|
||||
const tableRow = JSON.parse(JSON.stringify(chart.data.tableRow))
|
||||
const excelHeader = fields.map(item => item.name)
|
||||
|
||||
1
frontend/src/icons/svg/file-excel.svg
Normal file
1
frontend/src/icons/svg/file-excel.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1687676106978" filter="currentColor" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3283" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326z m1.8 562H232V136h302v216a42 42 0 0 0 42 42h216v494zM514.1 580.1l-61.8-102.4c-2.2-3.6-6.1-5.8-10.3-5.8h-38.4c-2.3 0-4.5 0.6-6.4 1.9-5.6 3.5-7.3 10.9-3.7 16.6l82.3 130.4-83.4 132.8a12.04 12.04 0 0 0 10.2 18.4h34.5c4.2 0 8-2.2 10.2-5.7L510 664.8l62.3 101.4c2.2 3.6 6.1 5.7 10.2 5.7H620c2.3 0 4.5-0.7 6.5-1.9 5.6-3.6 7.2-11 3.6-16.6l-84-130.4 85.3-132.5a12.04 12.04 0 0 0-10.1-18.5h-35.7c-4.2 0-8.1 2.2-10.3 5.8l-61.2 102.3z" p-id="3284"></path></svg>
|
||||
|
After Width: | Height: | Size: 988 B |
@ -935,6 +935,8 @@ export default {
|
||||
password_input_error: 'Original password input error'
|
||||
},
|
||||
chart: {
|
||||
empty_hide: 'hide empty',
|
||||
hide: 'hide',
|
||||
chart_refresh_tips: 'View refresh setting takes precedence over panel refresh setting',
|
||||
'1-trend': 'trend',
|
||||
'2-state': 'State',
|
||||
|
||||
@ -934,6 +934,8 @@ export default {
|
||||
password_input_error: '原始密碼輸入錯誤'
|
||||
},
|
||||
chart: {
|
||||
empty_hide: '隱藏空值',
|
||||
hide: '隱藏',
|
||||
chart_refresh_tips: '視圖刷新設置優先於儀表板刷新設置',
|
||||
'1-trend': '趨勢',
|
||||
'2-state': '狀態',
|
||||
|
||||
@ -933,6 +933,8 @@ export default {
|
||||
password_input_error: '原始密码输入错误'
|
||||
},
|
||||
chart: {
|
||||
empty_hide: '隐藏空值',
|
||||
hide: '隐藏',
|
||||
chart_refresh_tips: '视图刷新设置优先于仪表板刷新设置',
|
||||
'1-trend': '趋势',
|
||||
'2-state': '状态',
|
||||
|
||||
@ -72,9 +72,13 @@ export function baseMapOption(chart_option, geoJson, chart, themeStyle, curAreaC
|
||||
const reg = new RegExp('\n', 'g')
|
||||
const text = tooltip.formatter.replace(reg, '<br/>')
|
||||
tooltip.formatter = params => {
|
||||
const val = params.value
|
||||
if (tooltip.emptyHide && (val === null || typeof val === 'undefined' || isNaN(val))) {
|
||||
return ''
|
||||
}
|
||||
const a = params.seriesName
|
||||
const b = params.name
|
||||
const c = params.value ?? ''
|
||||
const c = (val === null || typeof val === 'undefined' || isNaN(val)) ? '' : val
|
||||
return text.replace(new RegExp('{a}', 'g'), a).replace(new RegExp('{b}', 'g'), b).replace(new RegExp('{c}', 'g'), c)
|
||||
}
|
||||
chart_option.tooltip = tooltip
|
||||
|
||||
@ -3323,6 +3323,7 @@ export const TYPE_CONFIGS = [
|
||||
],
|
||||
'tooltip-selector': [
|
||||
'show',
|
||||
'emptyHide',
|
||||
'textStyle',
|
||||
'formatter'
|
||||
],
|
||||
|
||||
@ -18,6 +18,16 @@
|
||||
>{{ $t('chart.show') }}</el-checkbox>
|
||||
</el-form-item>
|
||||
<div v-show="tooltipForm.show">
|
||||
<el-form-item
|
||||
v-show="showProperty('emptyHide')"
|
||||
:label="$t('chart.empty_hide')"
|
||||
class="form-item"
|
||||
>
|
||||
<el-checkbox
|
||||
v-model="tooltipForm.emptyHide"
|
||||
@change="changeTooltipAttr('emptyHide')"
|
||||
>{{ $t('chart.hide') }}</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-show="showProperty('trigger')"
|
||||
:label="$t('chart.trigger_position')"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dataease-mobile",
|
||||
"version": "1.18.8",
|
||||
"version": "1.18.9",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "npm run dev:h5",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user