(core) add buttons to delete bad rules

Summary:
When access rules refer to tables and/or columns that no longer exist, offer convenient buttons to remove these rules.

It could alternatively be useful to generate errors when deleting tables or columns that are mentioned in access rules, and refuse to do so unless the access rules are updated first.

Test Plan: added and updated tests

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: jarek

Differential Revision: https://phab.getgrist.com/D3718
This commit is contained in:
Paul Fitzpatrick
2022-12-05 11:37:48 -05:00
parent 8c610dcb33
commit ebaf04dace
5 changed files with 201 additions and 11 deletions

View File

@@ -1,4 +1,5 @@
import {parsePermissions} from 'app/common/ACLPermissions';
import {AclRuleProblem} from 'app/common/ActiveDocAPI';
import {ILogger} from 'app/common/BaseAPI';
import {DocData} from 'app/common/DocData';
import {AclMatchFunc, ParsedAclFormula, RulePart, RuleSet, UserAttributeRule} from 'app/common/GranularAccessClause';
@@ -232,6 +233,20 @@ export class ACLRuleCollection {
* Check that all references to table and column IDs in ACL rules are valid.
*/
public checkDocEntities(docData: DocData) {
const problems = this.findRuleProblems(docData);
if (problems.length === 0) { return; }
throw new Error(problems[0].comment);
}
/**
* Enumerate rule problems caused by table and column IDs that are not valid.
* Problems include:
* - Rules for a table that does not exist
* - Rules for columns that include a column that does not exist
* - User attributes links to a column that does not exist
*/
public findRuleProblems(docData: DocData): AclRuleProblem[] {
const problems: AclRuleProblem[] = [];
const tablesTable = docData.getMetaTable('_grist_Tables');
const columnsTable = docData.getMetaTable('_grist_Tables_column');
@@ -239,7 +254,12 @@ export class ACLRuleCollection {
const validTableIds = new Set(tablesTable.getColValues('tableId'));
const invalidTables = this.getAllTableIds().filter(t => !validTableIds.has(t));
if (invalidTables.length > 0) {
throw new Error(`Invalid tables in rules: ${invalidTables.join(', ')}`);
problems.push({
tables: {
tableIds: invalidTables,
},
comment: `Invalid tables in rules: ${invalidTables.join(', ')}`,
});
}
// Collect valid columns, grouped by tableRef (rowId of table record).
@@ -249,15 +269,22 @@ export class ACLRuleCollection {
getSetMapValue(validColumns, colTableRefs[i], () => new Set()).add(colId);
}
// For each table, check that any explicitly mentioned columns are valid.
// For each valid table, check that any explicitly mentioned columns are valid.
for (const tableId of this.getAllTableIds()) {
if (!validTableIds.has(tableId)) { continue; }
const tableRef = tablesTable.findRow('tableId', tableId);
const validTableCols = validColumns.get(tableRef);
for (const ruleSet of this.getAllColumnRuleSets(tableId)) {
if (Array.isArray(ruleSet.colIds)) {
const invalidColIds = ruleSet.colIds.filter(c => !validTableCols?.has(c));
if (invalidColIds.length > 0) {
throw new Error(`Invalid columns in rules for table ${tableId}: ${invalidColIds.join(', ')}`);
problems.push({
columns: {
tableId,
colIds: invalidColIds,
},
comment: `Invalid columns in rules for table ${tableId}: ${invalidColIds.join(', ')}`,
});
}
}
}
@@ -265,16 +292,25 @@ export class ACLRuleCollection {
// Check for valid tableId/lookupColId combinations in UserAttribute rules.
const invalidUAColumns: string[] = [];
const names: string[] = [];
for (const rule of this.getUserAttributeRules().values()) {
const tableRef = tablesTable.findRow('tableId', rule.tableId);
const colRef = columnsTable.findMatchingRowId({parentId: tableRef, colId: rule.lookupColId});
if (!colRef) {
invalidUAColumns.push(`${rule.tableId}.${rule.lookupColId}`);
names.push(rule.name);
}
}
if (invalidUAColumns.length > 0) {
throw new Error(`Invalid columns in User Attribute rules: ${invalidUAColumns.join(', ')}`);
problems.push({
userAttributes: {
invalidUAColumns,
names,
},
comment: `Invalid columns in User Attribute rules: ${invalidUAColumns.join(', ')}`,
});
}
return problems;
}
private _safeReadAclRules(docData: DocData, options: ReadAclOptions): ReadAclResults {

View File

@@ -176,6 +176,26 @@ export interface AclTableDescription {
groupByColLabels: string[] | null; // Labels of groupby columns for summary tables, or null.
}
export interface AclResources {
tables: {[tableId: string]: AclTableDescription};
problems: AclRuleProblem[];
}
export interface AclRuleProblem {
tables?: {
tableIds: string[],
};
columns?: {
tableId: string,
colIds: string[],
};
userAttributes?: {
invalidUAColumns: string[],
names: string[],
}
comment: string;
}
export function getTableTitle(table: AclTableDescription): string {
let {title} = table;
if (table.groupByColLabels) {
@@ -349,7 +369,7 @@ export interface ActiveDocAPI {
* for editing ACLs. It is only available to users who can edit ACLs, and lists all resources
* regardless of rules that may block access to them.
*/
getAclResources(): Promise<{[tableId: string]: AclTableDescription}>;
getAclResources(): Promise<AclResources>;
/**
* Wait for document to finish initializing.