(core) implement cleaner row-level access control for outgoing messages

Summary:
This implements row-level access control for outgoing messages, replacing the document reloading placeholder that was there before.

 * Prior to broadcasting messages, GranularAccess is notified of actions+undo.
 * While broadcasting messages to different sessions, if we find we need row level access control information, rows before and after the change are reconstructed.
 * Messages are rewritten if rows that were previously forbidden are now allowed, and vice versa.

The diff is somewhat under-tested and under-optimized. Next step would be to implement row-level access control for incoming actions, which may result in some rejiggering of the code from this diff to avoid duplication of effort under some conditions.

Test Plan: added test

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2670
This commit is contained in:
Paul Fitzpatrick
2020-11-30 10:50:00 -05:00
parent c1c17bf54e
commit 0e2deecc55
8 changed files with 433 additions and 61 deletions

View File

@@ -16,8 +16,14 @@ type FetchTableFunc = (tableId: string) => Promise<TableDataAction>;
export class DocData extends ActionDispatcher {
private _tables: Map<string, TableData> = new Map();
constructor(private _fetchTableFunc: FetchTableFunc, metaTableData: {[tableId: string]: TableDataAction}) {
/**
* If metaTableData is not supplied, then any tables needed should be loaded manually,
* using syncTable(). All column types will be set to Any, which will affect default
* values.
*/
constructor(private _fetchTableFunc: FetchTableFunc, metaTableData: {[tableId: string]: TableDataAction} | null) {
super();
if (metaTableData === null) { return; }
// Create all meta tables, and populate data we already have.
for (const tableId in schema) {
if (schema.hasOwnProperty(tableId)) {
@@ -67,6 +73,17 @@ export class DocData extends ActionDispatcher {
return (!table.isLoaded || force) ? table.fetchData(this._fetchTableFunc) : Promise.resolve();
}
/**
* Fetches the data for tableId unconditionally, and without knowledge of its metadata.
* Columns will be assumed to have type 'Any'.
*/
public async syncTable(tableId: string): Promise<void> {
const tableData = await this._fetchTableFunc(tableId);
const colTypes = fromPairs(Object.keys(tableData[3]).map(c => [c, 'Any']));
colTypes.id = 'Any';
this._tables.set(tableId, this.createTableData(tableId, tableData, colTypes));
}
/**
* Handles an action received from the server, by forwarding it to the appropriate TableData
* object.