From 2a86cde4747ef93b77df2223b5de57d5797d7923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?= Date: Mon, 19 Dec 2022 17:52:44 +0100 Subject: [PATCH] (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 --- app/common/DocActions.ts | 2 +- app/server/lib/GranularAccess.ts | 54 +++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/common/DocActions.ts b/app/common/DocActions.ts index 2f4bbdcc..e9323103 100644 --- a/app/common/DocActions.ts +++ b/app/common/DocActions.ts @@ -31,7 +31,7 @@ export type TableDataAction = ['TableData', string, number[], BulkColValues]; export type AddColumn = ['AddColumn', string, string, ColInfo]; export type RemoveColumn = ['RemoveColumn', string, string]; export type RenameColumn = ['RenameColumn', string, string, string]; -export type ModifyColumn = ['ModifyColumn', string, string, ColInfo]; +export type ModifyColumn = ['ModifyColumn', string, string, Partial]; export type AddTable = ['AddTable', string, ColInfoWithId[]]; export type RemoveTable = ['RemoveTable', string]; diff --git a/app/server/lib/GranularAccess.ts b/app/server/lib/GranularAccess.ts index 0a819463..f7323349 100644 --- a/app/server/lib/GranularAccess.ts +++ b/app/server/lib/GranularAccess.ts @@ -40,7 +40,8 @@ import { getDocSessionAccess, getDocSessionAltSessionId, getDocSessionUser, OptDocSession } from 'app/server/lib/DocSession'; import { DocStorage, REMOVE_UNUSED_ATTACHMENTS_DELAY } from 'app/server/lib/DocStorage'; 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 { integerParam } from 'app/server/lib/requestUtils'; import { getColIdsFromDocAction, getColValuesFromDocAction, getRelatedRows, @@ -191,6 +192,17 @@ const UPLOADED_ATTACHMENT_OWNERSHIP_PERIOD = // older than this limit. 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 { actionGroup: ActionGroup; docActions: DocAction[]; @@ -1324,6 +1336,7 @@ export class GranularAccess implements GranularAccessForBundle { */ private _pruneColumns(a: DocAction, permInfo: IPermissionInfo, tableId: string, accessCheck: IAccessCheck): DocAction|null { + permInfo = new TransformColumnPermissionInfo(permInfo); if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') { return a; } 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')) { 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. 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 { userRef: string; id: number;