datalist fields-privileges

This commit is contained in:
devezhao-mbp 2019-06-07 20:02:58 +08:00
parent c1eeccd94c
commit 210cf9d956
8 changed files with 210 additions and 124 deletions

View file

@ -27,6 +27,7 @@ import org.apache.commons.lang.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.configuration.ConfigEntry;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
@ -47,13 +48,23 @@ public class DataListManager extends BaseLayoutManager {
public static final DataListManager instance = new DataListManager();
private DataListManager() { }
/**
* @param belongEntity
* @param user
* @return
*/
public JSON getColumnLayout(String belongEntity, ID user) {
return getColumnLayout(belongEntity, user, true);
}
/**
* @param belongEntity
* @param user
* @param validPrivileges
* @return
*/
public JSON getColumnLayout(String belongEntity, ID user, boolean validPrivileges) {
ConfigEntry config = getLayoutOfDatalist(user, belongEntity);
List<Map<String, Object>> columnList = new ArrayList<>();
@ -77,8 +88,8 @@ public class DataListManager extends BaseLayoutManager {
for (Object o : (JSONArray) config.getJSON("config")) {
JSONObject item = (JSONObject) o;
String field = item.getString("field");
Field validField = MetadataHelper.getLastJoinField(entityMeta, field);
if (validField == null) {
Field lastField = MetadataHelper.getLastJoinField(entityMeta, field);
if (lastField == null) {
LOG.warn("Unknow field '" + field + "' in '" + belongEntity + "'");
continue;
}
@ -86,22 +97,31 @@ public class DataListManager extends BaseLayoutManager {
String fieldPath[] = field.split("\\.");
Map<String, Object> formatted = null;
if (fieldPath.length == 1) {
formatted = formatColumn(validField);
formatted = formatColumn(lastField);
} else {
Field refField = entityMeta.getField(fieldPath[0]);
formatted = formatColumn(validField, refField);
// 如果没有引用实体的读权限则直接过滤掉字段
Field parentField = entityMeta.getField(fieldPath[0]);
if (!validPrivileges) {
formatted = formatColumn(lastField, parentField);
} else if (Application.getSecurityManager().allowedR(user, lastField.getOwnEntity().getEntityCode())) {
formatted = formatColumn(lastField, parentField);
}
}
Integer width = item.getInteger("width");
if (width != null) {
formatted.put("width", width);
if (formatted != null) {
Integer width = item.getInteger("width");
if (width != null) {
formatted.put("width", width);
}
columnList.add(formatted);
}
columnList.add(formatted);
}
}
return JSONUtils.toJSONObject(
new String[] { "entity", "nameField", "fields" },
new String[] { "entity", "nameField", "fields" },
new Object[] { belongEntity, namedField.getName(), columnList });
}

View file

@ -18,6 +18,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.helper.datalist;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
@ -39,9 +44,9 @@ import cn.devezhao.persist4j.engine.ID;
* @author Zhao Fangfang
* @since 1.0, 2013-6-20
*/
public class JSONQueryParser {
public class DataListQueryParser {
protected JSONObject queryExpressie;
protected JSONObject queryExpr;
private DataList dataListControl;
private Entity entity;
@ -51,30 +56,24 @@ public class JSONQueryParser {
private int[] limit;
private boolean reload;
private Map<String, Integer> queryJoinFields;
/**
* @param queryExpressie
* @param queryExpr
*/
public JSONQueryParser(JSONObject queryExpressie) {
this(queryExpressie, null);
public DataListQueryParser(JSONObject queryExpr) {
this(queryExpr, null);
}
/**
* @param queryExpressie
* @param queryExpr
* @param dataListControl
*/
public JSONQueryParser(JSONObject queryExpressie, DataList dataListControl) {
this.queryExpressie = queryExpressie;
public DataListQueryParser(JSONObject queryExpr, DataList dataListControl) {
this.queryExpr = queryExpr;
this.dataListControl = dataListControl;
this.entity = dataListControl != null ?
dataListControl.getEntity() : MetadataHelper.getEntity(queryExpressie.getString("entity"));
}
/**
* @return
*/
protected Entity getEntity() {
return entity;
dataListControl.getEntity() : MetadataHelper.getEntity(queryExpr.getString("entity"));
}
/**
@ -96,7 +95,14 @@ public class JSONQueryParser {
/**
* @return
*/
public int[] getSqlLimit() {
protected Entity getEntity() {
return entity;
}
/**
* @return
*/
protected int[] getSqlLimit() {
doParseIfNeed();
return limit;
}
@ -104,32 +110,58 @@ public class JSONQueryParser {
/**
* @return
*/
public boolean isNeedReload() {
protected boolean isNeedReload() {
doParseIfNeed();
return reload;
}
/**
* @return
*/
protected Map<String, Integer> getQueryJoinFields() {
return queryJoinFields;
}
/**
* 解析 SQL
*/
protected void doParseIfNeed() {
private void doParseIfNeed() {
if (sql != null) {
return;
}
StringBuffer sqlBase = new StringBuffer("select ");
JSONArray fieldsNode = queryExpressie.getJSONArray("fields");
JSONArray fieldsNode = queryExpr.getJSONArray("fields");
int fieldIndex = -1;
Set<String> queryJoinFields = new HashSet<>();
for (Object o : fieldsNode) {
// DataListManager 中已验证字段有效此处不再次验证
String field = o.toString().trim();
sqlBase.append(field).append(',');
fieldIndex++;
if (field.split("\\.").length > 1) {
queryJoinFields.add(field.split("\\.")[0]);
}
}
// 最后增加一个主键列
String pkName = entity.getPrimaryField().getName();
sqlBase.append(pkName)
.append(" from ")
.append(entity.getName());
sqlBase.append(pkName);
fieldIndex++;
// 查询关联项 ID 以验证权限
if (!queryJoinFields.isEmpty()) {
this.queryJoinFields = new HashMap<>();
for (String field : queryJoinFields) {
sqlBase.append(',').append(field);
fieldIndex++;
this.queryJoinFields.put(field, fieldIndex);
}
}
sqlBase.append(" from ").append(entity.getName());
// 过滤器
@ -141,7 +173,7 @@ public class JSONQueryParser {
sqlWhere.append(" and (").append(defaultFilter).append(')');
}
// Adv
String advExpId = queryExpressie.getString("advFilter");
String advExpId = queryExpr.getString("advFilter");
if (ID.isId(advExpId)) {
ConfigEntry adv = AdvFilterManager.instance.getAdvFilter(ID.valueOf(advExpId));
if (adv != null) {
@ -152,7 +184,7 @@ public class JSONQueryParser {
}
}
// Quick
JSONObject quickExp = queryExpressie.getJSONObject("filter");
JSONObject quickExp = queryExpr.getJSONObject("filter");
if (quickExp != null) {
String where = new AdvFilterParser(entity, quickExp).toSqlWhere();
if (StringUtils.isNotBlank(where)) {
@ -165,7 +197,7 @@ public class JSONQueryParser {
StringBuffer sqlSort = new StringBuffer(" order by ");
String sortNode = queryExpressie.getString("sort");
String sortNode = queryExpr.getString("sort");
if (StringUtils.isNotBlank(sortNode)) {
sqlSort.append(parseSort(sortNode));
} else if (entity.containsField(EntityHelper.ModifiedOn)) {
@ -185,12 +217,12 @@ public class JSONQueryParser {
.append(sqlWhere)
.toString();
int pageNo = NumberUtils.toInt(queryExpressie.getString("pageNo"), 1);
int pageSize = NumberUtils.toInt(queryExpressie.getString("pageSize"), 20);
int pageNo = NumberUtils.toInt(queryExpr.getString("pageNo"), 1);
int pageSize = NumberUtils.toInt(queryExpr.getString("pageSize"), 20);
this.limit = new int[] { pageSize, pageNo * pageSize - pageSize };
this.reload = limit[1] == 0;
if (!reload) {
reload = BooleanUtils.toBoolean(queryExpressie.getString("reload"));
reload = BooleanUtils.toBoolean(queryExpr.getString("reload"));
}
}

View file

@ -18,6 +18,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
package com.rebuild.server.helper.datalist;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -45,6 +47,8 @@ import cn.devezhao.persist4j.query.compiler.SelectItem;
public class DataWrapper {
private static final Log LOG = LogFactory.getLog(DataWrapper.class);
private static final String NO_READ_PRIVILEGES = "$NOPRIVILEGES$";
private int total;
private Object[][] data;
@ -52,6 +56,10 @@ public class DataWrapper {
private SelectItem[] selectFields;
private Entity entity;
// for 权限验证
private ID user;
private Map<String, Integer> queryJoinFields;
/**
* @param total
* @param data
@ -65,35 +73,63 @@ public class DataWrapper {
this.entity = entity;
}
/**
* @param user
* @param joinFields
*/
protected void setPrivilegesFilter(ID user, Map<String, Integer> joinFields) {
if (user != null && joinFields != null && !joinFields.isEmpty()) {
this.user = user;
this.queryJoinFields = joinFields;
}
}
/**
* @return
*/
public JSON toJson() {
final Field namedFiled = MetadataHelper.getNameField(entity);
for (Object[] row : data) {
final int joinFieldsLen = queryJoinFields == null ? 0 : queryJoinFields.size();
final int selectFieldsLen = selectFields.length - joinFieldsLen;
for (int i = 0; i < data.length; i++) {
final Object[] original = data[i];
Object[] row = original;
if (joinFieldsLen > 0) {
row = new Object[selectFieldsLen];
System.arraycopy(original, 0, row, 0, selectFieldsLen);
data[i] = row;
}
Object namedVal = null;
for (int i = 0; i < selectFields.length; i++) {
if (row[i] == null) {
row[i] = StringUtils.EMPTY;
for (int j = 0; j < selectFieldsLen; j++) {
if (!checkHasJoinFieldPrivileges(selectFields[j], original)) {
row[j] = NO_READ_PRIVILEGES;
continue;
}
Field field = selectFields[i].getField();
if (row[j] == null) {
row[j] = StringUtils.EMPTY;
continue;
}
Field field = selectFields[j].getField();
if (field.equals(namedFiled)) {
namedVal = row[i];
namedVal = row[j];
}
if (field.getType() == FieldType.REFERENCE) {
int rec = field.getReferenceEntity().getEntityCode();
if (rec == EntityHelper.ClassificationData || rec == EntityHelper.PickList) {
row[i] = FieldValueWrapper.wrapFieldValue(row[i], EasyMeta.valueOf(field));
row[j] = FieldValueWrapper.wrapFieldValue(row[j], EasyMeta.valueOf(field));
} else {
row[i] = readReferenceRichs((ID) row[i], null);
row[j] = readReferenceRich((ID) row[j], null);
}
} else if (field.getType() == FieldType.PRIMARY) { // Last index always
row[i] = readReferenceRichs((ID) row[i], namedVal);
row[j] = readReferenceRich((ID) row[j], namedVal);
} else {
row[i] = FieldValueWrapper.wrapFieldValue(row[i], new EasyMeta(field));
row[j] = FieldValueWrapper.wrapFieldValue(row[j], new EasyMeta(field));
}
}
}
@ -110,7 +146,7 @@ public class DataWrapper {
* @param nameVal
* @return Returns [ID, Name(Field), EntityMeta[Name, Icon]]
*/
private Object[] readReferenceRichs(ID idVal, Object nameVal) {
private Object[] readReferenceRich(ID idVal, Object nameVal) {
Entity entity = MetadataHelper.getEntity(idVal.getEntityCode());
Field nameField = MetadataHelper.getNameField(entity);
@ -129,4 +165,29 @@ public class DataWrapper {
String[] metadata = new String[] { entity.getName(), new EasyMeta(entity).getIcon() };
return new Object[] { idVal, nameVal, metadata };
}
/**
* 验证字段权限
*
* @param field
* @param original
* @return
*/
private boolean checkHasJoinFieldPrivileges(SelectItem field, Object[] original) {
if (this.queryJoinFields == null) {
return true;
}
String fieldPath[] = field.getFieldPath().split("\\.");
if (fieldPath.length == 1) {
return true;
}
int fieldIndex = queryJoinFields.get(fieldPath[0]);
Object check = original[fieldIndex];
if (check != null && !Application.getSecurityManager().allowedR(user, (ID) check)) {
return false;
}
return true;
}
}

View file

@ -39,7 +39,7 @@ import cn.devezhao.persist4j.engine.ID;
public class DefaultDataList implements DataList {
private Entity entity;
private JSONQueryParser queryParser;
private DataListQueryParser queryParser;
private ID user;
/**
@ -48,7 +48,7 @@ public class DefaultDataList implements DataList {
*/
public DefaultDataList(JSONObject query, ID user) {
this.entity = MetadataHelper.getEntity(query.getString("entity"));
this.queryParser = new JSONQueryParser(query, this);
this.queryParser = new DataListQueryParser(query, this);
this.user = user;
}
@ -59,10 +59,11 @@ public class DefaultDataList implements DataList {
@Override
public String getDefaultFilter() {
// 列表默认过滤
int ec = queryParser.getEntity().getEntityCode();
// TODO 可配置的列表默认过滤
if (queryParser.getEntity().getEntityCode() == EntityHelper.User) {
if (ec == EntityHelper.User) {
return String.format("userId <> '%s'", UserService.SYSTEM_USER);
}
return null;
@ -82,6 +83,7 @@ public class DefaultDataList implements DataList {
DataWrapper wrapper = new DataWrapper(
totalRows, array, query.getSelectItems(), query.getRootEntity());
wrapper.setPrivilegesFilter(user, queryParser.getQueryJoinFields());
return wrapper.toJson();
}
}

View file

@ -119,13 +119,18 @@ public class DataListSettingsControll extends BaseControll implements PortalsCon
continue;
}
Entity refEntity = field.getReferenceEntity();
// 无权限的不返回
if (!Application.getSecurityManager().allowedR(user, refEntity.getEntityCode())) {
continue;
}
for (Field field4Ref : MetadataSorter.sortFields(refEntity)) {
fieldList.add(DataListManager.instance.formatColumn(field4Ref, field));
}
}
ConfigEntry raw = DataListManager.instance.getLayoutOfDatalist(user, entity);
JSONObject config = (JSONObject) DataListManager.instance.getColumnLayout(entity, user);
JSONObject config = (JSONObject) DataListManager.instance.getColumnLayout(entity, user, false);
Map<String, Object> ret = new HashMap<>();
ret.put("fieldList", fieldList);

View file

@ -936,6 +936,11 @@ i.split.ui-draggable-dragging {
padding: 8px !important;
}
.column-nopriv {
color: #999;
cursor: help;
}
.text-3dot {
overflow: hidden;
text-overflow: ellipsis;

View file

@ -168,9 +168,11 @@ class RbList extends React.Component {
const field = this.state.fields[index]
if (!field) return null
const cellKey = 'row-' + lastGhost[0] + '-' + index
if (!cellVal) {
let cellKey = 'row-' + lastGhost[0] + '-' + index
return <td key={cellKey}><div></div></td>
} else if (cellVal === '$NOPRIVILEGES$') {
return <td key={cellKey}><div className="column-nopriv" title="你无权读取此项数据">[无权限]</div></td>
} else {
let w = this.state.fields[index].width || this.__defaultColumnWidth
let t = field.type
@ -178,43 +180,8 @@ class RbList extends React.Component {
cellVal = lastGhost
t = '$NAME$'
}
return CellRenders.render(cellVal, t, w)
return CellRenders.render(cellVal, t, w, cellKey + '.' + field.field)
}
// let styles = { width: (this.state.fields[index].width || this.__defaultColumnWidth) + 'px' }
// if (field.type === 'IMAGE') {
// cellVal = JSON.parse(cellVal || '[]')
// return (<td key={cellKey} className="td-min">
// <div style={styles} className="column-imgs" title={cellVal.length + ' '}>
// {cellVal.map((item, idx) => {
// let imgUrl = rb.baseUrl + '/filex/img/' + item
// let imgName = $fileCutName(item)
// return <a key={cellKey + idx} href={'#!/Preview/' + item} title={imgName}><img src={imgUrl + '?imageView2/2/w/100/interlace/1/q/100'} /></a>
// })}</div></td>)
// } else if (field.type === 'FILE') {
// cellVal = JSON.parse(cellVal || '[]')
// return (<td key={cellKey} className="td-min"><div style={styles} className="column-files">
// <ul className="list-unstyled" title={cellVal.length + ' '}>
// {cellVal.map((item, idx) => {
// let fileName = $fileCutName(item)
// return <li key={cellKey + idx} className="text-truncate"><a href={'#!/Preview/' + item} title={fileName}>{fileName}</a></li>
// })}</ul>
// </div></td>)
// } else if (field.type === 'REFERENCE') {
// return <td key={cellKey}><div style={styles}><a href={'#!/View/' + cellVal[2][0] + '/' + cellVal[0]} onClick={() => this.clickView(cellVal)}>{cellVal[1]}</a></div></td>
// } else if (field.field === this.props.config.nameField) {
// cellVal = lastGhost
// return <td key={cellKey}><div style={styles}><a href={'#!/View/' + cellVal[2][0] + '/' + cellVal[0]} onClick={() => this.clickView(cellVal)} className="column-main">{cellVal[1]}</a></div></td>
// } else if (field.type === 'URL') {
// return <td key={cellKey}><div style={styles}><a href={rb.baseUrl + '/common/url-safe?url=' + encodeURIComponent(cellVal)} className="column-url" target="_blank" rel="noopener noreferrer">{cellVal}</a></div></td>
// } else if (field.type === 'EMAIL') {
// return <td key={cellKey}><div style={styles}><a href={'mailto:' + cellVal} className="column-url">{cellVal}</a></div></td>
// } else if (field.type === 'AVATAR') {
// let imgUrl = rb.baseUrl + '/filex/img/' + cellVal + '?imageView2/2/w/100/interlace/1/q/100'
// return <td key={cellKey} className="user-avatar"><img src={imgUrl} alt="Avatar" /></td>
// } else {
// return <td key={cellKey}><div style={styles}>{cellVal}</div></td>
// }
}
toggleAllRow() {
@ -248,11 +215,6 @@ class RbList extends React.Component {
return false
}
clickView(cellVal) {
rb.RbViewModal({ id: cellVal[0], entity: cellVal[2][0] })
return false
}
sortField(field, e) {
let fields = this.state.fields
for (let i = 0; i < fields.length; i++) {
@ -324,24 +286,24 @@ var CellRenders = {
addRender(type, func) {
this.__renders[type] = func
},
/**
* @param {*} value
* @param {*} type
* @param {*} width
*/
render(value, type, width) {
clickView(v) {
rb.RbViewModal({ id: v[0], entity: v[2][0] })
return false
},
render(value, type, width, key) {
let style = { width: (width || COLUMN_MIN_WIDTH) + 'px' }
let func = this.__renders[type]
if (func) return func(value, style)
else return <td><div style={style}>{value}</div></td>
if (func) return func(value, style, key)
else return <td key={key}><div style={style}>{value}</div></td>
}
}
CellRenders.addRender('$NAME$', function (v, s) {
return <td><div style={s}><a href={'#!/View/' + v[2][0] + '/' + v[0]} onClick={() => this.clickView(v)} className="column-main">{v[1]}</a></div></td>
CellRenders.addRender('$NAME$', function (v, s, k) {
return <td key={k}><div style={s}><a href={'#!/View/' + v[2][0] + '/' + v[0]} onClick={() => this.clickView(v)} className="column-main">{v[1]}</a></div></td>
})
CellRenders.addRender('IMAGE', function (v, s) {
CellRenders.addRender('IMAGE', function (v, s, k) {
v = JSON.parse(v || '[]')
return <td className="td-min">
return <td key={k} className="td-min">
<div style={s} className="column-imgs" title={v.length + ' 个图片'}>
{v.map((item) => {
let imgUrl = rb.baseUrl + '/filex/img/' + item
@ -349,9 +311,9 @@ CellRenders.addRender('IMAGE', function (v, s) {
return <a key={'k-' + item} href={'#!/Preview/' + item} title={imgName}><img src={imgUrl + '?imageView2/2/w/100/interlace/1/q/100'} /></a>
})}</div></td>
})
CellRenders.addRender('FILE', function (v, s) {
CellRenders.addRender('FILE', function (v, s, k) {
v = JSON.parse(v || '[]')
return <td className="td-min"><div style={s} className="column-files">
return <td key={k} className="td-min"><div style={s} className="column-files">
<ul className="list-unstyled" title={v.length + ' 个文件'}>
{v.map((item) => {
let fileName = $fileCutName(item)
@ -359,19 +321,18 @@ CellRenders.addRender('FILE', function (v, s) {
})}</ul>
</div></td>
})
CellRenders.addRender('REFERENCE', function (v, s) {
return <td><div style={s}><a href={'#!/View/' + v[2][0] + '/' + v[0]} onClick={() => this.clickView(v)}>{v[1]}</a></div></td>
CellRenders.addRender('REFERENCE', function (v, s, k) {
return <td key={k}><div style={s}><a href={'#!/View/' + v[2][0] + '/' + v[0]} onClick={() => this.clickView(v)}>{v[1]}</a></div></td>
})
CellRenders.addRender('URL', function (v, s) {
return <td><div style={s}><a href={rb.baseUrl + '/common/url-safe?url=' + encodeURIComponent(v)} className="column-url" target="_blank" rel="noopener noreferrer">{v}</a></div></td>
CellRenders.addRender('URL', function (v, s, k) {
return <td key={k}><div style={s}><a href={rb.baseUrl + '/common/url-safe?url=' + $encode(v)} className="column-url" target="_blank" rel="noopener noreferrer">{v}</a></div></td>
})
CellRenders.addRender('EMAIL', function (v, s) {
return <td><div style={s}><a href={'mailto:' + v} className="column-url">{v}</a></div></td>
CellRenders.addRender('EMAIL', function (v, s, k) {
return <td key={k}><div style={s}><a href={'mailto:' + v} className="column-url">{v}</a></div></td>
})
CellRenders.addRender('AVATAR', function (v) {
CellRenders.addRender('AVATAR', function (v, s, k) {
let imgUrl = rb.baseUrl + '/filex/img/' + v + '?imageView2/2/w/100/interlace/1/q/100'
return <td className="user-avatar"><img src={imgUrl} alt="Avatar" /></td>
return <td key={k} className="user-avatar"><img src={imgUrl} alt="Avatar" /></td>
})
//

View file

@ -49,7 +49,7 @@ public class DataListTest extends TestSupport {
@Test
public void testQueryParser() throws Exception {
JSONQueryParser queryParser = new JSONQueryParser(queryExpressie);
DataListQueryParser queryParser = new DataListQueryParser(queryExpressie);
System.out.println(queryParser.toSql());
System.out.println(queryParser.toCountSql());
}