Merge branch 'dev' into pr@dev_st_fix

This commit is contained in:
dataeaseShu 2023-07-03 10:35:51 +08:00
commit 7312a2624a
19 changed files with 95 additions and 41 deletions

View File

@ -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");
}
}
/**
* 将文件名解析成文件的上传路径
*/

View 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;
}

View File

@ -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);
}

View 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";

View File

@ -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

View File

@ -0,0 +1,4 @@
UPDATE `my_plugin`
SET `version` = '1.18.9'
where `plugin_id` > 0
and `version` = '1.18.8';

View File

@ -1,6 +1,6 @@
{
"name": "dataease",
"version": "1.18.8",
"version": "1.18.9",
"description": "dataease front",
"private": true,
"scripts": {

View File

@ -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'
}
}

View File

@ -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')

View File

@ -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)

View 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

View File

@ -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',

View File

@ -934,6 +934,8 @@ export default {
password_input_error: '原始密碼輸入錯誤'
},
chart: {
empty_hide: '隱藏空值',
hide: '隱藏',
chart_refresh_tips: '視圖刷新設置優先於儀表板刷新設置',
'1-trend': '趨勢',
'2-state': '狀態',

View File

@ -933,6 +933,8 @@ export default {
password_input_error: '原始密码输入错误'
},
chart: {
empty_hide: '隐藏空值',
hide: '隐藏',
chart_refresh_tips: '视图刷新设置优先于仪表板刷新设置',
'1-trend': '趋势',
'2-state': '状态',

View File

@ -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

View File

@ -3323,6 +3323,7 @@ export const TYPE_CONFIGS = [
],
'tooltip-selector': [
'show',
'emptyHide',
'textStyle',
'formatter'
],

View File

@ -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')"

View File

@ -1,6 +1,6 @@
{
"name": "dataease-mobile",
"version": "1.18.8",
"version": "1.18.9",
"private": true,
"scripts": {
"serve": "npm run dev:h5",

View File

@ -16,7 +16,7 @@
</parent>
<properties>
<dataease.version>1.18.8</dataease.version>
<dataease.version>1.18.9</dataease.version>
</properties>
<name>dataease</name>