[MERGE] trunk-acl-thu (check read/write access restrictions on fields with a groups attribute)
bzr revid: rco@openerp.com-20121217101528-nt5hsjvvodaap57m
This commit is contained in:
commit
eecc7de437
|
@ -141,10 +141,6 @@ for users who do not belong to the authorized groups:
|
||||||
|
|
||||||
.. note:: The tests related to this feature are in ``openerp/tests/test_acl.py``.
|
.. note:: The tests related to this feature are in ``openerp/tests/test_acl.py``.
|
||||||
|
|
||||||
.. warning:: At the time of writing the implementation of this feature is partial
|
|
||||||
and does not yet restrict read/write RPC access to the field.
|
|
||||||
The corresponding test is written already but currently disabled.
|
|
||||||
|
|
||||||
Workflow transition rules
|
Workflow transition rules
|
||||||
+++++++++++++++++++++++++
|
+++++++++++++++++++++++++
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ BZrmED0AAAAASUVORK5CYII=
|
||||||
<field name="signature">--
|
<field name="signature">--
|
||||||
Mr Demo</field>
|
Mr Demo</field>
|
||||||
<field name="company_id" ref="main_company"/>
|
<field name="company_id" ref="main_company"/>
|
||||||
<field name="groups_id" eval="[(6,0,[ref('base.group_user')])]"/>
|
<field name="groups_id" eval="[(6,0,[ref('base.group_user'), ref('base.group_partner_manager')])]"/>
|
||||||
<field name="image">/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
|
<field name="image">/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
|
||||||
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
|
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
|
||||||
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACEAIQDASIA
|
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCACEAIQDASIA
|
||||||
|
|
|
@ -1560,7 +1560,7 @@ class column_info(object):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s(%s, %s, %s, %s, %s)' % (
|
return '%s(%s, %s, %s, %s, %s)' % (
|
||||||
self.__name__, self.name, self.column,
|
self.__class__.__name__, self.name, self.column,
|
||||||
self.parent_model, self.parent_column, self.original_parent)
|
self.parent_model, self.parent_column, self.original_parent)
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -3537,6 +3537,37 @@ class BaseModel(object):
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def check_field_access_rights(self, cr, user, operation, fields, context=None):
|
||||||
|
"""
|
||||||
|
Check the user access rights on the given fields. This raises Access
|
||||||
|
Denied if the user does not have the rights. Otherwise it returns the
|
||||||
|
fields (as is if the fields is not falsy, or the readable/writable
|
||||||
|
fields if fields is falsy).
|
||||||
|
"""
|
||||||
|
def p(field_name):
|
||||||
|
"""Predicate to test if the user has access to the given field name."""
|
||||||
|
# Ignore requested field if it doesn't exist. This is ugly but
|
||||||
|
# it seems to happen at least with 'name_alias' on res.partner.
|
||||||
|
if field_name not in self._all_columns:
|
||||||
|
return True
|
||||||
|
field = self._all_columns[field_name].column
|
||||||
|
if field.groups:
|
||||||
|
return self.user_has_groups(cr, user, groups=field.groups, context=context)
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
if not fields:
|
||||||
|
fields = filter(p, self._all_columns.keys())
|
||||||
|
else:
|
||||||
|
filtered_fields = filter(lambda a: not p(a), fields)
|
||||||
|
if filtered_fields:
|
||||||
|
_logger.warning('Access Denied by ACLs for operation: %s, uid: %s, model: %s, fields: %s', operation, user, self._name, ', '.join(filtered_fields))
|
||||||
|
raise except_orm(
|
||||||
|
_('Access Denied'),
|
||||||
|
_('The requested operation cannot be completed due to security restrictions. '
|
||||||
|
'Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
|
||||||
|
(self._description, operation))
|
||||||
|
return fields
|
||||||
|
|
||||||
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
|
def read(self, cr, user, ids, fields=None, context=None, load='_classic_read'):
|
||||||
""" Read records with given ids with the given fields
|
""" Read records with given ids with the given fields
|
||||||
|
|
||||||
|
@ -3562,8 +3593,7 @@ class BaseModel(object):
|
||||||
if not context:
|
if not context:
|
||||||
context = {}
|
context = {}
|
||||||
self.check_access_rights(cr, user, 'read')
|
self.check_access_rights(cr, user, 'read')
|
||||||
if not fields:
|
fields = self.check_field_access_rights(cr, user, 'read', fields)
|
||||||
fields = list(set(self._columns.keys() + self._inherit_fields.keys()))
|
|
||||||
if isinstance(ids, (int, long)):
|
if isinstance(ids, (int, long)):
|
||||||
select = [ids]
|
select = [ids]
|
||||||
else:
|
else:
|
||||||
|
@ -4020,6 +4050,7 @@ class BaseModel(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
readonly = None
|
readonly = None
|
||||||
|
self.check_field_access_rights(cr, user, 'write', vals.keys())
|
||||||
for field in vals.copy():
|
for field in vals.copy():
|
||||||
fobj = None
|
fobj = None
|
||||||
if field in self._columns:
|
if field in self._columns:
|
||||||
|
|
|
@ -8,6 +8,7 @@ Tests can be explicitely added to the `fast_suite` or `checks` lists or not.
|
||||||
See the :ref:`test-framework` section in the :ref:`features` list.
|
See the :ref:`test-framework` section in the :ref:`features` list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from . import test_acl
|
||||||
from . import test_expression, test_mail, test_ir_sequence, test_orm, \
|
from . import test_expression, test_mail, test_ir_sequence, test_orm, \
|
||||||
test_fields, test_basecase, \
|
test_fields, test_basecase, \
|
||||||
test_view_validation, test_uninstall, test_misc, test_db_cursor, \
|
test_view_validation, test_uninstall, test_misc, test_db_cursor, \
|
||||||
|
@ -20,6 +21,7 @@ fast_suite = [
|
||||||
]
|
]
|
||||||
|
|
||||||
checks = [
|
checks = [
|
||||||
|
test_acl,
|
||||||
test_expression,
|
test_expression,
|
||||||
test_mail,
|
test_mail,
|
||||||
test_db_cursor,
|
test_db_cursor,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import unittest2
|
import unittest2
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
import openerp
|
||||||
|
from openerp.tools.misc import mute_logger
|
||||||
|
|
||||||
import common
|
import common
|
||||||
|
|
||||||
# test group that demo user should not have
|
# test group that demo user should not have
|
||||||
|
@ -55,6 +58,7 @@ class TestACL(common.TransactionCase):
|
||||||
self.tech_group.write({'users': [(3, self.demo_uid)]})
|
self.tech_group.write({'users': [(3, self.demo_uid)]})
|
||||||
self.res_currency._columns['rate'].groups = False
|
self.res_currency._columns['rate'].groups = False
|
||||||
|
|
||||||
|
@mute_logger('openerp.osv.orm')
|
||||||
def test_field_crud_restriction(self):
|
def test_field_crud_restriction(self):
|
||||||
"Read/Write RPC access to restricted field should be forbidden"
|
"Read/Write RPC access to restricted field should be forbidden"
|
||||||
# Verify the test environment first
|
# Verify the test environment first
|
||||||
|
@ -65,12 +69,10 @@ class TestACL(common.TransactionCase):
|
||||||
|
|
||||||
# Now restrict access to the field and check it's forbidden
|
# Now restrict access to the field and check it's forbidden
|
||||||
self.res_partner._columns['bank_ids'].groups = GROUP_TECHNICAL_FEATURES
|
self.res_partner._columns['bank_ids'].groups = GROUP_TECHNICAL_FEATURES
|
||||||
# FIXME TODO: enable next tests when access rights checks per field are implemented
|
with self.assertRaises(openerp.osv.orm.except_orm):
|
||||||
# from openerp.osv.orm import except_orm
|
self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids'])
|
||||||
# with self.assertRaises(except_orm):
|
with self.assertRaises(openerp.osv.orm.except_orm):
|
||||||
# self.res_partner.read(self.cr, self.demo_uid, [1], ['bank_ids'])
|
self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})
|
||||||
# with self.assertRaises(except_orm):
|
|
||||||
# self.res_partner.write(self.cr, self.demo_uid, [1], {'bank_ids': []})
|
|
||||||
|
|
||||||
# Add the restricted group, and check that it works again
|
# Add the restricted group, and check that it works again
|
||||||
self.tech_group.write({'users': [(4, self.demo_uid)]})
|
self.tech_group.write({'users': [(4, self.demo_uid)]})
|
||||||
|
@ -86,4 +88,4 @@ class TestACL(common.TransactionCase):
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest2.main()
|
unittest2.main()
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -53,31 +53,46 @@ class TestRelatedField(common.TransactionCase):
|
||||||
def test_1_single_related(self):
|
def test_1_single_related(self):
|
||||||
""" test a related field with a single indirection like fields.related('foo') """
|
""" test a related field with a single indirection like fields.related('foo') """
|
||||||
# add a related field test_related_company_id on res.partner
|
# add a related field test_related_company_id on res.partner
|
||||||
|
# and simulate a _inherits_reload() to populate _all_columns.
|
||||||
old_columns = self.partner._columns
|
old_columns = self.partner._columns
|
||||||
|
old_all_columns = self.partner._all_columns
|
||||||
self.partner._columns = dict(old_columns)
|
self.partner._columns = dict(old_columns)
|
||||||
|
self.partner._all_columns = dict(old_all_columns)
|
||||||
self.partner._columns.update({
|
self.partner._columns.update({
|
||||||
'single_related_company_id': fields.related('company_id', type='many2one', obj='res.company'),
|
'single_related_company_id': fields.related('company_id', type='many2one', obj='res.company'),
|
||||||
})
|
})
|
||||||
|
self.partner._all_columns.update({
|
||||||
|
'single_related_company_id': fields.column_info('single_related_company_id', self.partner._columns['single_related_company_id'], None, None, None)
|
||||||
|
})
|
||||||
|
|
||||||
self.do_test_company_field('single_related_company_id')
|
self.do_test_company_field('single_related_company_id')
|
||||||
|
|
||||||
# restore res.partner fields
|
# restore res.partner fields
|
||||||
self.partner._columns = old_columns
|
self.partner._columns = old_columns
|
||||||
|
self.partner._all_columns = old_all_columns
|
||||||
|
|
||||||
def test_2_related_related(self):
|
def test_2_related_related(self):
|
||||||
""" test a related field referring to a related field """
|
""" test a related field referring to a related field """
|
||||||
# add a related field on a related field on res.partner
|
# add a related field on a related field on res.partner
|
||||||
|
# and simulate a _inherits_reload() to populate _all_columns.
|
||||||
old_columns = self.partner._columns
|
old_columns = self.partner._columns
|
||||||
|
old_all_columns = self.partner._all_columns
|
||||||
self.partner._columns = dict(old_columns)
|
self.partner._columns = dict(old_columns)
|
||||||
|
self.partner._all_columns = dict(old_all_columns)
|
||||||
self.partner._columns.update({
|
self.partner._columns.update({
|
||||||
'single_related_company_id': fields.related('company_id', type='many2one', obj='res.company'),
|
'single_related_company_id': fields.related('company_id', type='many2one', obj='res.company'),
|
||||||
'related_related_company_id': fields.related('single_related_company_id', type='many2one', obj='res.company'),
|
'related_related_company_id': fields.related('single_related_company_id', type='many2one', obj='res.company'),
|
||||||
})
|
})
|
||||||
|
self.partner._all_columns.update({
|
||||||
|
'single_related_company_id': fields.column_info('single_related_company_id', self.partner._columns['single_related_company_id'], None, None, None),
|
||||||
|
'related_related_company_id': fields.column_info('related_related_company_id', self.partner._columns['related_related_company_id'], None, None, None)
|
||||||
|
})
|
||||||
|
|
||||||
self.do_test_company_field('related_related_company_id')
|
self.do_test_company_field('related_related_company_id')
|
||||||
|
|
||||||
# restore res.partner fields
|
# restore res.partner fields
|
||||||
self.partner._columns = old_columns
|
self.partner._columns = old_columns
|
||||||
|
self.partner._all_columns = old_all_columns
|
||||||
|
|
||||||
def test_3_read_write(self):
|
def test_3_read_write(self):
|
||||||
""" write on a related field """
|
""" write on a related field """
|
||||||
|
|
Loading…
Reference in New Issue