mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(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:
parent
b29ce996b6
commit
101450262c
@ -1155,6 +1155,7 @@ class ObsRulePart extends Disposable {
|
|||||||
private _completions = Computed.create<string[]>(this, (use) => [
|
private _completions = Computed.create<string[]>(this, (use) => [
|
||||||
...use(this._ruleSet.accessRules.userAttrChoices).map(opt => opt.value),
|
...use(this._ruleSet.accessRules.userAttrChoices).map(opt => opt.value),
|
||||||
...this._ruleSet.getValidColIds().map(colId => `rec.${colId}`),
|
...this._ruleSet.getValidColIds().map(colId => `rec.${colId}`),
|
||||||
|
...this._ruleSet.getValidColIds().map(colId => `$${colId}`),
|
||||||
...this._ruleSet.getValidColIds().map(colId => `newRec.${colId}`),
|
...this._ruleSet.getValidColIds().map(colId => `newRec.${colId}`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from collections import namedtuple
|
|||||||
import asttokens
|
import asttokens
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from codebuilder import replace_dollar_attrs
|
||||||
|
|
||||||
def parse_acl_formula(acl_formula):
|
def parse_acl_formula(acl_formula):
|
||||||
"""
|
"""
|
||||||
@ -31,6 +32,7 @@ def parse_acl_formula(acl_formula):
|
|||||||
if isinstance(acl_formula, six.binary_type):
|
if isinstance(acl_formula, six.binary_type):
|
||||||
acl_formula = acl_formula.decode('utf8')
|
acl_formula = acl_formula.decode('utf8')
|
||||||
try:
|
try:
|
||||||
|
acl_formula = replace_dollar_attrs(acl_formula)
|
||||||
tree = ast.parse(acl_formula, mode='eval')
|
tree = ast.parse(acl_formula, mode='eval')
|
||||||
result = _TreeConverter().visit(tree)
|
result = _TreeConverter().visit(tree)
|
||||||
for part in tokenize.generate_tokens(io.StringIO(acl_formula).readline):
|
for part in tokenize.generate_tokens(io.StringIO(acl_formula).readline):
|
||||||
|
@ -122,6 +122,26 @@ def make_formula_body(formula, default_value, assoc_value=None):
|
|||||||
return final_formula
|
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):
|
def _create_syntax_error_code(builder, input_text, err):
|
||||||
"""
|
"""
|
||||||
Returns the text for a function that raises the given SyntaxError and includes the offending
|
Returns the text for a function that raises the given SyntaxError and includes the offending
|
||||||
|
@ -33,6 +33,15 @@ class TestACLFormula(unittest.TestCase):
|
|||||||
['List', ['Const', 'sally@'], ['Const', 'xie@']]
|
['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(
|
self.assertEqual(parse_acl_formula(
|
||||||
"user.IsAdmin or rec.assigned is None or (not newRec.HasDuplicates and rec.StatusIndex <= newRec.StatusIndex)"),
|
"user.IsAdmin or rec.assigned is None or (not newRec.HasDuplicates and rec.StatusIndex <= newRec.StatusIndex)"),
|
||||||
['Or',
|
['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(
|
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"),
|
"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',
|
['Or',
|
||||||
@ -71,6 +91,13 @@ class TestACLFormula(unittest.TestCase):
|
|||||||
['IsNot', ['Attr', ['Name', 'rec'], 'A'], ['Const', False]]
|
['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(
|
self.assertEqual(parse_acl_formula(
|
||||||
"user.Office.City == 'Seattle' and user.Status.IsActive"),
|
"user.Office.City == 'Seattle' and user.Status.IsActive"),
|
||||||
['And',
|
['And',
|
||||||
@ -171,7 +198,7 @@ class TestACLFormulaUserActions(test_engine.EngineTestCase):
|
|||||||
"aclFormula": ["not user.IsGood", ""],
|
"aclFormula": ["not user.IsGood", ""],
|
||||||
}])
|
}])
|
||||||
self.assertPartialOutActions(out_actions, { "stored": [
|
self.assertPartialOutActions(out_actions, { "stored": [
|
||||||
[ 'BulkUpdateRecord', '_grist_ACLRules', [2, 3], {
|
['BulkUpdateRecord', '_grist_ACLRules', [2, 3], {
|
||||||
"aclFormula": ["not user.IsGood", ""],
|
"aclFormula": ["not user.IsGood", ""],
|
||||||
"aclFormulaParsed": ['["Not", ["Attr", ["Name", "user"], "IsGood"]]', ''],
|
"aclFormulaParsed": ['["Not", ["Attr", ["Name", "user"], "IsGood"]]', ''],
|
||||||
}],
|
}],
|
||||||
|
Loading…
Reference in New Issue
Block a user