mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) always allow owners to edit access rules, so they don't get stuck
Summary: Access to structural tables currently depends on SchemaEdit permission. We now make an exception for owner access to _grist_ACLResources and _grist_ACLRules, giving them unconditional access. It was too easy for owners to lock themselves out of editing access rules. Test Plan: added test Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2790
This commit is contained in:
parent
fd73831b39
commit
9696e24aac
@ -388,7 +388,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
return false; // have to look closely
|
return false; // have to look closely
|
||||||
}
|
}
|
||||||
const tableAccess = await this.getTableAccess(docSession, tableId);
|
const tableAccess = await this.getTableAccess(docSession, tableId);
|
||||||
const accessCheck = getAccessForActionType(a, 'fatal');
|
const accessCheck = await this._getAccessForActionType(docSession, a, 'fatal');
|
||||||
accessCheck.get(tableAccess); // will throw if access denied.
|
accessCheck.get(tableAccess); // will throw if access denied.
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -613,7 +613,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
* accessCheck may throw if denials are fatal.
|
* accessCheck may throw if denials are fatal.
|
||||||
*/
|
*/
|
||||||
private _pruneColumns(a: DocAction, permInfo: IPermissionInfo, tableId: string,
|
private _pruneColumns(a: DocAction, permInfo: IPermissionInfo, tableId: string,
|
||||||
accessCheck: AccessCheck): DocAction|null {
|
accessCheck: IAccessCheck): DocAction|null {
|
||||||
if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') {
|
if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') {
|
||||||
return a;
|
return a;
|
||||||
} else if (a[0] === 'AddRecord' || a[0] === 'BulkAddRecord' || a[0] === 'UpdateRecord' ||
|
} else if (a[0] === 'AddRecord' || a[0] === 'BulkAddRecord' || a[0] === 'UpdateRecord' ||
|
||||||
@ -759,7 +759,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
* Like _pruneRows, but fails immediately if access to any row is forbidden.
|
* Like _pruneRows, but fails immediately if access to any row is forbidden.
|
||||||
* The accessCheck supplied should throw an error on denial.
|
* The accessCheck supplied should throw an error on denial.
|
||||||
*/
|
*/
|
||||||
private async _checkRows(cursor: ActionCursor, accessCheck: AccessCheck): Promise<void> {
|
private async _checkRows(cursor: ActionCursor, accessCheck: IAccessCheck): Promise<void> {
|
||||||
const {action} = cursor;
|
const {action} = cursor;
|
||||||
// This check applies to data changes only.
|
// This check applies to data changes only.
|
||||||
if (!isDataAction(action)) { return; }
|
if (!isDataAction(action)) { return; }
|
||||||
@ -808,7 +808,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
* Modify action in place, scrubbing any rows and cells to which access is not granted.
|
* Modify action in place, scrubbing any rows and cells to which access is not granted.
|
||||||
*/
|
*/
|
||||||
private async _filterRowsAndCells(cursor: ActionCursor, rowsBefore: TableDataAction, rowsAfter: TableDataAction,
|
private async _filterRowsAndCells(cursor: ActionCursor, rowsBefore: TableDataAction, rowsAfter: TableDataAction,
|
||||||
accessCheck: AccessCheck) {
|
accessCheck: IAccessCheck) {
|
||||||
const ruler = await this._getRuler(cursor);
|
const ruler = await this._getRuler(cursor);
|
||||||
const {docSession, action} = cursor;
|
const {docSession, action} = cursor;
|
||||||
if (action && isSchemaAction(action)) {
|
if (action && isSchemaAction(action)) {
|
||||||
@ -1329,8 +1329,8 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _checkIncomingDocAction(cursor: ActionCursor): Promise<void> {
|
private async _checkIncomingDocAction(cursor: ActionCursor): Promise<void> {
|
||||||
const {action} = cursor;
|
const {action, docSession} = cursor;
|
||||||
const accessCheck = getAccessForActionType(action, 'fatal');
|
const accessCheck = await this._getAccessForActionType(docSession, action, 'fatal');
|
||||||
const tableId = getTableId(action);
|
const tableId = getTableId(action);
|
||||||
const permInfo = await this._getStepAccess(cursor);
|
const permInfo = await this._getStepAccess(cursor);
|
||||||
const tableAccess = permInfo.getTableAccess(tableId);
|
const tableAccess = permInfo.getTableAccess(tableId);
|
||||||
@ -1372,6 +1372,32 @@ export class GranularAccess implements GranularAccessForBundle {
|
|||||||
const steps = await this._getMetaSteps();
|
const steps = await this._getMetaSteps();
|
||||||
return steps[cursor.actionIdx];
|
return steps[cursor.actionIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get an AccessCheck appropriate for the specific action.
|
||||||
|
// TODO: deal with ReplaceTableData, which both deletes and creates rows.
|
||||||
|
private async _getAccessForActionType(docSession: OptDocSession, a: DocAction,
|
||||||
|
severity: 'check'|'fatal'): Promise<IAccessCheck> {
|
||||||
|
const tableId = getTableId(a);
|
||||||
|
if (STRUCTURAL_TABLES.has(tableId)) {
|
||||||
|
// Special case: ensure owners always have full access to ACL tables, so they
|
||||||
|
// can change rules and don't get stuck.
|
||||||
|
if (isAclTable(tableId) && await this.isOwner(docSession)) {
|
||||||
|
return {
|
||||||
|
get() { return 'allow'; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Otherwise, access to structural tables currently follows the schemaEdit flag.
|
||||||
|
return accessChecks[severity].schemaEdit;
|
||||||
|
} else if (a[0] === 'UpdateRecord' || a[0] === 'BulkUpdateRecord') {
|
||||||
|
return accessChecks[severity].update;
|
||||||
|
} else if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') {
|
||||||
|
return accessChecks[severity].delete;
|
||||||
|
} else if (a[0] === 'AddRecord' || a[0] === 'BulkAddRecord') {
|
||||||
|
return accessChecks[severity].create;
|
||||||
|
} else {
|
||||||
|
return accessChecks[severity].schemaEdit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1529,8 +1555,11 @@ class UserAttributes {
|
|||||||
public override?: UserOverride;
|
public override?: UserOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IAccessCheck {
|
||||||
|
get(ps: PermissionSetWithContext): string;
|
||||||
|
}
|
||||||
|
|
||||||
class AccessCheck {
|
class AccessCheck implements IAccessCheck {
|
||||||
constructor(public access: 'update'|'delete'|'create'|'schemaEdit'|'read',
|
constructor(public access: 'update'|'delete'|'create'|'schemaEdit'|'read',
|
||||||
public severity: 'check'|'fatal') {
|
public severity: 'check'|'fatal') {
|
||||||
}
|
}
|
||||||
@ -1562,21 +1591,6 @@ export const accessChecks = {
|
|||||||
// The AccessCheck for the "read" permission is used enough to merit a shortcut.
|
// The AccessCheck for the "read" permission is used enough to merit a shortcut.
|
||||||
const readAccessCheck = accessChecks.check.read;
|
const readAccessCheck = accessChecks.check.read;
|
||||||
|
|
||||||
// Get an AccessCheck appropriate for the specific action.
|
|
||||||
// TODO: deal with ReplaceTableData, which both deletes and creates rows.
|
|
||||||
function getAccessForActionType(a: DocAction, severity: 'check'|'fatal'): AccessCheck {
|
|
||||||
if (STRUCTURAL_TABLES.has(getTableId(a))) {
|
|
||||||
return accessChecks[severity].schemaEdit;
|
|
||||||
} else if (a[0] === 'UpdateRecord' || a[0] === 'BulkUpdateRecord') {
|
|
||||||
return accessChecks[severity].update;
|
|
||||||
} else if (a[0] === 'RemoveRecord' || a[0] === 'BulkRemoveRecord') {
|
|
||||||
return accessChecks[severity].delete;
|
|
||||||
} else if (a[0] === 'AddRecord' || a[0] === 'BulkAddRecord') {
|
|
||||||
return accessChecks[severity].create;
|
|
||||||
} else {
|
|
||||||
return accessChecks[severity].schemaEdit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manage censoring metadata.
|
* Manage censoring metadata.
|
||||||
|
Loading…
Reference in New Issue
Block a user