(core) Add support for special ACL rules, for viewing rules and downloading documents.

Summary:
- Use special ACLResources of the form "*SPECIAL:<RuleType>" to represent
  special document-wide rules.
- Include default rules that give Read access to these resources to Owners only.
- Add UI with a checkbox to give access to everyone instead.
- Allow expanding the UI for advanced configuration.

- These rules don't actually have any behavior yet.

Test Plan: WIP

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2764
This commit is contained in:
Dmitry S
2021-03-24 23:00:58 -04:00
parent d8df2404c2
commit e14488bcc8
3 changed files with 247 additions and 25 deletions

View File

@@ -8,6 +8,8 @@ import sortBy = require('lodash/sortBy');
const defaultMatchFunc: AclMatchFunc = () => true;
export const SPECIAL_RULES_TABLE_ID = '*SPECIAL';
// This is the hard-coded default RuleSet that's added to any user-created default rule.
const DEFAULT_RULE_SET: RuleSet = {
tableId: '*',
@@ -30,6 +32,39 @@ const DEFAULT_RULE_SET: RuleSet = {
}],
};
const SPECIAL_RULE_SETS: Record<string, RuleSet> = {
AccessRules: {
tableId: SPECIAL_RULES_TABLE_ID,
colIds: ['AccessRules'],
body: [{
aclFormula: "user.Access in [OWNER]",
matchFunc: (input) => ['owners'].includes(String(input.user.Access)),
permissions: parsePermissions('+R'),
permissionsText: '+R',
}, {
aclFormula: "",
matchFunc: defaultMatchFunc,
permissions: parsePermissions('none'),
permissionsText: 'none',
}],
},
FullCopies: {
tableId: SPECIAL_RULES_TABLE_ID,
colIds: ['FullCopies'],
body: [{
aclFormula: "user.Access in [OWNER]",
matchFunc: (input) => ['owners'].includes(String(input.user.Access)),
permissions: parsePermissions('+R'),
permissionsText: '+R',
}, {
aclFormula: "",
matchFunc: defaultMatchFunc,
permissions: parsePermissions('none'),
permissionsText: 'none',
}],
}
};
// If the user-created rules become dysfunctional, we can swap in this emergency set.
// It grants full access to owners, and no access to anyone else.
const EMERGENCY_RULE_SET: RuleSet = {
@@ -59,6 +94,7 @@ export class ACLRuleCollection {
private _haveRules = false;
// Map of tableId to list of column RuleSets (those with colIds other than '*')
// Includes also SPECIAL_RULES_TABLE_ID.
private _columnRuleSets = new Map<string, RuleSet[]>();
// Maps 'tableId:colId' to one of the RuleSets in the list _columnRuleSets.get(tableId).
@@ -130,6 +166,24 @@ export class ACLRuleCollection {
const tableIds = new Set<string>();
let defaultRuleSet: RuleSet = DEFAULT_RULE_SET;
// Collect special rules, combining them with corresponding defaults.
const specialRuleSets = new Map<string, RuleSet>(Object.entries(SPECIAL_RULE_SETS));
for (const ruleSet of ruleSets) {
if (ruleSet.tableId === SPECIAL_RULES_TABLE_ID) {
const specialType = String(ruleSet.colIds);
const specialDefault = specialRuleSets.get(specialType);
if (!specialDefault) {
throw new Error(`Invalid rule for ${ruleSet.tableId}:${ruleSet.colIds}`);
}
specialRuleSets.set(specialType, {...ruleSet, body: [...ruleSet.body, ...specialDefault.body]});
}
}
// Insert the special rule sets into colRuleSets.
for (const ruleSet of specialRuleSets.values()) {
getSetMapValue(colRuleSets, SPECIAL_RULES_TABLE_ID, () => []).push(ruleSet);
}
this._haveRules = (ruleSets.length > 0);
for (const ruleSet of ruleSets) {
if (ruleSet.tableId === '*') {
@@ -142,6 +196,8 @@ export class ACLRuleCollection {
// tableId of '*' cannot list particular columns.
throw new Error(`Invalid rule for tableId ${ruleSet.tableId}, colIds ${ruleSet.colIds}`);
}
} else if (ruleSet.tableId === SPECIAL_RULES_TABLE_ID) {
// Skip, since we handled these separately earlier.
} else if (ruleSet.colIds === '*') {
tableIds.add(ruleSet.tableId);
if (tableRuleSets.has(ruleSet.tableId)) {