Add __access__ to Model
issue9903 review324601002
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Add __access__ to Model
|
||||
* Add route wrapper to allow null origin
|
||||
* Add header grouping on report
|
||||
* Protect trusted devices against brute force attack
|
||||
|
||||
@@ -17,6 +17,11 @@ Class attributes are:
|
||||
It contains the a unique name to reference the model throughout the
|
||||
platform.
|
||||
|
||||
.. attribute:: Model.__access__
|
||||
|
||||
A set that contains the names of relation field for which the access rights
|
||||
are also checked for this model.
|
||||
|
||||
.. attribute:: Model.__rpc__
|
||||
|
||||
It contains a dictionary with method name as key and an instance of
|
||||
|
||||
@@ -29,6 +29,7 @@ CREATE TABLE ir_model_field (
|
||||
ttype VARCHAR,
|
||||
help TEXT,
|
||||
module VARCHAR,
|
||||
"access" BOOL,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (model) REFERENCES ir_model(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
@@ -20,7 +20,8 @@ CREATE TABLE ir_model_field (
|
||||
field_description VARCHAR,
|
||||
ttype VARCHAR,
|
||||
help TEXT,
|
||||
module VARCHAR
|
||||
module VARCHAR,
|
||||
"access" BOOLEAN
|
||||
);
|
||||
|
||||
|
||||
|
||||
+44
-9
@@ -243,6 +243,15 @@ class ModelField(ModelSQL, ModelView):
|
||||
depends=['module'])
|
||||
module = fields.Char('Module',
|
||||
help="Module in which this field is defined.")
|
||||
access = fields.Boolean(
|
||||
"Access",
|
||||
states={
|
||||
'invisible': ~Eval('relation'),
|
||||
},
|
||||
depends=['relation'],
|
||||
help="If checked, the access right on the model of the field "
|
||||
"is also tested against the relation of the field.")
|
||||
|
||||
_get_name_cache = Cache('ir.model.field.get_name')
|
||||
|
||||
@classmethod
|
||||
@@ -273,6 +282,7 @@ class ModelField(ModelSQL, ModelView):
|
||||
ir_model_field.relation.as_('relation'),
|
||||
ir_model_field.module.as_('module'),
|
||||
ir_model_field.help.as_('help'),
|
||||
ir_model_field.access.as_('access'),
|
||||
where=ir_model.model == model.__name__))
|
||||
model_fields = {f['name']: f for f in cursor_dict(cursor)}
|
||||
|
||||
@@ -283,27 +293,32 @@ class ModelField(ModelSQL, ModelView):
|
||||
relation = field.relation_name
|
||||
else:
|
||||
relation = None
|
||||
access = field_name in model.__access__
|
||||
|
||||
if field_name not in model_fields:
|
||||
cursor.execute(*ir_model_field.insert(
|
||||
[ir_model_field.model, ir_model_field.name,
|
||||
ir_model_field.field_description,
|
||||
ir_model_field.ttype, ir_model_field.relation,
|
||||
ir_model_field.help, ir_model_field.module],
|
||||
ir_model_field.help, ir_model_field.module,
|
||||
ir_model_field.access],
|
||||
[[
|
||||
model_id, field_name,
|
||||
field.string,
|
||||
field._type, relation,
|
||||
field.help, module_name]]))
|
||||
field.help, module_name,
|
||||
access]]))
|
||||
elif (model_fields[field_name]['field_description'] != field.string
|
||||
or model_fields[field_name]['ttype'] != field._type
|
||||
or model_fields[field_name]['relation'] != relation
|
||||
or model_fields[field_name]['help'] != field.help):
|
||||
or model_fields[field_name]['help'] != field.help
|
||||
or model_fields[field_name]['access'] != access):
|
||||
cursor.execute(*ir_model_field.update(
|
||||
[ir_model_field.field_description,
|
||||
ir_model_field.ttype, ir_model_field.relation,
|
||||
ir_model_field.help],
|
||||
[field.string, field._type, relation, field.help],
|
||||
ir_model_field.help, ir_model_field.access],
|
||||
[field.string, field._type, relation, field.help,
|
||||
access],
|
||||
where=(ir_model_field.id
|
||||
== model_fields[field_name]['id'])))
|
||||
|
||||
@@ -520,6 +535,18 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView):
|
||||
else:
|
||||
return access
|
||||
|
||||
def fill_models(Model, models):
|
||||
if Model.__name__ in models:
|
||||
return
|
||||
models.append(Model.__name__)
|
||||
for field_name in Model.__access__:
|
||||
field = getattr(Model, field_name)
|
||||
fill_models(field.get_target(), models)
|
||||
model2models = defaultdict(list)
|
||||
for model in models:
|
||||
fill_models(pool.get(model), model2models[model])
|
||||
|
||||
all_models = list(set(sum(model2models.values(), [])))
|
||||
default = {'read': True, 'write': True, 'create': True, 'delete': True}
|
||||
access = dict((m, default) for m in models)
|
||||
cursor.execute(*model_access.join(ir_model, 'LEFT',
|
||||
@@ -542,16 +569,24 @@ class ModelAccess(DeactivableMixin, ModelSQL, ModelView):
|
||||
Max(Case(
|
||||
(model_access.perm_delete == Literal(True), 1),
|
||||
else_=0)),
|
||||
where=ir_model.model.in_(models)
|
||||
where=ir_model.model.in_(all_models)
|
||||
& (model_access.active == Literal(True))
|
||||
& ((
|
||||
(user_group.user == user)
|
||||
& (group.active == Literal(True)))
|
||||
| (model_access.group == Null)),
|
||||
group_by=ir_model.model))
|
||||
access.update(dict(
|
||||
(m, {'read': r, 'write': w, 'create': c, 'delete': d})
|
||||
for m, r, w, c, d in cursor.fetchall()))
|
||||
raw_access = {
|
||||
m: {'read': r, 'write': w, 'create': c, 'delete': d}
|
||||
for m, r, w, c, d in cursor.fetchall()}
|
||||
|
||||
for model in models:
|
||||
access[model] = {
|
||||
perm: max(
|
||||
(raw_access[m][perm] for m in model2models[model]
|
||||
if m in raw_access),
|
||||
default=True)
|
||||
for perm in ['read', 'write', 'create', 'delete']}
|
||||
for model, maccess in access.items():
|
||||
cls._get_access_cache.set((user, model), maccess)
|
||||
return access
|
||||
|
||||
+27
-2
@@ -167,6 +167,25 @@ class Rule(ModelSQL, ModelView):
|
||||
|
||||
assert mode in cls.modes
|
||||
|
||||
model_names = []
|
||||
model2field = defaultdict(list)
|
||||
|
||||
def update_model_names(Model, path=None):
|
||||
if Model.__name__ in model_names:
|
||||
return
|
||||
model_names.append(Model.__name__)
|
||||
if path:
|
||||
model2field[Model.__name__].append(path)
|
||||
for field_name in Model.__access__:
|
||||
field = getattr(Model, field_name)
|
||||
Target = field.get_target()
|
||||
if path:
|
||||
target_path = path + '.' + field_name
|
||||
else:
|
||||
target_path = field_name
|
||||
update_model_names(Target, target_path)
|
||||
update_model_names(pool.get(model_name))
|
||||
|
||||
cursor = transaction.connection.cursor()
|
||||
user_id = transaction.user
|
||||
# root user above constraint
|
||||
@@ -179,7 +198,7 @@ class Rule(ModelSQL, ModelView):
|
||||
).join(model,
|
||||
condition=rule_group.model == model.id
|
||||
).select(rule_table.id,
|
||||
where=(model.model == model_name)
|
||||
where=(model.model.in_(model_names))
|
||||
& (getattr(rule_group, 'perm_%s' % mode) == Literal(True))
|
||||
& (rule_group.id.in_(
|
||||
rule_group_group.join(
|
||||
@@ -198,7 +217,7 @@ class Rule(ModelSQL, ModelView):
|
||||
cursor.execute(*rule_group.join(model,
|
||||
condition=rule_group.model == model.id
|
||||
).select(rule_group.id,
|
||||
where=(model.model == model_name)
|
||||
where=(model.model.in_(model_names))
|
||||
& ~rule_group.id.in_(rule_table.select(rule_table.rule_group))
|
||||
& rule_group.id.in_(rule_group_group.join(user_group,
|
||||
condition=rule_group_group.group == user_group.group
|
||||
@@ -215,6 +234,12 @@ class Rule(ModelSQL, ModelView):
|
||||
assert rule.domain, ('Rule domain empty,'
|
||||
'check if migration was done')
|
||||
dom = decoder.decode(rule.domain)
|
||||
target_model = rule.rule_group.model.model
|
||||
if target_model in model2field:
|
||||
target_dom = ['OR']
|
||||
for field in model2field[target_model]:
|
||||
target_dom.append((field, 'where', dom))
|
||||
dom = target_dom
|
||||
if rule.rule_group.global_p:
|
||||
clause_global[rule.rule_group].append(dom)
|
||||
else:
|
||||
|
||||
@@ -14,6 +14,8 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="field_description" colspan="3"/>
|
||||
<label name="help" yfill="1"/>
|
||||
<field name="help" colspan="3"/>
|
||||
<label name="access"/>
|
||||
<field name="access"/>
|
||||
<label name="module"/>
|
||||
<field name="module"/>
|
||||
</form>
|
||||
|
||||
@@ -7,5 +7,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="ttype"/>
|
||||
<field name="relation" expand="1"/>
|
||||
<field name="field_description" expand="1"/>
|
||||
<field name="access"/>
|
||||
<field name="module"/>
|
||||
</tree>
|
||||
|
||||
@@ -45,6 +45,7 @@ class Model(URLMixin, PoolBase, metaclass=ModelMeta):
|
||||
'fields_get': RPC(cache=dict(days=1)),
|
||||
'pre_validate': RPC(instantiate=0),
|
||||
}
|
||||
cls.__access__ = set()
|
||||
|
||||
# Copy fields and update depends
|
||||
for attr in dir(cls):
|
||||
|
||||
@@ -23,8 +23,20 @@ class TestAccessRelate(ModelSQL):
|
||||
value = fields.Integer("Value")
|
||||
|
||||
|
||||
class TestAccessModel(TestAccess):
|
||||
"Test Access from Model"
|
||||
__name__ = 'test.access.model'
|
||||
access = fields.Many2One('test.access', "Access")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__access__.add('access')
|
||||
|
||||
|
||||
def register(module):
|
||||
Pool.register(
|
||||
TestAccess,
|
||||
TestAccessRelate,
|
||||
TestAccessModel,
|
||||
module=module, type_='model')
|
||||
|
||||
@@ -17,8 +17,23 @@ class TestRuleRelation(ModelSQL):
|
||||
field = fields.Char("Field")
|
||||
|
||||
|
||||
class TestRuleModel(ModelSQL):
|
||||
"Test Rule from Model"
|
||||
__name__ = 'test.rule.model'
|
||||
__access__ = 'test.rule'
|
||||
|
||||
name = fields.Char("Name")
|
||||
rule = fields.Many2One('test.rule', "Rule")
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls.__access__.add('rule')
|
||||
|
||||
|
||||
def register(module):
|
||||
Pool.register(
|
||||
TestRule,
|
||||
TestRuleRelation,
|
||||
TestRuleModel,
|
||||
module=module, type_='model')
|
||||
|
||||
@@ -18,11 +18,13 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
def setUpClass(cls):
|
||||
activate_module('tests')
|
||||
|
||||
model_name = model_access_name = 'test.access'
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
pool = Pool()
|
||||
Model = pool.get('ir.model')
|
||||
model, = Model.search([('model', '=', 'test.access')])
|
||||
model, = Model.search([('model', '=', self.model_access_name)])
|
||||
return model
|
||||
|
||||
@property
|
||||
@@ -42,7 +44,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
def test_access_empty(self):
|
||||
"Test access without model access"
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
|
||||
self._assert(record)
|
||||
@@ -52,7 +54,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test access without group"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -67,7 +69,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test no access without group"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -82,7 +84,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test one access with groups"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -102,7 +104,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test one access without group"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -122,7 +124,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test all access with groups"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -142,7 +144,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test no access with groups"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -162,7 +164,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test one access with group"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -177,7 +179,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
"Test no access with group"
|
||||
pool = Pool()
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
ModelAccess.create([{
|
||||
'model': self.model.id,
|
||||
@@ -193,7 +195,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Group = pool.get('res.group')
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
group, = Group.create([{'name': 'Test'}])
|
||||
ModelAccess.create([{
|
||||
@@ -215,7 +217,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Group = pool.get('res.group')
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
group, = Group.create([{'name': 'Test'}])
|
||||
ModelAccess.create([{
|
||||
@@ -237,7 +239,7 @@ class _ModelAccessTestCase(unittest.TestCase):
|
||||
pool = Pool()
|
||||
Group = pool.get('res.group')
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
group, = Group.create([{'name': 'Test'}])
|
||||
ModelAccess.create([{
|
||||
@@ -259,13 +261,13 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
|
||||
|
||||
def _assert(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
TestAccess.read([record.id], ['field1'])
|
||||
TestAccess.search([])
|
||||
|
||||
def _assert_raises(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
with self.assertRaises(AccessError):
|
||||
TestAccess.read([record.id], ['field1'])
|
||||
with self.assertRaises(AccessError):
|
||||
@@ -275,7 +277,7 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
|
||||
def test_access_relate_empty(self):
|
||||
"Test access on search relate without model access"
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
record, = TestAccess.create([{}])
|
||||
|
||||
TestAccess.read([record.id], ['relate.value'])
|
||||
@@ -286,7 +288,7 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
|
||||
def test_access_relate(self):
|
||||
"Test access on search relate"
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
Model = pool.get('ir.model')
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
record, = TestAccess.create([{}])
|
||||
@@ -307,7 +309,7 @@ class ModelAccessReadTestCase(_ModelAccessTestCase):
|
||||
def test_no_access_relate(self):
|
||||
"Test no access on search relate"
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
Model = pool.get('ir.model')
|
||||
ModelAccess = pool.get('ir.model.access')
|
||||
record, = TestAccess.create([{}])
|
||||
@@ -333,12 +335,12 @@ class ModelAccessWriteTestCase(_ModelAccessTestCase):
|
||||
|
||||
def _assert(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
TestAccess.write([record], {})
|
||||
|
||||
def _assert_raises(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
with self.assertRaises(AccessError):
|
||||
TestAccess.write([record], {})
|
||||
|
||||
@@ -348,12 +350,12 @@ class ModelAccessCreateTestCase(_ModelAccessTestCase):
|
||||
|
||||
def _assert(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
TestAccess.create([{}])
|
||||
|
||||
def _assert_raises(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
with self.assertRaises(AccessError):
|
||||
TestAccess.create([{}])
|
||||
|
||||
@@ -363,16 +365,35 @@ class ModelAccessDeleteTestCase(_ModelAccessTestCase):
|
||||
|
||||
def _assert(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
TestAccess.delete([record])
|
||||
|
||||
def _assert_raises(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get('test.access')
|
||||
TestAccess = pool.get(self.model_name)
|
||||
with self.assertRaises(AccessError):
|
||||
TestAccess.delete([record])
|
||||
|
||||
|
||||
class ModelAccessModelTestCase(_ModelAccessTestCase):
|
||||
model_name = 'test.access.model'
|
||||
_perm = 'perm_read'
|
||||
|
||||
def _assert(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get(self.model_name)
|
||||
TestAccess.read([record.id], ['field1'])
|
||||
TestAccess.search([])
|
||||
|
||||
def _assert_raises(self, record):
|
||||
pool = Pool()
|
||||
TestAccess = pool.get(self.model_name)
|
||||
with self.assertRaises(AccessError):
|
||||
TestAccess.read([record.id], ['field1'])
|
||||
with self.assertRaises(AccessError):
|
||||
TestAccess.search([])
|
||||
|
||||
|
||||
class _ModelFieldAccessTestCase(unittest.TestCase):
|
||||
_perm = None
|
||||
|
||||
@@ -926,6 +947,8 @@ def suite():
|
||||
).loadTestsFromTestCase(ModelAccessCreateTestCase))
|
||||
suite_.addTests(unittest.TestLoader(
|
||||
).loadTestsFromTestCase(ModelAccessDeleteTestCase))
|
||||
suite_.addTests(unittest.TestLoader(
|
||||
).loadTestsFromTestCase(ModelAccessModelTestCase))
|
||||
suite_.addTests(unittest.TestLoader(
|
||||
).loadTestsFromTestCase(ModelFieldAccessReadTestCase))
|
||||
suite_.addTests(unittest.TestLoader(
|
||||
|
||||
@@ -623,6 +623,63 @@ class ModelRuleTestCase(unittest.TestCase):
|
||||
value, = TestRule.read([non_deletable.id], ['_delete'])
|
||||
self.assertEqual(value['_delete'], False)
|
||||
|
||||
@with_transaction()
|
||||
def test_model_with_rule(self):
|
||||
"Test model with rule"
|
||||
pool = Pool()
|
||||
TestRule = pool.get('test.rule')
|
||||
TestRuleModel = pool.get('test.rule.model')
|
||||
RuleGroup = pool.get('ir.rule.group')
|
||||
Model = pool.get('ir.model')
|
||||
|
||||
model, = Model.search([('model', '=', 'test.rule')])
|
||||
rule_group, = RuleGroup.create([{
|
||||
'name': "Field different from foo",
|
||||
'model': model.id,
|
||||
'global_p': True,
|
||||
'perm_read': True,
|
||||
'perm_create': False,
|
||||
'perm_write': False,
|
||||
'perm_delete': False,
|
||||
'rules': [('create', [{
|
||||
'domain': json.dumps(
|
||||
[('field', '!=', 'foo')]),
|
||||
}])],
|
||||
}])
|
||||
rule, = TestRule.create([{'field': 'bar'}])
|
||||
test, = TestRuleModel.create([{'rule': rule.id, 'name': 'foo'}])
|
||||
|
||||
TestRuleModel.read([test.id], ['name'])
|
||||
|
||||
@with_transaction()
|
||||
def test_model_with_rule_fail(self):
|
||||
"Test model with rule fail"
|
||||
pool = Pool()
|
||||
TestRule = pool.get('test.rule')
|
||||
TestRuleModel = pool.get('test.rule.model')
|
||||
RuleGroup = pool.get('ir.rule.group')
|
||||
Model = pool.get('ir.model')
|
||||
|
||||
model, = Model.search([('model', '=', 'test.rule')])
|
||||
rule_group, = RuleGroup.create([{
|
||||
'name': "Field different from foo",
|
||||
'model': model.id,
|
||||
'global_p': True,
|
||||
'perm_read': True,
|
||||
'perm_create': False,
|
||||
'perm_write': False,
|
||||
'perm_delete': False,
|
||||
'rules': [('create', [{
|
||||
'domain': json.dumps(
|
||||
[('field', '!=', 'foo')]),
|
||||
}])],
|
||||
}])
|
||||
rule, = TestRule.create([{'field': 'foo'}])
|
||||
test, = TestRuleModel.create([{'rule': rule.id, 'name': 'foo'}])
|
||||
|
||||
with self.assertRaisesRegex(AccessError, "Field different from foo"):
|
||||
TestRuleModel.read([test.id], ['name'])
|
||||
|
||||
|
||||
def suite():
|
||||
suite_ = unittest.TestSuite()
|
||||
|
||||
@@ -246,6 +246,23 @@ class ModuleTestCase(unittest.TestCase):
|
||||
msg="Wrong '%s' type for _rec_name of %s'" % (
|
||||
field._type, mname))
|
||||
|
||||
@with_transaction()
|
||||
def test_model__access__(self):
|
||||
"Test existing model __access__"
|
||||
pool = Pool()
|
||||
for mname, Model in pool.iterobject():
|
||||
if not isregisteredby(Model, self.module):
|
||||
continue
|
||||
for field_name in Model.__access__:
|
||||
self.assertIn(field_name, Model._fields.keys(),
|
||||
msg="Wrong __access__ '%s' for %s" % (field_name, mname))
|
||||
field = Model._fields[field_name]
|
||||
Target = field.get_target()
|
||||
self.assertTrue(
|
||||
Target,
|
||||
msg='Missing target for __access__ "%s" of %s' % (
|
||||
field_name, mname))
|
||||
|
||||
@with_transaction()
|
||||
def test_view(self):
|
||||
'Test validity of all views of the module'
|
||||
@@ -529,10 +546,22 @@ class ModuleTestCase(unittest.TestCase):
|
||||
'Test missing default model access'
|
||||
pool = Pool()
|
||||
Access = pool.get('ir.model.access')
|
||||
no_groups = {a.model.name for a in Access.search([
|
||||
no_groups = {a.model.model for a in Access.search([
|
||||
('group', '=', None),
|
||||
])}
|
||||
with_groups = {a.model.name for a in Access.search([
|
||||
|
||||
def has_access(Model, models):
|
||||
if Model.__name__ in models:
|
||||
return True
|
||||
for field_name in Model.__access__:
|
||||
Target = Model._fields[field_name].get_target()
|
||||
if has_access(Target, models):
|
||||
return True
|
||||
for mname, Model in pool.iterobject():
|
||||
if has_access(Model, no_groups):
|
||||
no_groups.add(mname)
|
||||
|
||||
with_groups = {a.model.model for a in Access.search([
|
||||
('group', '!=', None),
|
||||
])}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user