mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
ea71312d0e
Summary: Attachments are a special case for granular access control. A user is now allowed to read a given attachment if they have read access to a cell containing its id. So when a user writes to a cell in an attachment column, it is important that they can only write the ids of cells to which they have access. This diff allows a user to add an attachment id in a cell if: * The user already has access to that a attachment via some existing cell, or * The user recently updated the attachment, or * The attachment change is from an undo/redo of a previous action attributed to that user Test Plan: Updated tests Reviewers: georgegevoian, dsagal Reviewed By: georgegevoian, dsagal Differential Revision: https://phab.getgrist.com/D3681
110 lines
4.6 KiB
TypeScript
110 lines
4.6 KiB
TypeScript
import { AddRecord, BulkAddRecord, BulkRemoveRecord, BulkUpdateRecord,
|
|
CellValue, DocAction, getTableId, RemoveRecord, ReplaceTableData,
|
|
TableDataAction, UpdateRecord } from "app/common/DocActions";
|
|
import { getSetMapValue } from "app/common/gutil";
|
|
|
|
/**
|
|
* A little class for tracking pre-existing rows touched by a sequence of DocActions for
|
|
* a given table.
|
|
*/
|
|
class RowIdTracker {
|
|
public blockedIds = new Set<number>(); // row ids minted within the DocActions (so NOT pre-existing).
|
|
public blocked: boolean = false; // set if all pre-existing rows are wiped/
|
|
public ids = new Set<number>(); // set of pre-existing rows touched.
|
|
}
|
|
|
|
/**
|
|
* This gets a list of pre-existing rows that the DocActions may touch. Returns
|
|
* a list of form [tableId, Set{rowId1, rowId2, ...}].
|
|
*/
|
|
export function getRelatedRows(docActions: DocAction[]): ReadonlyArray<readonly [string, Set<number>]> {
|
|
// Relate tableIds for tables with what they were before the actions, if renamed.
|
|
const tableIds = new Map<string, string>(); // key is current tableId
|
|
const rowIds = new Map<string, RowIdTracker>(); // key is pre-existing tableId
|
|
const addedTables = new Set<string>(); // track newly added tables to ignore; key is current tableId
|
|
for (const docAction of docActions) {
|
|
const currentTableId = getTableId(docAction);
|
|
const tableId = tableIds.get(currentTableId) || currentTableId;
|
|
if (docAction[0] === 'RenameTable') {
|
|
if (addedTables.has(currentTableId)) {
|
|
addedTables.delete(currentTableId);
|
|
addedTables.add(docAction[2]);
|
|
continue;
|
|
}
|
|
tableIds.delete(currentTableId);
|
|
tableIds.set(docAction[2], tableId);
|
|
continue;
|
|
}
|
|
if (docAction[0] === 'AddTable') {
|
|
addedTables.add(currentTableId);
|
|
}
|
|
if (docAction[0] === 'RemoveTable') {
|
|
addedTables.delete(currentTableId);
|
|
continue;
|
|
}
|
|
if (addedTables.has(currentTableId)) { continue; }
|
|
|
|
// tableId will now be that prior to docActions, regardless of renames.
|
|
const tracker = getSetMapValue(rowIds, tableId, () => new RowIdTracker());
|
|
|
|
if (docAction[0] === 'RemoveRecord' || docAction[0] === 'BulkRemoveRecord' ||
|
|
docAction[0] === 'UpdateRecord' || docAction[0] === 'BulkUpdateRecord') {
|
|
// All row ids mentioned are external, unless created within this set of DocActions.
|
|
if (!tracker.blocked) {
|
|
for (const id of getRowIdsFromDocAction(docAction)) {
|
|
if (!tracker.blockedIds.has(id)) { tracker.ids.add(id); }
|
|
}
|
|
}
|
|
} else if (docAction[0] === 'AddRecord' || docAction[0] === 'BulkAddRecord') {
|
|
// All row ids mentioned are created within this set of DocActions, and are not external.
|
|
for (const id of getRowIdsFromDocAction(docAction)) { tracker.blockedIds.add(id); }
|
|
} else if (docAction[0] === 'ReplaceTableData' || docAction[0] === 'TableData') {
|
|
// No pre-existing rows can be referred to for this table from now on.
|
|
tracker.blocked = true;
|
|
}
|
|
}
|
|
|
|
return [...rowIds.entries()].map(([tableId, tracker]) => [tableId, tracker.ids] as const);
|
|
}
|
|
|
|
/**
|
|
* Tiny helper to get the row ids mentioned in a record-related DocAction as a list
|
|
* (even if the action is not a bulk action).
|
|
*/
|
|
export function getRowIdsFromDocAction(docActions: RemoveRecord | BulkRemoveRecord | AddRecord |
|
|
BulkAddRecord | UpdateRecord | BulkUpdateRecord | ReplaceTableData |
|
|
TableDataAction) {
|
|
const ids = docActions[2];
|
|
return (typeof ids === 'number') ? [ids] : ids;
|
|
}
|
|
|
|
/**
|
|
* Tiny helper to get the col ids mentioned in a record-related DocAction as a list
|
|
* (even if the action is not a bulk action). When the action touches the whole row,
|
|
* it returns ["*"].
|
|
*/
|
|
export function getColIdsFromDocAction(docActions: RemoveRecord | BulkRemoveRecord | AddRecord |
|
|
BulkAddRecord | UpdateRecord | BulkUpdateRecord | ReplaceTableData |
|
|
TableDataAction) {
|
|
if (docActions[3]) { return Object.keys(docActions[3]); }
|
|
return ['*'];
|
|
}
|
|
|
|
/**
|
|
* Extract column values for a particular column as CellValue[] from a
|
|
* record-related DocAction. Undefined if absent.
|
|
*/
|
|
export function getColValuesFromDocAction(docAction: RemoveRecord | BulkRemoveRecord | AddRecord |
|
|
BulkAddRecord | UpdateRecord | BulkUpdateRecord | ReplaceTableData |
|
|
TableDataAction, colId: string): CellValue[]|undefined {
|
|
const colValues = docAction[3];
|
|
if (!colValues) { return undefined; }
|
|
const cellValues = colValues[colId];
|
|
if (!cellValues) { return undefined; }
|
|
if (Array.isArray(docAction[2])) {
|
|
return cellValues as CellValue[];
|
|
} else {
|
|
return [cellValues as CellValue];
|
|
}
|
|
}
|