(core) Hiding helper columns used for column transformation

Summary:
When a column is transformed, it creates two helper columns whose values are always
broadcasted to all clients. Now when there are some ACL rules, we are going to prune
those columns from messages sent to other connected clients.

Test Plan: Added new tests

Reviewers: dsagal, paulfitz

Reviewed By: dsagal, paulfitz

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D3728
This commit is contained in:
Jarosław Sadziński 2022-12-19 17:52:44 +01:00
parent 1a4561dbf2
commit 2a86cde474
2 changed files with 54 additions and 2 deletions

View File

@ -31,7 +31,7 @@ export type TableDataAction = ['TableData', string, number[], BulkColValues];
export type AddColumn = ['AddColumn', string, string, ColInfo]; export type AddColumn = ['AddColumn', string, string, ColInfo];
export type RemoveColumn = ['RemoveColumn', string, string]; export type RemoveColumn = ['RemoveColumn', string, string];
export type RenameColumn = ['RenameColumn', string, string, string]; export type RenameColumn = ['RenameColumn', string, string, string];
export type ModifyColumn = ['ModifyColumn', string, string, ColInfo]; export type ModifyColumn = ['ModifyColumn', string, string, Partial<ColInfo>];
export type AddTable = ['AddTable', string, ColInfoWithId[]]; export type AddTable = ['AddTable', string, ColInfoWithId[]];
export type RemoveTable = ['RemoveTable', string]; export type RemoveTable = ['RemoveTable', string];

View File

@ -40,7 +40,8 @@ import { getDocSessionAccess, getDocSessionAltSessionId, getDocSessionUser,
OptDocSession } from 'app/server/lib/DocSession'; OptDocSession } from 'app/server/lib/DocSession';
import { DocStorage, REMOVE_UNUSED_ATTACHMENTS_DELAY } from 'app/server/lib/DocStorage'; import { DocStorage, REMOVE_UNUSED_ATTACHMENTS_DELAY } from 'app/server/lib/DocStorage';
import log from 'app/server/lib/log'; import log from 'app/server/lib/log';
import { IPermissionInfo, PermissionInfo, PermissionSetWithContext } from 'app/server/lib/PermissionInfo'; import { IPermissionInfo, MixedPermissionSetWithContext,
PermissionInfo, PermissionSetWithContext } from 'app/server/lib/PermissionInfo';
import { TablePermissionSetWithContext } from 'app/server/lib/PermissionInfo'; import { TablePermissionSetWithContext } from 'app/server/lib/PermissionInfo';
import { integerParam } from 'app/server/lib/requestUtils'; import { integerParam } from 'app/server/lib/requestUtils';
import { getColIdsFromDocAction, getColValuesFromDocAction, getRelatedRows, import { getColIdsFromDocAction, getColValuesFromDocAction, getRelatedRows,
@ -191,6 +192,17 @@ const UPLOADED_ATTACHMENT_OWNERSHIP_PERIOD =
// older than this limit. // older than this limit.
const HISTORICAL_ATTACHMENT_OWNERSHIP_PERIOD = 24 * 60 * 60 * 1000; const HISTORICAL_ATTACHMENT_OWNERSHIP_PERIOD = 24 * 60 * 60 * 1000;
// Transform columns are special. In case we have some rules defined they are only visible
// to those with SCHEMA_EDIT permission.
const TRANSFORM_COLUMN_PREFIXES = ['gristHelper_Converted', 'gristHelper_Transform'];
/**
* Checks if this is a special helper column used during type conversion.
*/
function isTransformColumn(colId: string): boolean {
return TRANSFORM_COLUMN_PREFIXES.some(prefix => colId.startsWith(prefix));
}
interface DocUpdateMessage { interface DocUpdateMessage {
actionGroup: ActionGroup; actionGroup: ActionGroup;
docActions: DocAction[]; docActions: DocAction[];
@ -1324,6 +1336,7 @@ export class GranularAccess implements GranularAccessForBundle {
*/ */
private _pruneColumns(a: DocAction, permInfo: IPermissionInfo, tableId: string, private _pruneColumns(a: DocAction, permInfo: IPermissionInfo, tableId: string,
accessCheck: IAccessCheck): DocAction|null { accessCheck: IAccessCheck): DocAction|null {
permInfo = new TransformColumnPermissionInfo(permInfo);
if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') { if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') {
return a; return a;
} else if (a[0] === 'AddRecord' || a[0] === 'BulkAddRecord' || a[0] === 'UpdateRecord' || } else if (a[0] === 'AddRecord' || a[0] === 'BulkAddRecord' || a[0] === 'UpdateRecord' ||
@ -2856,6 +2869,9 @@ export class CensorshipInfo {
(colId !== 'manualSort' && permInfo.getColumnAccess(tableId, colId).perms.read === 'deny')) { (colId !== 'manualSort' && permInfo.getColumnAccess(tableId, colId).perms.read === 'deny')) {
censoredColumnCodes.add(columnCode(tableRef, colId)); censoredColumnCodes.add(columnCode(tableRef, colId));
} }
if (isTransformColumn(colId) && permInfo.getColumnAccess(tableId, colId).perms.schemaEdit === 'deny') {
censoredColumnCodes.add(columnCode(tableRef, colId));
}
} }
// Collect a list of all sections and views containing a table to which the user has no access. // Collect a list of all sections and views containing a table to which the user has no access.
rec = new RecordView(tables._grist_Views_section, undefined); rec = new RecordView(tables._grist_Views_section, undefined);
@ -3158,6 +3174,42 @@ function actionHasRuleChange(a: DocAction): boolean {
); );
} }
/**
* Wrapper around a permission info object that overrides permissions for transform columns.
*/
class TransformColumnPermissionInfo implements IPermissionInfo {
constructor(private _inner: IPermissionInfo) {
}
public getColumnAccess(tableId: string, colId: string): MixedPermissionSetWithContext {
const access = this._inner.getColumnAccess(tableId, colId);
const isSchemaDenied = access.perms.schemaEdit === 'deny';
// If this is a transform column, it's only accessible if the user has a schemaEdit access.
if (isSchemaDenied && isTransformColumn(colId)) {
return {
...access,
perms: {
create: 'deny',
read: 'deny',
update: 'deny',
delete: 'deny',
schemaEdit: 'deny',
}
};
}
return access;
}
public getTableAccess(tableId: string): TablePermissionSetWithContext {
return this._inner.getTableAccess(tableId);
}
public getFullAccess(): MixedPermissionSetWithContext {
return this._inner.getFullAccess();
}
public getRuleCollection(): ACLRuleCollection {
return this._inner.getRuleCollection();
}
}
interface SingleCellInfo extends SingleCell { interface SingleCellInfo extends SingleCell {
userRef: string; userRef: string;
id: number; id: number;