(core) Support for $ syntax in ACL rules

Summary: Adding support for the "$" syntax in ACL rules.

Test Plan: Updated

Reviewers: georgegevoian, dsagal

Reviewed By: georgegevoian, dsagal

Differential Revision: https://phab.getgrist.com/D3692
This commit is contained in:
Jarosław Sadziński 2022-11-09 15:57:06 +01:00
parent b29ce996b6
commit 101450262c
4 changed files with 51 additions and 1 deletions

View File

@ -1155,6 +1155,7 @@ class ObsRulePart extends Disposable {
private _completions = Computed.create<string[]>(this, (use) => [
...use(this._ruleSet.accessRules.userAttrChoices).map(opt => opt.value),
...this._ruleSet.getValidColIds().map(colId => `rec.${colId}`),
...this._ruleSet.getValidColIds().map(colId => `$${colId}`),
...this._ruleSet.getValidColIds().map(colId => `newRec.${colId}`),
]);

View File

@ -7,6 +7,7 @@ from collections import namedtuple
import asttokens
import six
from codebuilder import replace_dollar_attrs
def parse_acl_formula(acl_formula):
"""
@ -31,6 +32,7 @@ def parse_acl_formula(acl_formula):
if isinstance(acl_formula, six.binary_type):
acl_formula = acl_formula.decode('utf8')
try:
acl_formula = replace_dollar_attrs(acl_formula)
tree = ast.parse(acl_formula, mode='eval')
result = _TreeConverter().visit(tree)
for part in tokenize.generate_tokens(io.StringIO(acl_formula).readline):

View File

@ -122,6 +122,26 @@ def make_formula_body(formula, default_value, assoc_value=None):
return final_formula
def replace_dollar_attrs(formula):
"""
Translates formula "$" expression into rec. expression. This is extracted from the
make_formula_body function.
"""
formula_builder_text = textbuilder.Text(formula)
tmp_patches = textbuilder.make_regexp_patches(formula, DOLLAR_REGEX, 'DOLLAR')
tmp_formula = textbuilder.Replacer(formula_builder_text, tmp_patches)
atok = asttokens.ASTTokens(tmp_formula.get_text(), parse=True)
patches = []
for node in ast.walk(atok.tree):
if isinstance(node, ast.Name) and node.id.startswith('DOLLAR'):
input_pos = tmp_formula.map_back_offset(node.first_token.startpos)
m = DOLLAR_REGEX.match(formula, input_pos)
if m:
patches.append(textbuilder.make_patch(formula, m.start(0), m.end(0), 'rec.'))
final_formula = textbuilder.Replacer(formula_builder_text, patches)
return final_formula.get_text()
def _create_syntax_error_code(builder, input_text, err):
"""
Returns the text for a function that raises the given SyntaxError and includes the offending

View File

@ -33,6 +33,15 @@ class TestACLFormula(unittest.TestCase):
['List', ['Const', 'sally@'], ['Const', 'xie@']]
]])
self.assertEqual(parse_acl_formula(
"$office == 'Seattle' and user.email in ['sally@', 'xie@']"),
['And',
['Eq', ['Attr', ['Name', 'rec'], 'office'], ['Const', 'Seattle']],
['In',
['Attr', ['Name', 'user'], 'email'],
['List', ['Const', 'sally@'], ['Const', 'xie@']]
]])
self.assertEqual(parse_acl_formula(
"user.IsAdmin or rec.assigned is None or (not newRec.HasDuplicates and rec.StatusIndex <= newRec.StatusIndex)"),
['Or',
@ -44,6 +53,17 @@ class TestACLFormula(unittest.TestCase):
]
])
self.assertEqual(parse_acl_formula(
"user.IsAdmin or $assigned is None or (not newRec.HasDuplicates and $StatusIndex <= newRec.StatusIndex)"),
['Or',
['Attr', ['Name', 'user'], 'IsAdmin'],
['Is', ['Attr', ['Name', 'rec'], 'assigned'], ['Const', None]],
['And',
['Not', ['Attr', ['Name', 'newRec'], 'HasDuplicates']],
['LtE', ['Attr', ['Name', 'rec'], 'StatusIndex'], ['Attr', ['Name', 'newRec'], 'StatusIndex']]
]
])
self.assertEqual(parse_acl_formula(
"r.A <= n.A + 1 or r.A >= n.A - 1 or r.B < n.B * 2.5 or r.B > n.B / 2.5 or r.C % 2 != 0"),
['Or',
@ -71,6 +91,13 @@ class TestACLFormula(unittest.TestCase):
['IsNot', ['Attr', ['Name', 'rec'], 'A'], ['Const', False]]
])
self.assertEqual(parse_acl_formula(
"$A is True or $A is not False"),
['Or',
['Is', ['Attr', ['Name', 'rec'], 'A'], ['Const', True]],
['IsNot', ['Attr', ['Name', 'rec'], 'A'], ['Const', False]]
])
self.assertEqual(parse_acl_formula(
"user.Office.City == 'Seattle' and user.Status.IsActive"),
['And',
@ -171,7 +198,7 @@ class TestACLFormulaUserActions(test_engine.EngineTestCase):
"aclFormula": ["not user.IsGood", ""],
}])
self.assertPartialOutActions(out_actions, { "stored": [
[ 'BulkUpdateRecord', '_grist_ACLRules', [2, 3], {
['BulkUpdateRecord', '_grist_ACLRules', [2, 3], {
"aclFormula": ["not user.IsGood", ""],
"aclFormulaParsed": ['["Not", ["Attr", ["Name", "user"], "IsGood"]]', ''],
}],