2021-12-07 11:21:16 +00:00
|
|
|
import {PartialPermissionSet} from 'app/common/ACLPermissions';
|
|
|
|
import {CellValue, RowRecord} from 'app/common/DocActions';
|
|
|
|
import {MetaRowRecord} from 'app/common/TableData';
|
|
|
|
import {Role} from './roles';
|
2020-10-19 14:25:21 +00:00
|
|
|
|
2020-11-17 21:49:32 +00:00
|
|
|
export interface RuleSet {
|
|
|
|
tableId: '*' | string;
|
|
|
|
colIds: '*' | string[];
|
2020-12-04 23:29:29 +00:00
|
|
|
// The default permissions for this resource, if set, are represented by a RulePart with
|
|
|
|
// aclFormula of "", which must be the last element of body.
|
2020-11-17 21:49:32 +00:00
|
|
|
body: RulePart[];
|
2020-10-12 13:50:07 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 21:49:32 +00:00
|
|
|
export interface RulePart {
|
2021-12-07 11:21:16 +00:00
|
|
|
origRecord?: MetaRowRecord<'_grist_ACLRules'>; // Original record used to create this RulePart.
|
2020-11-17 21:49:32 +00:00
|
|
|
aclFormula: string;
|
|
|
|
permissions: PartialPermissionSet;
|
|
|
|
permissionsText: string; // The text version of PermissionSet, as stored.
|
|
|
|
|
|
|
|
// Compiled version of aclFormula.
|
|
|
|
matchFunc?: AclMatchFunc;
|
2021-02-15 21:36:33 +00:00
|
|
|
|
|
|
|
// Optional memo, currently extracted from comment in formula.
|
|
|
|
memo?: string;
|
2020-10-12 13:50:07 +00:00
|
|
|
}
|
|
|
|
|
2021-03-01 16:51:30 +00:00
|
|
|
// Light wrapper for reading records or user attributes.
|
2020-11-17 21:49:32 +00:00
|
|
|
export interface InfoView {
|
|
|
|
get(key: string): CellValue;
|
|
|
|
toJSON(): {[key: string]: any};
|
2020-10-19 14:25:21 +00:00
|
|
|
}
|
|
|
|
|
2021-03-01 16:51:30 +00:00
|
|
|
// As InfoView, but also supporting writing.
|
|
|
|
export interface InfoEditor {
|
|
|
|
get(key: string): CellValue;
|
|
|
|
set(key: string, val: CellValue): this;
|
|
|
|
toJSON(): {[key: string]: any};
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:49:32 +00:00
|
|
|
// Represents user info, which may include properties which are themselves RowRecords.
|
2021-07-15 00:45:53 +00:00
|
|
|
export interface UserInfo {
|
|
|
|
Name: string | null;
|
|
|
|
Email: string | null;
|
|
|
|
Access: Role | null;
|
|
|
|
Origin: string | null;
|
|
|
|
LinkKey: Record<string, string | undefined>;
|
|
|
|
UserID: number | null;
|
2022-10-17 09:47:16 +00:00
|
|
|
UserRef: string | null;
|
2021-07-15 00:45:53 +00:00
|
|
|
[attributes: string]: unknown;
|
|
|
|
toJSON(): {[key: string]: any};
|
|
|
|
}
|
2020-11-17 21:49:32 +00:00
|
|
|
|
2020-11-03 23:44:09 +00:00
|
|
|
/**
|
2020-11-17 21:49:32 +00:00
|
|
|
* Input into the AclMatchFunc. Compiled formulas evaluate AclMatchInput to produce a boolean.
|
2020-11-03 23:44:09 +00:00
|
|
|
*/
|
2020-11-17 21:49:32 +00:00
|
|
|
export interface AclMatchInput {
|
|
|
|
user: UserInfo;
|
|
|
|
rec?: InfoView;
|
|
|
|
newRec?: InfoView;
|
2020-11-03 23:44:09 +00:00
|
|
|
}
|
|
|
|
|
2020-10-19 14:25:21 +00:00
|
|
|
/**
|
2020-11-17 21:49:32 +00:00
|
|
|
* The actual boolean function that can evaluate a request. The result of compiling ParsedAclFormula.
|
2020-10-19 14:25:21 +00:00
|
|
|
*/
|
2020-11-17 21:49:32 +00:00
|
|
|
export type AclMatchFunc = (input: AclMatchInput) => boolean;
|
2020-10-19 14:25:21 +00:00
|
|
|
|
2020-11-03 23:44:09 +00:00
|
|
|
/**
|
2020-11-17 21:49:32 +00:00
|
|
|
* Representation of a parsed ACL formula.
|
2020-11-03 23:44:09 +00:00
|
|
|
*/
|
2021-05-13 13:29:28 +00:00
|
|
|
type PrimitiveCellValue = number|string|boolean|null;
|
(core) Speed up and upgrade build.
Summary:
- Upgrades to build-related packages:
- Upgrade typescript, related libraries and typings.
- Upgrade webpack, eslint; add tsc-watch, node-dev, eslint_d.
- Build organization changes:
- Build webpack from original typescript, transpiling only; with errors still
reported by a background tsc watching process.
- Typescript-related changes:
- Reduce imports of AWS dependencies (very noticeable speedup)
- Avoid auto-loading global @types
- Client code is now built with isolatedModules flag (for safe transpilation)
- Use allowJs to avoid copying JS files manually.
- Linting changes
- Enhance Arcanist ESLintLinter to run before/after commands, and set up to use eslint_d
- Update eslint config, and include .eslintignore to avoid linting generated files.
- Include a bunch of eslint-prompted and eslint-generated fixes
- Add no-unused-expression rule to eslint, and fix a few warnings about it
- Other items:
- Refactor cssInput to avoid circular dependency
- Remove a bit of unused code, libraries, dependencies
Test Plan: No behavior changes, all existing tests pass. There are 30 tests fewer reported because `test_gpath.py` was removed (it's been unused for years)
Reviewers: paulfitz
Reviewed By: paulfitz
Subscribers: paulfitz
Differential Revision: https://phab.getgrist.com/D3498
2022-06-27 20:09:41 +00:00
|
|
|
export type ParsedAclFormula = [string, ...(ParsedAclFormula|PrimitiveCellValue)[]];
|
2020-10-19 14:25:21 +00:00
|
|
|
|
2021-03-10 14:08:46 +00:00
|
|
|
/**
|
|
|
|
* Observations about a formula.
|
|
|
|
*/
|
|
|
|
export interface FormulaProperties {
|
|
|
|
hasRecOrNewRec?: boolean;
|
2021-05-13 13:29:28 +00:00
|
|
|
usedColIds?: string[];
|
2021-03-10 14:08:46 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 21:49:32 +00:00
|
|
|
export interface UserAttributeRule {
|
2020-12-04 23:29:29 +00:00
|
|
|
origRecord?: RowRecord; // Original record used to create this UserAttributeRule.
|
2020-11-17 21:49:32 +00:00
|
|
|
name: string; // Should be unique among UserAttributeRules.
|
|
|
|
tableId: string; // Table in which to look up an existing attribute.
|
|
|
|
lookupColId: string; // Column in tableId in which to do the lookup.
|
|
|
|
charId: string; // Attribute to look up, possibly a path. E.g. 'Email' or 'office.city'.
|
2020-10-19 14:25:21 +00:00
|
|
|
}
|
2021-03-10 14:08:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check some key facts about the formula.
|
|
|
|
*/
|
|
|
|
export function getFormulaProperties(formula: ParsedAclFormula) {
|
2021-05-23 17:43:11 +00:00
|
|
|
const result: FormulaProperties = {};
|
2021-03-10 14:08:46 +00:00
|
|
|
if (usesRec(formula)) { result.hasRecOrNewRec = true; }
|
2021-05-13 13:29:28 +00:00
|
|
|
const colIds = new Set<string>();
|
|
|
|
collectRecColIds(formula, colIds);
|
|
|
|
result.usedColIds = Array.from(colIds);
|
2021-03-10 14:08:46 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check whether a formula mentions `rec` or `newRec`.
|
|
|
|
*/
|
|
|
|
export function usesRec(formula: ParsedAclFormula): boolean {
|
|
|
|
if (!Array.isArray(formula)) { throw new Error('expected a list'); }
|
2021-05-13 13:29:28 +00:00
|
|
|
if (isRecOrNewRec(formula)) {
|
2021-03-10 14:08:46 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return formula.some(el => {
|
|
|
|
if (!Array.isArray(el)) { return false; }
|
2021-05-13 13:29:28 +00:00
|
|
|
return usesRec(el);
|
2021-03-10 14:08:46 +00:00
|
|
|
});
|
|
|
|
}
|
2021-05-13 13:29:28 +00:00
|
|
|
|
|
|
|
function isRecOrNewRec(formula: ParsedAclFormula|PrimitiveCellValue): boolean {
|
|
|
|
return Array.isArray(formula) &&
|
|
|
|
formula[0] === 'Name' &&
|
|
|
|
(formula[1] === 'rec' || formula[1] === 'newRec');
|
|
|
|
}
|
|
|
|
|
|
|
|
function collectRecColIds(formula: ParsedAclFormula, colIds: Set<string>): void {
|
|
|
|
if (!Array.isArray(formula)) { throw new Error('expected a list'); }
|
|
|
|
if (formula[0] === 'Attr' && isRecOrNewRec(formula[1])) {
|
|
|
|
const colId = formula[2];
|
|
|
|
colIds.add(String(colId));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
formula.forEach(el => Array.isArray(el) && collectRecColIds(el, colIds));
|
|
|
|
}
|