(core) Trim unapplicable permissions bits for column rules, both at parse time, and in UI

Summary:
- UI now trims column rules before saving.
- When rules are loaded, bits that aren't applicable to a resource get ignored.
  This should fix the incorrect behavior in existing docs without a migration.

Test Plan:
- Added test of UI, that it now sends trimmed rules
- Added a unitteset of new trimPermissions() function
- Add test of fixed interpretation of existing rules: now only permission bits
  applicable to a resource get respected. I.e. create/delete/schemaEdit are
  ignored in column rules, and schemaEdit is also ignored in table rules.
- Note that DuplicateTest was affected: updated on the assumption that
  schemaEdit still can't actually apply at a table level.

Reviewers: georgegevoian, paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D4205
This commit is contained in:
Dmitry S
2024-03-03 22:41:48 -05:00
parent 0532ed6547
commit ca8ac806db
6 changed files with 63 additions and 16 deletions

View File

@@ -41,7 +41,10 @@ export type PartialPermissionSet = PermissionSet<PartialPermissionValue>;
export type MixedPermissionSet = PermissionSet<MixedPermissionValue>;
export type TablePermissionSet = PermissionSet<TablePermissionValue>;
const PERMISSION_BITS: {[letter: string]: keyof PermissionSet} = {
// One of the strings 'read', 'update', etc.
export type PermissionKey = keyof PermissionSet;
const PERMISSION_BITS: {[letter: string]: PermissionKey} = {
R: 'read',
C: 'create',
U: 'update',
@@ -60,6 +63,9 @@ const ALIASES: {[key: string]: string} = {
};
const REVERSE_ALIASES = fromPairs(Object.entries(ALIASES).map(([alias, value]) => [value, alias]));
export const AVAILABLE_BITS_TABLES: PermissionKey[] = ['read', 'update', 'create', 'delete'];
export const AVAILABLE_BITS_COLUMNS: PermissionKey[] = ['read', 'update'];
// Comes in useful for initializing unset PermissionSets.
export function emptyPermissionSet(): PartialPermissionSet {
return {read: "", create: "", update: "", delete: "", schemaEdit: ""};
@@ -141,6 +147,20 @@ export function mergePartialPermissions(a: PartialPermissionSet, b: PartialPermi
return mergePermissions([a, b], ([_a, _b]) => combinePartialPermission(_a, _b));
}
/**
* Returns permissions trimmed to include only the available bits, and empty for any other bits.
*/
export function trimPermissions(
permissions: PartialPermissionSet, availableBits: PermissionKey[]
): PartialPermissionSet {
const trimmed = emptyPermissionSet();
for (const bit of availableBits) {
trimmed[bit] = permissions[bit];
}
return trimmed;
}
/**
* Merge a list of PermissionSets by combining individual bits.
*/

View File

@@ -1,4 +1,5 @@
import {parsePermissions, permissionSetToText, splitSchemaEditPermissionSet} from 'app/common/ACLPermissions';
import {AVAILABLE_BITS_COLUMNS, AVAILABLE_BITS_TABLES, trimPermissions} from 'app/common/ACLPermissions';
import {ACLShareRules, TableWithOverlay} from 'app/common/ACLShareRules';
import {AclRuleProblem} from 'app/common/ActiveDocAPI';
import {DocData} from 'app/common/DocData';
@@ -537,13 +538,18 @@ function readAclRules(docData: DocData, {log, compile, enrichRulesForImplementat
if (hasShares && rule.id >= 0) {
aclFormulaParsed = shareRules.transformNonShareRules({rule, aclFormulaParsed});
}
let permissions = parsePermissions(String(rule.permissionsText));
if (tableId !== '*' && tableId !== SPECIAL_RULES_TABLE_ID) {
const availableBits = (colIds === '*') ? AVAILABLE_BITS_TABLES : AVAILABLE_BITS_COLUMNS;
permissions = trimPermissions(permissions, availableBits);
}
body.push({
origRecord: rule,
aclFormula: String(rule.aclFormula),
matchFunc: rule.aclFormula ? compile?.(aclFormulaParsed) : defaultMatchFunc,
memo: rule.memo,
permissions: parsePermissions(String(rule.permissionsText)),
permissionsText: String(rule.permissionsText),
permissions,
permissionsText: permissionSetToText(permissions)
});
}
}