From 09297baccfb3d1f51db2bc4ddc2c66089558e508 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 24 Jun 2010 17:56:02 +0200 Subject: [PATCH] [IMP] osv_memory: disabled regular ir_model_access access rights and switched to per-user access only. also did some cleanup bzr revid: odo@openerp.com-20100624155602-noiat5i91uyeyy5t --- bin/addons/__init__.py | 20 ++++++--- bin/addons/base/ir/ir.xml | 1 + bin/addons/base/ir/ir_model.py | 26 +++++++++++- bin/osv/expression.py | 3 ++ bin/osv/orm.py | 74 +++++++++++++++++++--------------- 5 files changed, 83 insertions(+), 41 deletions(-) diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py index a538e928cad..6cccbb938f0 100644 --- a/bin/addons/__init__.py +++ b/bin/addons/__init__.py @@ -33,8 +33,6 @@ import pooler import netsvc -from osv import fields -import addons import zipfile import release @@ -303,7 +301,7 @@ def load_information_from_description_file(module): :param module: The name of the module (sale, purchase, ...) """ for filename in ['__openerp__.py', '__terp__.py']: - description_file = addons.get_module_resource(module, filename) + description_file = get_module_resource(module, filename) if os.path.isfile(description_file): return eval(tools.file_open(description_file).read()) @@ -823,7 +821,7 @@ def load_modules(db, force_demo=False, status=None, update_module=False): while True: loop_guardrail += 1 if loop_guardrail > 100: - raise ProgrammingError() + raise ValueError('Possible recursive module tree detected, aborting.') cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),)) module_list = [name for (name,) in cr.fetchall() if name not in graph] @@ -840,9 +838,19 @@ def load_modules(db, force_demo=False, status=None, update_module=False): has_updates = has_updates or r if has_updates: - cr.execute("""select model,name from ir_model where id NOT IN (select model_id from ir_model_access)""") + cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""") for (model, name) in cr.fetchall(): - logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name)) + model_obj = pool.get(model) + if not isinstance(model_obj, osv.osv.osv_memory): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name)) + + # Temporary warning while we remove access rights on osv_memory objects, as they have + # been replaced by owner-only access rights + cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""") + for (model, name) in cr.fetchall(): + model_obj = pool.get(model) + if isinstance(model_obj, osv.osv.osv_memory): + logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name)) cr.execute("SELECT model from ir_model") for (model,) in cr.fetchall(): diff --git a/bin/addons/base/ir/ir.xml b/bin/addons/base/ir/ir.xml index d5be9539a0a..2949107ccff 100644 --- a/bin/addons/base/ir/ir.xml +++ b/bin/addons/base/ir/ir.xml @@ -814,6 +814,7 @@ + diff --git a/bin/addons/base/ir/ir_model.py b/bin/addons/base/ir/ir_model.py index 086a3c3fe81..eb6959a2d79 100644 --- a/bin/addons/base/ir/ir_model.py +++ b/bin/addons/base/ir/ir_model.py @@ -44,6 +44,25 @@ class ir_model(osv.osv): _name = 'ir.model' _description = "Objects" _rec_name = 'name' + + def _is_osv_memory(self, cr, uid, ids, field_name, arg, context=None): + models = self.browse(cr, uid, ids, context=context) + res = dict.fromkeys(ids) + for model in models: + res[model.id] = isinstance(self.pool.get(model.model), osv.osv_memory) + return res + + def _search_osv_memory(self, cr, uid, model, name, domain, context=None): + if not domain: + return [] + field, operator, value = domain[0] + if operator not in ['=', '!=']: + raise osv.except_osv('Invalid search criterions','The osv_memory field can only be compared with = and != operator.') + value = bool(value) if operator == '=' else not bool(value) + all_model_ids = self.search(cr, uid, [], context=context) + is_osv_mem = self._is_osv_memory(cr, uid, all_model_ids, 'osv_memory', arg=None, context=context) + return [('id', 'in', [id for id in is_osv_mem if bool(is_osv_mem[id]) == value])] + _columns = { 'name': fields.char('Object Name', size=64, translate=True, required=True), 'model': fields.char('Object', size=64, required=True, select=1), @@ -51,6 +70,9 @@ class ir_model(osv.osv): 'field_id': fields.one2many('ir.model.fields', 'model_id', 'Fields', required=True), 'state': fields.selection([('manual','Custom Object'),('base','Base Object')],'Manually Created',readonly=True), 'access_ids': fields.one2many('ir.model.access', 'model_id', 'Access'), + 'osv_memory': fields.function(_is_osv_memory, method=True, string='In-memory model', type='boolean', + fnct_search=_search_osv_memory, + help="Indicates whether this object model lives in memory only, i.e. is not persisted (osv.osv_memory)") } _defaults = { 'model': lambda *a: 'x_', @@ -81,7 +103,7 @@ class ir_model(osv.osv): def unlink(self, cr, user, ids, context=None): for model in self.browse(cr, user, ids, context): - if model.state <> 'manual': + if model.state != 'manual': raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,)) res = super(ir_model, self).unlink(cr, user, ids, context) pooler.restart_pool(cr.dbname) @@ -300,7 +322,7 @@ class ir_model_access(osv.osv): _name = 'ir.model.access' _columns = { 'name': fields.char('Name', size=64, required=True), - 'model_id': fields.many2one('ir.model', 'Object', required=True), + 'model_id': fields.many2one('ir.model', 'Object', required=True, domain=[('osv_memory','=', False)]), 'group_id': fields.many2one('res.groups', 'Group', ondelete='cascade'), 'perm_read': fields.boolean('Read Access'), 'perm_write': fields.boolean('Write Access'), diff --git a/bin/osv/expression.py b/bin/osv/expression.py index 5fbdd84abcd..15e8d86ed2a 100644 --- a/bin/osv/expression.py +++ b/bin/osv/expression.py @@ -76,6 +76,9 @@ class expression(object): self.__main_table = None # 'root' table. set by parse() self.__DUMMY_LEAF = (1, '=', 1) # a dummy leaf that must not be parsed or sql generated + @property + def exp(self): + return self.__exp[:] def parse(self, cr, uid, table, context): """ transform the leafs of the expression """ diff --git a/bin/osv/orm.py b/bin/osv/orm.py index e365949b2f9..734bd41a3be 100644 --- a/bin/osv/orm.py +++ b/bin/osv/orm.py @@ -1754,6 +1754,10 @@ class orm_memory(orm_template): self.check_id = 0 cr.execute('delete from wkf_instance where res_type=%s', (self._name,)) + def _check_access(self, uid, object_id, mode): + if uid != 1 and self.datas[object_id]['internal.create_uid'] != uid: + raise except_orm(_('AccessError'), '%s access is only allowed on your own records for osv_memory objects' % mode.capitalize()) + def vaccum(self, cr, uid): self.check_id += 1 if self.check_id % self._check_time: @@ -1774,7 +1778,6 @@ class orm_memory(orm_template): def read(self, cr, user, ids, fields_to_read=None, context=None, load='_classic_read'): if not context: context = {} - self.pool.get('ir.model.access').check(cr, user, self._name, 'read', context=context) if not fields_to_read: fields_to_read = self._columns.keys() result = [] @@ -1785,8 +1788,10 @@ class orm_memory(orm_template): for id in ids: r = {'id': id} for f in fields_to_read: - if id in self.datas: - r[f] = self.datas[id].get(f, False) + record = self.datas.get(id) + if record: + self._check_access(user, id, 'read') + r[f] = record.get(f, False) if r[f] and isinstance(self._columns[f], fields.binary) and context.get('bin_size', False): r[f] = len(r[f]) result.append(r) @@ -1804,7 +1809,6 @@ class orm_memory(orm_template): def write(self, cr, user, ids, vals, context=None): if not ids: return True - self.pool.get('ir.model.access').check(cr, user, self._name, 'write', context=context) vals2 = {} upd_todo = [] for field in vals: @@ -1812,18 +1816,18 @@ class orm_memory(orm_template): vals2[field] = vals[field] else: upd_todo.append(field) - for id_new in ids: - self.datas[id_new].update(vals2) - self.datas[id_new]['internal.date_access'] = time.time() + for object_id in ids: + self._check_access(user, object_id, mode='write') + self.datas[object_id].update(vals2) + self.datas[object_id]['internal.date_access'] = time.time() for field in upd_todo: - self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context) - self._validate(cr, user, [id_new], context) + self._columns[field].set_memory(cr, self, object_id, field, vals[field], user, context) + self._validate(cr, user, [object_id], context) wf_service = netsvc.LocalService("workflow") - wf_service.trg_write(user, self._name, id_new, cr) - return id_new + wf_service.trg_write(user, self._name, object_id, cr) + return object_id def create(self, cr, user, vals, context=None): - self.pool.get('ir.model.access').check(cr, user, self._name, 'create', context=context) self.vaccum(cr, user) self.next_id += 1 id_new = self.next_id @@ -1842,6 +1846,7 @@ class orm_memory(orm_template): upd_todo.append(field) self.datas[id_new] = vals2 self.datas[id_new]['internal.date_access'] = time.time() + self.datas[id_new]['internal.create_uid'] = user for field in upd_todo: self._columns[field].set_memory(cr, self, id_new, field, vals[field], user, context) @@ -1936,13 +1941,19 @@ class orm_memory(orm_template): import expression e = expression.expression(args) e.parse(cr, user, self, context) - res=e.__dict__['_expression__exp'] + res = e.exp return res or [] def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False): if not context: context = {} + + # implicit filter on current user + if not args: + args = [] + args.insert(0, ('internal.create_uid', '=', user)) + result = self._where_calc(cr, user, args, context=context) if result==[]: return self.datas.keys() @@ -1954,25 +1965,20 @@ class orm_memory(orm_template): if result: for id, data in self.datas.items(): counter=counter+1 - data['id'] = id - if limit and (counter >int(limit)): + data['id'] = id + if limit and (counter > int(limit)): break f = True for arg in result: - if arg[1] =='=': - val =eval('data[arg[0]]'+'==' +' arg[2]', locals()) - elif arg[1] in ['<','>','in','not in','<=','>=','<>']: - val =eval('data[arg[0]]'+arg[1] +' arg[2]', locals()) - elif arg[1] in ['ilike']: - if str(data[arg[0]]).find(str(arg[2]))!=-1: - val= True - else: - val=False + if arg[1] == '=': + val = eval('data[arg[0]]'+'==' +' arg[2]', locals()) + elif arg[1] in ['<','>','in','not in','<=','>=','<>']: + val = eval('data[arg[0]]'+arg[1] +' arg[2]', locals()) + elif arg[1] in ['ilike']: + val = (str(data[arg[0]]).find(str(arg[2]))!=-1) + + f = f and val - if f and val: - f = True - else: - f = False if f: res.append(id) if count: @@ -1980,20 +1986,22 @@ class orm_memory(orm_template): return res or [] def unlink(self, cr, uid, ids, context=None): - self.pool.get('ir.model.access').check(cr, uid, self._name, 'unlink', context=context) for id in ids: - if id in self.datas: - del self.datas[id] + self._check_access(uid, id, 'unlink') + self.datas.pop(id, None) if len(ids): cr.execute('delete from wkf_instance where res_type=%s and res_id IN %s', (self._name, tuple(ids))) return True def perm_read(self, cr, user, ids, context=None, details=True): result = [] + credentials = self.pool.get('res.users').name_get(cr, user, [user])[0] + create_date = time.strftime('%Y-%m-%d %H:%M:%S') for id in ids: + self._check_access(user, id, 'read') result.append({ - 'create_uid': (user, 'Root'), - 'create_date': time.strftime('%Y-%m-%d %H:%M:%S'), + 'create_uid': credentials, + 'create_date': create_date, 'write_uid': False, 'write_date': False, 'id': id