You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/sandbox/grist/test_acl_formula.py

179 lines
6.8 KiB

# -*- coding: utf-8 -*-
# pylint:disable=line-too-long
import unittest
from acl_formula import parse_acl_formula
import test_engine
class TestACLFormula(unittest.TestCase):
def test_basic(self):
# Test a few basic formulas and structures, hitting everything we expect to support
self.assertEqual(parse_acl_formula(
"user.Email == 'X@'"),
["Eq", ["Attr", ["Name", "user"], "Email"],
["Const", "X@"]])
self.assertEqual(parse_acl_formula(
"user.Role in ('editors', 'owners')"),
["In", ["Attr", ["Name", "user"], "Role"],
["List", ["Const", "editors"], ["Const", "owners"]]])
self.assertEqual(parse_acl_formula(
"user.Role not in ('editors', 'owners')"),
["NotIn", ["Attr", ["Name", "user"], "Role"],
["List", ["Const", "editors"], ["Const", "owners"]]])
self.assertEqual(parse_acl_formula(
"rec.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',
['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',
['LtE',
['Attr', ['Name', 'r'], 'A'],
['Add', ['Attr', ['Name', 'n'], 'A'], ['Const', 1]]],
['GtE',
['Attr', ['Name', 'r'], 'A'],
['Sub', ['Attr', ['Name', 'n'], 'A'], ['Const', 1]]],
['Lt',
['Attr', ['Name', 'r'], 'B'],
['Mult', ['Attr', ['Name', 'n'], 'B'], ['Const', 2.5]]],
['Gt',
['Attr', ['Name', 'r'], 'B'],
['Div', ['Attr', ['Name', 'n'], 'B'], ['Const', 2.5]]],
['NotEq',
['Mod', ['Attr', ['Name', 'r'], 'C'], ['Const', 2]],
['Const', 0]]
])
self.assertEqual(parse_acl_formula(
"rec.A is True or rec.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',
['Eq',
['Attr', ['Attr', ['Name', 'user'], 'Office'], 'City'],
['Const', 'Seattle']],
['Attr', ['Attr', ['Name', 'user'], 'Status'], 'IsActive']
])
self.assertEqual(parse_acl_formula(
"True # Comment! "),
['Comment', ['Const', True], 'Comment!'])
self.assertEqual(parse_acl_formula(
"\"#x\" == \" # Not a comment \"#Comment!"),
['Comment',
['Eq', ['Const', '#x'], ['Const', ' # Not a comment ']],
'Comment!'
])
self.assertEqual(parse_acl_formula(
"# Allow owners\nuser.Access == 'owners' # ignored\n# comment ignored"),
['Comment',
['Eq', ['Attr', ['Name', 'user'], 'Access'], ['Const', 'owners']],
'Allow owners'
])
def test_unsupported(self):
# Test a few constructs we expect to fail
# Not an expression
self.assertRaises(SyntaxError, parse_acl_formula, "return 1")
self.assertRaises(SyntaxError, parse_acl_formula, "def foo(): pass")
# Unsupported node type
self.assertRaisesRegex(ValueError, r'Unsupported syntax', parse_acl_formula, "max(rec)")
self.assertRaisesRegex(ValueError, r'Unsupported syntax', parse_acl_formula, "user.id in {1, 2, 3}")
self.assertRaisesRegex(ValueError, r'Unsupported syntax', parse_acl_formula, "1 if user.IsAnon else 2")
# Unsupported operation
self.assertRaisesRegex(ValueError, r'Unsupported syntax', parse_acl_formula, "1 | 2")
self.assertRaisesRegex(ValueError, r'Unsupported syntax', parse_acl_formula, "1 << 2")
self.assertRaisesRegex(ValueError, r'Unsupported syntax', parse_acl_formula, "~test")
# Syntax error
self.assertRaises(SyntaxError, parse_acl_formula, "[(]")
self.assertRaises(SyntaxError, parse_acl_formula, "user.id in (1,2))")
self.assertRaisesRegex(SyntaxError, r'invalid syntax on line 1 col 9', parse_acl_formula, "foo and !bar")
class TestACLFormulaUserActions(test_engine.EngineTestCase):
def test_acl_actions(self):
# Adding or updating ACLRules automatically includes aclFormula compilation.
# Single Add
out_actions = self.apply_user_action(
['AddRecord', '_grist_ACLRules', None, {"resource": 1, "aclFormula": "user.UserID == 7"}],
)
self.assertPartialOutActions(out_actions, { "stored": [
["AddRecord", "_grist_ACLRules", 1, {"resource": 1, "aclFormula": "user.UserID == 7",
"aclFormulaParsed": '["Eq", ["Attr", ["Name", "user"], "UserID"], ["Const", 7]]',
"rulePos": 1.0
}],
]})
# Single Update
out_actions = self.apply_user_action(
['UpdateRecord', '_grist_ACLRules', 1, {
"aclFormula": "user.UserID == 8",
"aclFormulaParsed": "hello"
}],
)
self.assertPartialOutActions(out_actions, { "stored": [
["UpdateRecord", "_grist_ACLRules", 1, {
"aclFormula": "user.UserID == 8",
"aclFormulaParsed": '["Eq", ["Attr", ["Name", "user"], "UserID"], ["Const", 8]]',
}],
]})
# BulkAddRecord
out_actions = self.apply_user_action(['BulkAddRecord', '_grist_ACLRules', [None, None], {
"resource": [1, 1],
"aclFormula": ["user.IsGood", "user.IsBad"],
"aclFormulaParsed": ["[1]", '["ignored"]'], # Should get overwritten
}])
self.assertPartialOutActions(out_actions, { "stored": [
[ 'BulkAddRecord', '_grist_ACLRules', [2, 3], {
"resource": [1, 1],
"aclFormula": ["user.IsGood", "user.IsBad"],
"aclFormulaParsed": [ # Gets overwritten
'["Attr", ["Name", "user"], "IsGood"]',
'["Attr", ["Name", "user"], "IsBad"]',
],
"rulePos": [2.0, 3.0], # Gets filled in.
}],
]})
# BulkUpdateRecord
out_actions = self.apply_user_action(['BulkUpdateRecord', '_grist_ACLRules', [2, 3], {
"aclFormula": ["not user.IsGood", ""],
}])
self.assertPartialOutActions(out_actions, { "stored": [
[ 'BulkUpdateRecord', '_grist_ACLRules', [2, 3], {
"aclFormula": ["not user.IsGood", ""],
"aclFormulaParsed": ['["Not", ["Attr", ["Name", "user"], "IsGood"]]', ''],
}],
]})