mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) back-end support for tables that are accessible only by owners
Summary:
This makes it possible to serve a table or tables only to owners.
* The _grist_ACLResources table is abused (temporarily) such that rows of the form `{colId: '~o', tableId}` are interpreted as meaning that `tableId` is private to owners.
* Many websocket and api endpoints are updated to preserve the privacy of these tables.
* In a document where some tables are private, a lot of capabilities are turned off for non-owners to avoid leaking info indirectly.
* The client is tweaked minimally, to show '-' where a page with some private material would otherwise go.
No attempt is made to protect data from private tables pulled into non-private tables via formulas.
There are some known leaks remaining:
* Changes to the schema of private tables are still broadcast to all clients (fixable).
* Non-owner may be able to access snapshots or make forks or use other corners of API (fixable).
* Changing name of table makes it public, since tableId in ACLResource is not updated (fixable).
Security will require some work, the attack surface is large.
Test Plan: added tests
Reviewers: dsagal
Reviewed By: dsagal
Differential Revision: https://phab.getgrist.com/D2604
This commit is contained in:
@@ -8,7 +8,7 @@ import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
||||
import {Authorizer} from 'app/server/lib/Authorizer';
|
||||
import {Client} from 'app/server/lib/Client';
|
||||
import {sendDocMessage} from 'app/server/lib/Comm';
|
||||
import {DocSession} from 'app/server/lib/DocSession';
|
||||
import {DocSession, OptDocSession} from 'app/server/lib/DocSession';
|
||||
import * as log from 'app/server/lib/log';
|
||||
|
||||
export class DocClients {
|
||||
@@ -73,14 +73,26 @@ export class DocClients {
|
||||
* @param {Object} client: Originating client used to set the `fromSelf` flag in the message.
|
||||
* @param {String} type: The type of the message, e.g. 'docUserAction'.
|
||||
* @param {Object} messageData: The data for this type of message.
|
||||
* @param {Object} filterMessage: Optional callback to filter message per client.
|
||||
*/
|
||||
public async broadcastDocMessage(client: Client|null, type: string, messageData: any): Promise<void> {
|
||||
public async broadcastDocMessage(client: Client|null, type: string, messageData: any,
|
||||
filterMessage?: (docSession: OptDocSession,
|
||||
messageData: any) => 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');
|
||||
sendDocMessage(curr.client, curr.fd, type, messageData, fromSelf);
|
||||
if (!filterMessage) {
|
||||
sendDocMessage(curr.client, curr.fd, type, messageData, fromSelf);
|
||||
} else {
|
||||
const filteredMessageData = 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 === 'AUTH_NO_VIEW') {
|
||||
// Skip sending data to this user, they have no view access.
|
||||
|
||||
Reference in New Issue
Block a user