(core) update read access for exceptional sessions

Summary:
Exceptional sessions had lost full read access to documents; this
restores it.  Exceptional sessions are used for system actions or
while creating documents.

Test Plan: added test

Reviewers: alexmojaki

Reviewed By: alexmojaki

Differential Revision: https://phab.getgrist.com/D2966
This commit is contained in:
Paul Fitzpatrick 2021-08-05 12:07:23 -04:00
parent 4ca47878ca
commit 4a23b964ed

View File

@ -653,6 +653,13 @@ export class GranularAccess implements GranularAccessForBundle {
* access level and any row-level access functions needed. * access level and any row-level access functions needed.
*/ */
public async getTableAccess(docSession: OptDocSession, tableId: string): Promise<TablePermissionSetWithContext> { public async getTableAccess(docSession: OptDocSession, tableId: string): Promise<TablePermissionSetWithContext> {
if (this._hasExceptionalFullAccess(docSession)) {
return {
perms: {read: 'allow', create: 'allow', delete: 'allow', update: 'allow', schemaEdit: 'allow'},
ruleType: 'table',
getMemos() { throw new Error('never needed'); }
};
}
return (await this._getAccess(docSession)).getTableAccess(tableId); return (await this._getAccess(docSession)).getTableAccess(tableId);
} }
@ -665,6 +672,7 @@ export class GranularAccess implements GranularAccessForBundle {
const cursor: ActionCursor = {docSession, action: data, actionIdx: null}; const cursor: ActionCursor = {docSession, action: data, actionIdx: null};
const tableId = getTableId(data); const tableId = getTableId(data);
if (this.getReadPermission(permInfo.getTableAccess(tableId)) === 'mixed') { if (this.getReadPermission(permInfo.getTableAccess(tableId)) === 'mixed') {
const readAccessCheck = this._readAccessCheck(docSession);
await this._filterRowsAndCells(cursor, data, data, readAccessCheck, true); await this._filterRowsAndCells(cursor, data, data, readAccessCheck, true);
} }
@ -684,7 +692,7 @@ export class GranularAccess implements GranularAccessForBundle {
} }
public assertCanRead(ps: PermissionSetWithContext) { public assertCanRead(ps: PermissionSetWithContext) {
readAccessCheck.throwIfDenied(ps); accessChecks.fatal.read.get(ps);
} }
/** /**
@ -734,6 +742,18 @@ export class GranularAccess implements GranularAccessForBundle {
} }
} }
// The AccessCheck for the "read" permission is used enough to merit a shortcut.
// We just need to be careful to retain unfettered access for exceptional sessions.
private _readAccessCheck(docSession: OptDocSession): IAccessCheck {
return this._hasExceptionalFullAccess(docSession) ? dummyAccessCheck : accessChecks.check.read;
}
// Return true for special system sessions or document-creation sessions, where
// unfettered access is appropriate.
private _hasExceptionalFullAccess(docSession: OptDocSession): Boolean {
return docSession.mode === 'system' || docSession.mode === 'nascent';
}
/** /**
* This filters a message being broadcast to all clients to be appropriate for one * This filters a message being broadcast to all clients to be appropriate for one
* particular client, if that client may need some material filtered out. * particular client, if that client may need some material filtered out.
@ -909,6 +929,7 @@ export class GranularAccess implements GranularAccessForBundle {
} }
// Return the results, also applying any cell-level access control. // Return the results, also applying any cell-level access control.
const readAccessCheck = this._readAccessCheck(cursor.docSession);
for (const a of revisedDocActions) { for (const a of revisedDocActions) {
await this._filterRowsAndCells({...cursor, action: a}, rowsAfter, rowsAfter, readAccessCheck, false); await this._filterRowsAndCells({...cursor, action: a}, rowsAfter, rowsAfter, readAccessCheck, false);
} }
@ -1518,6 +1539,7 @@ export class GranularAccess implements GranularAccessForBundle {
const permInfo = await this._getStepAccess(cursor); const permInfo = await this._getStepAccess(cursor);
const tableAccess = permInfo.getTableAccess(tableId); const tableAccess = permInfo.getTableAccess(tableId);
const access = this.getReadPermission(tableAccess); const access = this.getReadPermission(tableAccess);
const readAccessCheck = this._readAccessCheck(cursor.docSession);
const results: DocAction[] = []; const results: DocAction[] = [];
if (access === 'deny') { if (access === 'deny') {
// filter out this data. // filter out this data.
@ -1636,7 +1658,7 @@ export class GranularAccess implements GranularAccessForBundle {
// TODO: deal with ReplaceTableData, which both deletes and creates rows. // TODO: deal with ReplaceTableData, which both deletes and creates rows.
private async _getAccessForActionType(docSession: OptDocSession, a: DocAction, private async _getAccessForActionType(docSession: OptDocSession, a: DocAction,
severity: 'check'|'fatal'): Promise<IAccessCheck> { severity: 'check'|'fatal'): Promise<IAccessCheck> {
if (docSession.mode === 'system' || docSession.mode === 'nascent') { if (this._hasExceptionalFullAccess(docSession)) {
return dummyAccessCheck; return dummyAccessCheck;
} }
const tableId = getTableId(a); const tableId = getTableId(a);
@ -1821,6 +1843,7 @@ class UserAttributes {
interface IAccessCheck { interface IAccessCheck {
get(ps: PermissionSetWithContext): string; get(ps: PermissionSetWithContext): string;
throwIfDenied(ps: PermissionSetWithContext): void;
} }
class AccessCheck implements IAccessCheck { class AccessCheck implements IAccessCheck {
@ -1852,11 +1875,11 @@ export const accessChecks = {
}; };
// The AccessCheck for the "read" permission is used enough to merit a shortcut.
const readAccessCheck = accessChecks.check.read;
// This AccessCheck allows everything. // This AccessCheck allows everything.
const dummyAccessCheck = { get() { return 'allow'; } }; const dummyAccessCheck: IAccessCheck = {
get() { return 'allow'; },
throwIfDenied() {}
};
/** /**