mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Include helper columns in ACL rules
Summary: Extend the way ACL resources are read in the server so that if a rule applies to a specific column then that rule also applies to helper columns belonging to that column, as well as helper columns belonging to fields which display that column. This is particularly intended for display columns of reference columns, but it also applies to conditional formatting rule columns. Test Plan: Added a server test Reviewers: paulfitz, jarek Reviewed By: paulfitz, jarek Differential Revision: https://phab.getgrist.com/D3628
This commit is contained in:
@@ -4,6 +4,7 @@ import {DocData} from 'app/common/DocData';
|
||||
import {AclMatchFunc, ParsedAclFormula, RulePart, RuleSet, UserAttributeRule} from 'app/common/GranularAccessClause';
|
||||
import {getSetMapValue} from 'app/common/gutil';
|
||||
import {MetaRowRecord} from 'app/common/TableData';
|
||||
import {decodeObject} from 'app/plugin/objtypes';
|
||||
import sortBy = require('lodash/sortBy');
|
||||
|
||||
const defaultMatchFunc: AclMatchFunc = () => true;
|
||||
@@ -290,6 +291,11 @@ export class ACLRuleCollection {
|
||||
export interface ReadAclOptions {
|
||||
log: ILogger; // For logging warnings during rule processing.
|
||||
compile?: (parsed: ParsedAclFormula) => AclMatchFunc;
|
||||
// If true, call addHelperCols to add helper columns of restricted columns to rule sets.
|
||||
// Used in the server for extra filtering, but not in the client, because:
|
||||
// 1. They would show in the UI
|
||||
// 2. They would be saved back after editing, causing them to accumulate
|
||||
includeHelperCols?: boolean;
|
||||
}
|
||||
|
||||
export interface ReadAclResults {
|
||||
@@ -297,11 +303,66 @@ export interface ReadAclResults {
|
||||
userAttributes: UserAttributeRule[];
|
||||
}
|
||||
|
||||
/**
|
||||
* For each column in colIds, return the colIds of any hidden helper columns it has,
|
||||
* i.e. display columns of references, and conditional formatting rule columns.
|
||||
*/
|
||||
function getHelperCols(docData: DocData, tableId: string, colIds: string[], log: ILogger): string[] {
|
||||
const tablesTable = docData.getMetaTable('_grist_Tables');
|
||||
const columnsTable = docData.getMetaTable('_grist_Tables_column');
|
||||
const fieldsTable = docData.getMetaTable('_grist_Views_section_field');
|
||||
|
||||
const tableRef = tablesTable.findRow('tableId', tableId);
|
||||
if (!tableRef) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: string[] = [];
|
||||
for (const colId of colIds) {
|
||||
const [column] = columnsTable.filterRecords({parentId: tableRef, colId});
|
||||
if (!column) {
|
||||
continue;
|
||||
}
|
||||
|
||||
function addColsFromRefs(colRefs: unknown) {
|
||||
if (!Array.isArray(colRefs)) {
|
||||
return;
|
||||
}
|
||||
for (const colRef of colRefs) {
|
||||
if (typeof colRef !== 'number') {
|
||||
continue;
|
||||
}
|
||||
const extraCol = columnsTable.getRecord(colRef);
|
||||
if (!extraCol) {
|
||||
continue;
|
||||
}
|
||||
if (extraCol.colId.startsWith("gristHelper_") && extraCol.parentId === tableRef) {
|
||||
result.push(extraCol.colId);
|
||||
} else {
|
||||
log.error(`Invalid helper column ${extraCol.colId} of ${tableId}:${colId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addColsFromMetaRecord(rec: MetaRowRecord<'_grist_Tables_column' | '_grist_Views_section_field'>) {
|
||||
addColsFromRefs([rec.displayCol]);
|
||||
addColsFromRefs(decodeObject(rec.rules));
|
||||
}
|
||||
|
||||
addColsFromMetaRecord(column);
|
||||
for (const field of fieldsTable.filterRecords({colRef: column.id})) {
|
||||
addColsFromMetaRecord(field);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse all ACL rules in the document from DocData into a list of RuleSets and of
|
||||
* UserAttributeRules. This is used by both client-side code and server-side.
|
||||
*/
|
||||
function readAclRules(docData: DocData, {log, compile}: ReadAclOptions): ReadAclResults {
|
||||
function readAclRules(docData: DocData, {log, compile, includeHelperCols}: ReadAclOptions): ReadAclResults {
|
||||
const resourcesTable = docData.getMetaTable('_grist_ACLResources');
|
||||
const rulesTable = docData.getMetaTable('_grist_ACLRules');
|
||||
|
||||
@@ -328,6 +389,10 @@ function readAclRules(docData: DocData, {log, compile}: ReadAclOptions): ReadAcl
|
||||
const tableId = resourceRec.tableId;
|
||||
const colIds = resourceRec.colIds === '*' ? '*' : resourceRec.colIds.split(',');
|
||||
|
||||
if (includeHelperCols && Array.isArray(colIds)) {
|
||||
colIds.push(...getHelperCols(docData, tableId, colIds, log));
|
||||
}
|
||||
|
||||
const body: RulePart[] = [];
|
||||
for (const rule of rules) {
|
||||
if (rule.userAttributes) {
|
||||
|
||||
@@ -110,7 +110,7 @@ export class TableData extends ActionDispatcher implements SkippableRows {
|
||||
|
||||
reassignArray(this._rowIdCol, rowIds);
|
||||
for (const colData of this._colArray) {
|
||||
const values = colValues[colData.colId];
|
||||
const values = colData.colId === 'id' ? rowIds : colValues[colData.colId];
|
||||
// If colId is missing from tableData, use an array of default values. Note that reusing
|
||||
// default value like this is only OK because all default values we use are primitive.
|
||||
reassignArray(colData.values, values || this._rowIdCol.map(() => colData.defl));
|
||||
|
||||
Reference in New Issue
Block a user