mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) support locking document structure to be controlled by owners only
Summary: This is an incremental step in granular access control. Using a temporary `{colIds: '~o structure'}` representation in the `_grist_ACLResources` table, the document structure can be set to be controlled by owners only. Test Plan: added test Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2613
This commit is contained in:
parent
2087ae5f67
commit
b44e4a94ab
@ -525,7 +525,7 @@ export class ActiveDoc extends EventEmitter {
|
||||
// Check if user has rights to download this doc.
|
||||
public canDownload(docSession: OptDocSession) {
|
||||
return this._granularAccess.hasViewAccess(docSession) &&
|
||||
!this._granularAccess.hasNuancedAccess(docSession);
|
||||
this._granularAccess.canReadEverything(docSession);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -633,9 +633,9 @@ export class ActiveDoc extends EventEmitter {
|
||||
*/
|
||||
public async findColFromValues(docSession: DocSession, values: any[], n: number,
|
||||
optTableId?: string): Promise<number[]> {
|
||||
// This could leak information about private tables, so if there are any nuanced
|
||||
// permissions in force and the user does not have full access, do nothing.
|
||||
if (this._granularAccess.hasNuancedAccess(docSession)) { return []; }
|
||||
// This could leak information about private tables, so if user cannot read entire
|
||||
// document, do nothing.
|
||||
if (!this._granularAccess.canReadEverything(docSession)) { return []; }
|
||||
this.logInfo(docSession, "findColFromValues(%s, %s, %s)", docSession, values, n);
|
||||
await this.waitForInitialization();
|
||||
return this._dataEngine.pyCall('find_col_from_values', values, n, optTableId);
|
||||
@ -783,7 +783,7 @@ export class ActiveDoc extends EventEmitter {
|
||||
|
||||
public async autocomplete(docSession: DocSession, txt: string, tableId: string): Promise<string[]> {
|
||||
// Autocompletion can leak names of tables and columns.
|
||||
if (this._granularAccess.hasNuancedAccess(docSession)) { return []; }
|
||||
if (!this._granularAccess.canReadEverything(docSession)) { return []; }
|
||||
await this.waitForInitialization();
|
||||
return this._dataEngine.pyCall('autocomplete', txt, tableId);
|
||||
}
|
||||
@ -828,7 +828,7 @@ export class ActiveDoc extends EventEmitter {
|
||||
* ID for the fork. TODO: reconcile the two ways there are now of preparing a fork.
|
||||
*/
|
||||
public async fork(docSession: DocSession): Promise<ForkResult> {
|
||||
if (this._granularAccess.hasNuancedAccess(docSession)) {
|
||||
if (!this._granularAccess.canReadEverything(docSession)) {
|
||||
throw new Error('cannot confirm authority to copy document');
|
||||
}
|
||||
const userId = docSession.client.getCachedUserId();
|
||||
@ -1183,7 +1183,7 @@ export class ActiveDoc extends EventEmitter {
|
||||
actionGroup: ActionGroup,
|
||||
docActions: DocAction[]
|
||||
}) {
|
||||
if (!this._granularAccess.hasNuancedAccess(docSession)) { return message; }
|
||||
if (this._granularAccess.canReadEverything(docSession)) { return message; }
|
||||
const result = {
|
||||
actionGroup: this._granularAccess.filterActionGroup(docSession, message.actionGroup),
|
||||
docActions: this._granularAccess.filterOutgoingDocActions(docSession, message.docActions),
|
||||
|
@ -47,18 +47,24 @@ const SURPRISING_ACTIONS = new Set(['AddUser',
|
||||
const OK_ACTIONS = new Set(['Calculate', 'AddEmptyTable']);
|
||||
|
||||
/**
|
||||
* Manage granular access to a document. This allows nuances other than the coarse
|
||||
* owners/editors/viewers distinctions.
|
||||
*
|
||||
* Currently the only supported nuance is to mark certain tables as accessible by
|
||||
* owners only. To do so, in the _grist_ACLResources table, add a row like the
|
||||
* one already there, but with "~o" as the colIds, and the desired tableId set.
|
||||
* This is just a placeholder for a future representation.
|
||||
* Manage granular access to a document. This allows nuances other than the coarse
|
||||
* owners/editors/viewers distinctions. As a placeholder for a future representation,
|
||||
* nuances are stored in the _grist_ACLResources table. Supported nauances:
|
||||
*
|
||||
* - {tableId, colIds: '~o'}: mark specified table as accessible by owners only.
|
||||
* - {tableId: '', colIds: '~o structure'}: mark doc structure as editable by owners only.
|
||||
*
|
||||
*/
|
||||
export class GranularAccess {
|
||||
private _resources: TableData;
|
||||
|
||||
// Tables marked as accessible only by owners.
|
||||
private _ownerOnlyTableIds = new Set<string>();
|
||||
|
||||
// Document structure modifiable only by owners?
|
||||
private _onlyOwnersCanModifyStructure: boolean = false;
|
||||
|
||||
public constructor(private _docData: DocData) {
|
||||
this.update();
|
||||
}
|
||||
@ -70,9 +76,13 @@ export class GranularAccess {
|
||||
this._resources = this._docData.getTable('_grist_ACLResources')!;
|
||||
this._ownerOnlyTableIds.clear();
|
||||
for (const res of this._resources.getRecords()) {
|
||||
if (res.tableId && String(res.colIds).startsWith('~o')) {
|
||||
const code = String(res.colIds);
|
||||
if (res.tableId && code === '~o') {
|
||||
this._ownerOnlyTableIds.add(String(res.tableId));
|
||||
}
|
||||
if (!res.tableId && code === '~o structure') {
|
||||
this._onlyOwnersCanModifyStructure = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,7 +125,7 @@ export class GranularAccess {
|
||||
* to filter acceptible parts of ActionGroup, rather than denying entirely.
|
||||
*/
|
||||
public allowActionGroup(docSession: OptDocSession, actionGroup: ActionGroup): boolean {
|
||||
return !this.hasNuancedAccess(docSession);
|
||||
return this.canReadEverything(docSession);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,10 +179,20 @@ export class GranularAccess {
|
||||
* access is simple and without nuance.
|
||||
*/
|
||||
public hasNuancedAccess(docSession: OptDocSession): boolean {
|
||||
if (this._ownerOnlyTableIds.size === 0) { return false; }
|
||||
if (this._ownerOnlyTableIds.size === 0 && !this._onlyOwnersCanModifyStructure) {
|
||||
return false;
|
||||
}
|
||||
return !this.hasFullAccess(docSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether user can read everything in document.
|
||||
*/
|
||||
public canReadEverything(docSession: OptDocSession): boolean {
|
||||
if (this._ownerOnlyTableIds.size === 0) { return true; }
|
||||
return this.hasFullAccess(docSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether user has owner-level access to the document.
|
||||
*/
|
||||
@ -210,8 +230,8 @@ export class GranularAccess {
|
||||
*/
|
||||
public filterMetaTables(docSession: OptDocSession,
|
||||
tables: {[key: string]: TableDataAction}): {[key: string]: TableDataAction} {
|
||||
// If there are no nuances, return immediately.
|
||||
if (!this.hasNuancedAccess(docSession)) { return tables; }
|
||||
// If user has right to read everything, return immediately.
|
||||
if (this.canReadEverything(docSession)) { return tables; }
|
||||
// If we are going to modify metadata, make a copy.
|
||||
tables = JSON.parse(JSON.stringify(tables));
|
||||
// Collect a list of all tables (by tableRef) to which the user has no access.
|
||||
|
Loading…
Reference in New Issue
Block a user