mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) avoid censorship for one client clobbering data for another client
Summary: When filtering document updates to send to clients after a change, censorship of individual cells was being applied to state shared across the clients. This diff eliminates that shared state, and extends testing of broadcasts to check different orderings. Test Plan: extends a test to tickle a reported bug, and gives DocClients a knob to control message order needed to tickle the bug reliably. Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D3064
This commit is contained in:
@@ -11,6 +11,11 @@ import {sendDocMessage} from 'app/server/lib/Comm';
|
||||
import {DocSession, OptDocSession} from 'app/server/lib/DocSession';
|
||||
import * as log from 'app/server/lib/log';
|
||||
|
||||
// Allow tests to impose a serial order for broadcasts if they need that for repeatability.
|
||||
export const Deps = {
|
||||
BROADCAST_ORDER: 'parallel' as 'parallel' | 'series',
|
||||
};
|
||||
|
||||
export class DocClients {
|
||||
private _docSessions: DocSession[] = [];
|
||||
|
||||
@@ -78,48 +83,62 @@ export class DocClients {
|
||||
public async broadcastDocMessage(client: Client|null, type: string, messageData: any,
|
||||
filterMessage?: (docSession: OptDocSession,
|
||||
messageData: any) => Promise<any>): Promise<void> {
|
||||
await Promise.all(this._docSessions.map(async curr => {
|
||||
const fromSelf = (curr.client === client);
|
||||
try {
|
||||
// Make sure user still has view access.
|
||||
await curr.authorizer.assertAccess('viewers');
|
||||
if (!filterMessage) {
|
||||
sendDocMessage(curr.client, curr.fd, type, messageData, fromSelf);
|
||||
} else {
|
||||
try {
|
||||
const filteredMessageData = await filterMessage(curr, messageData);
|
||||
if (filteredMessageData) {
|
||||
sendDocMessage(curr.client, curr.fd, type, filteredMessageData, fromSelf);
|
||||
} else {
|
||||
this.activeDoc.logDebug(curr, 'skip broadcastDocMessage because it is not allowed for this client');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code && e.code === 'NEED_RELOAD') {
|
||||
sendDocMessage(curr.client, curr.fd, 'docShutdown', null, fromSelf);
|
||||
} else {
|
||||
sendDocMessage(curr.client, curr.fd, 'docUserAction', {error: String(e)}, fromSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code === 'AUTH_NO_VIEW') {
|
||||
// Skip sending data to this user, they have no view access.
|
||||
log.rawDebug('skip broadcastDocMessage because AUTH_NO_VIEW', {
|
||||
docId: curr.authorizer.getDocId(),
|
||||
...curr.client.getLogMeta()
|
||||
});
|
||||
// Go further and trigger a shutdown for this user, in case they are granted
|
||||
// access again later.
|
||||
sendDocMessage(curr.client, curr.fd, 'docShutdown', null, fromSelf);
|
||||
} else {
|
||||
throw(e);
|
||||
}
|
||||
const send = (curr: DocSession) => this._send(curr, client, type, messageData, filterMessage);
|
||||
if (Deps.BROADCAST_ORDER === 'parallel') {
|
||||
await Promise.all(this._docSessions.map(send));
|
||||
} else {
|
||||
for (const session of this._docSessions) {
|
||||
await send(session);
|
||||
}
|
||||
}));
|
||||
}
|
||||
if (type === "docUserAction" && messageData.docActions) {
|
||||
for (const action of messageData.docActions) {
|
||||
this.activeDoc.docPluginManager.receiveAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a single client. See broadcastDocMessage for parameters.
|
||||
*/
|
||||
private async _send(target: DocSession, client: Client|null, type: string, messageData: any,
|
||||
filterMessage?: (docSession: OptDocSession,
|
||||
messageData: any) => Promise<any>): Promise<void> {
|
||||
const fromSelf = (target.client === client);
|
||||
try {
|
||||
// Make sure user still has view access.
|
||||
await target.authorizer.assertAccess('viewers');
|
||||
if (!filterMessage) {
|
||||
sendDocMessage(target.client, target.fd, type, messageData, fromSelf);
|
||||
} else {
|
||||
try {
|
||||
const filteredMessageData = await filterMessage(target, messageData);
|
||||
if (filteredMessageData) {
|
||||
sendDocMessage(target.client, target.fd, type, filteredMessageData, fromSelf);
|
||||
} else {
|
||||
this.activeDoc.logDebug(target, 'skip broadcastDocMessage because it is not allowed for this client');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code && e.code === 'NEED_RELOAD') {
|
||||
sendDocMessage(target.client, target.fd, 'docShutdown', null, fromSelf);
|
||||
} else {
|
||||
sendDocMessage(target.client, target.fd, 'docUserAction', {error: String(e)}, fromSelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.code === 'AUTH_NO_VIEW') {
|
||||
// Skip sending data to this user, they have no view access.
|
||||
log.rawDebug('skip broadcastDocMessage because AUTH_NO_VIEW', {
|
||||
docId: target.authorizer.getDocId(),
|
||||
...target.client.getLogMeta()
|
||||
});
|
||||
// Go further and trigger a shutdown for this user, in case they are granted
|
||||
// access again later.
|
||||
sendDocMessage(target.client, target.fd, 'docShutdown', null, fromSelf);
|
||||
} else {
|
||||
throw(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user