(core) Update ACL resources/rules when tables/columns get renamed

Summary:
- Placed rule-updating functions in acl.py.
- Reset UI when rules update externally, or alert the user to reset if there
  are pending local changes.
- Removed some unused and distracting bits from client-side DocModel.

A few improvements related to poor error handling:
- In case of missing DocActions (tickled by broken ACL rule handling), don't
  add to confusion by attempting to process bad actions
- In case of missing attributes in ACL formulas, return undefined rather than
  fail; the latter creates more problems.
- In case in invalid rules, fail rather than skip; this feels more correct now
  that we have error checking and recovery option, and helps avoid invalid rules.
- Prevent saving invalid rules with an empty ACL formula.
- Fix bug with rule positions.

Test Plan: Added a python and browser test for table/column renames.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2698
This commit is contained in:
Dmitry S
2020-12-28 00:40:10 -05:00
parent d6d1eb217f
commit 5deac68315
14 changed files with 338 additions and 79 deletions

View File

@@ -119,10 +119,12 @@ BaseRowModel.prototype._process_RenameColumn = function(action, tableId, oldColI
// handle standard renames differently
if (this._fields.indexOf(newColId) !== -1) {
console.error("RowModel #RenameColumn %s %s %s: already exists", tableId, oldColId, newColId);
return;
}
var index = this._fields.indexOf(oldColId);
if (index === -1) {
console.error("RowModel #RenameColumn %s %s %s: not found", tableId, oldColId, newColId);
return;
}
this._fields[index] = newColId;

View File

@@ -24,9 +24,6 @@ import * as rowset from 'app/client/models/rowset';
import {RowId} from 'app/client/models/rowset';
import {schema, SchemaTypes} from 'app/common/schema';
import {ACLMembershipRec, createACLMembershipRec} from 'app/client/models/entities/ACLMembershipRec';
import {ACLPrincipalRec, createACLPrincipalRec} from 'app/client/models/entities/ACLPrincipalRec';
import {ACLResourceRec, createACLResourceRec} from 'app/client/models/entities/ACLResourceRec';
import {ColumnRec, createColumnRec} from 'app/client/models/entities/ColumnRec';
import {createDocInfoRec, DocInfoRec} from 'app/client/models/entities/DocInfoRec';
import {createPageRec, PageRec} from 'app/client/models/entities/PageRec';
@@ -41,8 +38,6 @@ import {createViewSectionRec, ViewSectionRec} from 'app/client/models/entities/V
// Re-export all the entity types available. The recommended usage is like this:
// import {ColumnRec, ViewFieldRec} from 'app/client/models/DocModel';
export {ACLMembershipRec} from 'app/client/models/entities/ACLMembershipRec';
export {ACLPrincipalRec} from 'app/client/models/entities/ACLPrincipalRec';
export {ColumnRec} from 'app/client/models/entities/ColumnRec';
export {DocInfoRec} from 'app/client/models/entities/DocInfoRec';
export {PageRec} from 'app/client/models/entities/PageRec';
@@ -115,9 +110,6 @@ export class DocModel {
public tabBar: MTM<TabBarRec> = this._metaTableModel("_grist_TabBar", createTabBarRec);
public validations: MTM<ValidationRec> = this._metaTableModel("_grist_Validations", createValidationRec);
public replHist: MTM<REPLRec> = this._metaTableModel("_grist_REPL_Hist", createREPLRec);
public aclPrincipals: MTM<ACLPrincipalRec> = this._metaTableModel("_grist_ACLPrincipals", createACLPrincipalRec);
public aclMemberships: MTM<ACLMembershipRec> = this._metaTableModel("_grist_ACLMemberships", createACLMembershipRec);
public aclResources: MTM<ACLResourceRec> = this._metaTableModel("_grist_ACLResources", createACLResourceRec);
public pages: MTM<PageRec> = this._metaTableModel("_grist_Pages", createPageRec);
public allTables: KoArray<TableRec>;

View File

@@ -1,14 +0,0 @@
import {ACLPrincipalRec, DocModel, IRowModel, refRecord} from 'app/client/models/DocModel';
import * as ko from 'knockout';
// Table for containment relationships between Principals, e.g. user contains multiple
// instances, group contains multiple users, and groups may contain other groups.
export interface ACLMembershipRec extends IRowModel<"_grist_ACLMemberships"> {
parentRec: ko.Computed<ACLPrincipalRec>;
childRec: ko.Computed<ACLPrincipalRec>;
}
export function createACLMembershipRec(this: ACLMembershipRec, docModel: DocModel): void {
this.parentRec = refRecord(docModel.aclPrincipals, this.parent);
this.childRec = refRecord(docModel.aclPrincipals, this.child);
}

View File

@@ -1,29 +0,0 @@
import {KoArray} from 'app/client/lib/koArray';
import {ACLMembershipRec, DocModel, IRowModel, recordSet} from 'app/client/models/DocModel';
import {KoSaveableObservable} from 'app/client/models/modelUtil';
import * as ko from 'knockout';
// A principals used by ACL rules, including users, groups, and instances.
export interface ACLPrincipalRec extends IRowModel<"_grist_ACLPrincipals"> {
// Declare a more specific type for 'type' than what's set automatically from schema.ts.
type: KoSaveableObservable<'user'|'instance'|'group'>;
// KoArray of ACLMembership row models which contain this principal as a child.
parentMemberships: ko.Computed<KoArray<ACLMembershipRec>>;
// Gives an array of ACLPrincipal parents to this row model.
parents: ko.Computed<ACLPrincipalRec[]>;
// KoArray of ACLMembership row models which contain this principal as a parent.
childMemberships: ko.Computed<KoArray<ACLMembershipRec>>;
// Gives an array of ACLPrincipal children of this row model.
children: ko.Computed<ACLPrincipalRec[]>;
}
export function createACLPrincipalRec(this: ACLPrincipalRec, docModel: DocModel): void {
this.parentMemberships = recordSet(this, docModel.aclMemberships, 'child');
this.childMemberships = recordSet(this, docModel.aclMemberships, 'parent');
this.parents = ko.pureComputed(() => this.parentMemberships().all().map(m => m.parentRec()));
this.children = ko.pureComputed(() => this.childMemberships().all().map(m => m.childRec()));
}

View File

@@ -1,7 +0,0 @@
import {DocModel, IRowModel} from 'app/client/models/DocModel';
export type ACLResourceRec = IRowModel<"_grist_ACLResources">;
export function createACLResourceRec(this: ACLResourceRec, docModel: DocModel): void {
// no extra fields
}