From 661a6818d89151b476c93af50be975da49e1459f Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 11 Jul 2010 17:25:27 +0300 Subject: [PATCH 1/4] Document API: framework for permisssion bits. The nodes may have extra permission attributes, rather than the ir.rule system. Store those bits in a 'uidperms' attribute of each node. bzr revid: p_christ@hol.gr-20100711142527-5o4v68yeuhgqvog4 --- addons/document/document_directory.py | 16 ++++++++++ addons/document/nodes.py | 42 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 4d1079d6be9..f340517be60 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -198,6 +198,22 @@ class document_directory(osv.osv): return nodes.get_node_context(cr, uid, context).get_uri(cr, uri) + def get_dir_permissions(self, cr, uid, ids ): + """Check what permission user 'uid' has on directory 'id' + """ + assert len(ids) == 1 + id = ids[0] + + cr.execute( "SELECT count(dg.item_id) AS needs, count(ug.uid) AS has " \ + " FROM document_directory_group_rel dg " \ + " LEFT OUTER JOIN res_groups_users_rel ug " \ + " ON (dg.group_id = ug.gid AND ug.uid = %s) " \ + " WHERE dg.item_id = %s ", (uid, id)) + needs, has = cr.fetchone() + if needs and not has: + return 1 # still allow to descend into. + else: + return 7 def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext): """ try to locate the node in uri, diff --git a/addons/document/nodes.py b/addons/document/nodes.py index 8988437e6aa..c57b3a31259 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -163,6 +163,7 @@ class node_class(object): self.context = context self.type=self.our_type self.parent = parent + self.uidperms = 5 # computed permissions for our uid, in unix bits self.mimetype = 'application/octet-stream' self.create_date = None self.write_date = None @@ -316,6 +317,33 @@ class node_class(object): def get_domain(self, cr, filters): return [] + def check_perms(self, perms): + """ Check the permissions of the current node. + + @param perms either an integers of the bits to check, or + a string with the permission letters + + Permissions of nodes are (in a unix way): + 1, x : allow descend into dir + 2, w : allow write into file, or modification to dir + 4, r : allow read of file, or listing of dir contents + 8, u : allow remove (unlink) + """ + + if isinstance(perms, str): + pe2 = 0 + chars = { 'x': 1, 'w': 2, 'r': 4, 'u': 8 } + for c in perms: + pe2 = pe2 | chars[c] + perms = pe2 + elif isinstance(perms, int): + if perms < 0 or perms > 15: + raise ValueError("Invalid permission bits") + else: + raise ValueError("Invalid permission attribute") + + return ((self.uidperms & perms) == perms) + class node_database(node_class): """ A node representing the database directory @@ -324,6 +352,7 @@ class node_database(node_class): def __init__(self, path=[], parent=False, context=None): super(node_database,self).__init__(path, parent, context) self.unixperms = 040750 + self.uidperms = 5 def children(self, cr, domain=None): res = self._child_get(cr, domain=domain) + self._file_get(cr) @@ -374,6 +403,16 @@ class node_database(node_class): def _get_ttag(self,cr): return 'db-%s' % cr.dbname +def mkdosname(company_name, default='noname'): + """ convert a string to a dos-like name""" + if not company_name: + return default + badchars = ' !@#$%^`~*()+={}[];:\'"/?.<>' + n = '' + for c in company_name[:8]: + n += (c in badchars and '_') or c + return n + class node_dir(node_database): our_type = 'collection' @@ -390,6 +429,9 @@ class node_dir(node_database): self.write_date = dirr and (dirr.write_date or dirr.create_date) or False self.content_length = 0 self.unixperms = 040750 + self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' + self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup') + self.uidperms = dirr.get_dir_permissions() if dctx: self.dctx.update(dctx) dc2 = self.context.context From 0dedf035f9e4b2381e4a9cd5d412ca7a8c930b92 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 11 Jul 2010 17:25:48 +0300 Subject: [PATCH 2/4] Document ftp: allow non-latin user, group. bzr revid: p_christ@hol.gr-20100711142548-6uzoxs4224u8n2cf --- addons/document_ftp/ftpserver/abstracted_fs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 4f0428ecb02..565f003dacd 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -539,8 +539,8 @@ class abstracted_fs(object): perms = filemode(node.unixperms) # permissions nlinks = 1 size = node.content_length or 0L - uname = node.uuser - gname = node.ugroup + uname = _to_decode(node.uuser) + gname = _to_decode(node.ugroup) # stat.st_mtime could fail (-1) if last mtime is too old # in which case we return the local time as last mtime try: @@ -637,9 +637,9 @@ class abstracted_fs(object): if 'unix.mode' in facts: mode = 'unix.mode=%s;' %oct(node.unixperms & 0777) if 'unix.uid' in facts: - uid = 'unix.uid=%s;' % node.uuser + uid = 'unix.uid=%s;' % _to_decode(node.uuser) if 'unix.gid' in facts: - gid = 'unix.gid=%s;' % node.ugroup + gid = 'unix.gid=%s;' % _to_decode(node.ugroup) # We provide unique fact (see RFC-3659, chapter 7.5.2) on # posix platforms only; we get it by mixing st_dev and # st_ino values which should be enough for granting an From a6010e22a2faba53f41d22068776360ac1545c55 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 11 Jul 2010 17:26:12 +0300 Subject: [PATCH 3/4] Document: implement permission checks based on group_ids. Conflicts: document/nodes.py bzr revid: p_christ@hol.gr-20100711142612-2erl7ia2j9fke1e0 --- addons/document/document_directory.py | 2 +- addons/document/nodes.py | 100 +++++++++++++++++++++++--- 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index f340517be60..0685f5c08ff 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -213,7 +213,7 @@ class document_directory(osv.osv): if needs and not has: return 1 # still allow to descend into. else: - return 7 + return 15 def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext): """ try to locate the node in uri, diff --git a/addons/document/nodes.py b/addons/document/nodes.py index c57b3a31259..d710db37c15 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -375,6 +375,13 @@ class node_database(node_class): where = [('parent_id','=',parent_id)] if name: where.append(('name','=',name)) + is_allowed = self.check_perms(1) + else: + is_allowed = self.check_perms(5) + + if not is_allowed: + raise IOError(errno.EPERM, "Permission into directory denied") + if not domain: domain = [] @@ -459,14 +466,18 @@ class node_dir(node_database): return self.dir_id == other.dir_id def get_data(self, cr): - res = '' - for child in self.children(cr): - res += child.get_data(cr) - return res + #res = '' + #for child in self.children(cr): + # res += child.get_data(cr) + return None def _file_get(self, cr, nodename=False): res = super(node_dir,self)._file_get(cr, nodename) + is_allowed = self.check_perms(nodename and 1 or 5) + if not is_allowed: + raise IOError(errno.EPERM, "Permission into directory denied") + cntobj = self.context._dirobj.pool.get('document.directory.content') uid = self.context.uid ctx = self.context.context.copy() @@ -479,7 +490,7 @@ class node_dir(node_database): res.extend(res3) return res - + def _child_get(self, cr, name=None, domain=None): return super(node_dir,self)._child_get(cr, name, self.dir_id, domain=domain) @@ -489,6 +500,9 @@ class node_dir(node_database): res = False if not directory: raise OSError(2, 'Not such file or directory.') + if not self.check_perms('u'): + raise IOError(errno.EPERM,"Permission denied") + if directory._table_name=='document.directory': if self.children(cr): raise OSError(39, 'Directory not empty.') @@ -499,6 +513,9 @@ class node_dir(node_database): def create_child_collection(self, cr, objname): object2 = False + if not self.check_perms(2): + raise IOError(errno.EPERM,"Permission denied") + dirobj = self.context._dirobj uid = self.context.uid ctx = self.context.context.copy() @@ -522,6 +539,9 @@ class node_dir(node_database): """ API function to create a child file object and node Return the node_* created """ + if not self.check_perms(2): + raise IOError(errno.EPERM,"Permission denied") + dirobj = self.context._dirobj uid = self.context.uid ctx = self.context.context.copy() @@ -565,6 +585,9 @@ class node_dir(node_database): if ndir_node and (ndir_node.context != self.context): raise NotImplementedError("Cannot move directories between contexts") + if (not self.check_perms('u')) or (not ndir_node.check_perms('w')): + raise IOError(errno.EPERM,"Permission denied") + dir_obj = self.context._dirobj if not fil_obj: dbro = dir_obj.browse(cr, self.context.uid, self.dir_id, context=self.context.context) @@ -619,6 +642,9 @@ class node_res_dir(node_class): self.write_date = dirr.write_date or dirr.create_date self.content_length = 0 self.unixperms = 040750 + self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' + self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup') + self.uidperms = dirr.get_dir_permissions() self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False self.resm_id = dirr.ressource_id self.namefield = dirr.resource_field.name or 'name' @@ -685,6 +711,12 @@ class node_res_dir(node_class): if name: where.append((self.namefield,'=',name)) + is_allowed = self.check_perms(1) + else: + is_allowed = self.check_perms(5) + + if not is_allowed: + raise IOError(errno.EPERM,"Permission denied") # print "Where clause for %s" % self.res_model, where if self.ressource_tree: @@ -729,6 +761,9 @@ class node_res_obj(node_class): self.write_date = parent.write_date self.content_length = 0 self.unixperms = 040750 + self.uidperms = parent.uidperms & 0x15 + self.uuser = parent.uuser + self.ugroup = parent.ugroup self.res_model = res_model self.domain = parent.domain self.displayname = path @@ -779,6 +814,10 @@ class node_res_obj(node_class): def _file_get(self,cr, nodename=False): res = [] + is_allowed = self.check_perms((nodename and 1) or 5) + if not is_allowed: + raise IOError(errno.EPERM,"Permission denied") + cntobj = self.context._dirobj.pool.get('document.directory.content') uid = self.context.uid ctx = self.context.context.copy() @@ -826,6 +865,11 @@ class node_res_obj(node_class): def _child_get(self, cr, name=None, domain=None): dirobj = self.context._dirobj + + is_allowed = self.check_perms((name and 1) or 5) + if not is_allowed: + raise IOError(errno.EPERM,"Permission denied") + uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) @@ -889,6 +933,10 @@ class node_res_obj(node_class): def create_child_collection(self, cr, objname): dirobj = self.context._dirobj + is_allowed = self.check_perms(2) + if not is_allowed: + raise IOError(errno.EPERM,"Permission denied") + uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) @@ -916,6 +964,10 @@ class node_res_obj(node_class): """ API function to create a child file object and node Return the node_* created """ + is_allowed = self.check_perms(2) + if not is_allowed: + raise IOError(errno.EPERM,"Permission denied") + dirobj = self.context._dirobj uid = self.context.uid ctx = self.context.context.copy() @@ -952,6 +1004,16 @@ class node_file(node_class): self.write_date = fil.write_date or fil.create_date self.content_length = fil.file_size self.displayname = fil.name + + self.uidperms = 14 + if parent: + if not parent.check_perms('x'): + self.uidperms = 0 + elif not parent.check_perms('w'): + self.uidperms = 4 + + self.uuser = (fil.user_id and fil.user_id.login) or 'nobody' + self.ugroup = mkdosname(fil.company_id and fil.company_id.name, default='nogroup') # This only propagates the problem to get_data. Better # fix those files to point to the root dir. @@ -976,6 +1038,9 @@ class node_file(node_class): def open_data(self, cr, mode): stor = self.storage_id assert stor, "No storage for file #%s" % self.file_id + if not self.check_perms(4): + raise IOError(errno.EPERM, "Permission denied") + # If storage is not set properly, we are just screwed here, don't # try to get it from default. stobj = self.context._dirobj.pool.get('document.storage') @@ -983,6 +1048,8 @@ class node_file(node_class): def rm(self, cr): uid = self.context.uid + if not self.check_perms(8): + raise IOError(errno.EPERM, "Permission denied") document_obj = self.context._dirobj.pool.get('ir.attachment') if self.type in ('collection','database'): return False @@ -1027,6 +1094,9 @@ class node_file(node_class): # this is where storage kicks in.. stor = self.storage_id assert stor, "No storage for file #%s" % self.file_id + if not self.check_perms(4): + raise IOError(errno.EPERM, "Permission denied") + # If storage is not set properly, we are just screwed here, don't # try to get it from default. stobj = self.context._dirobj.pool.get('document.storage') @@ -1047,6 +1117,9 @@ class node_file(node_class): # this is where storage kicks in.. stor = self.storage_id assert stor, "No storage for file #%s" % self.file_id + if not self.check_perms(2): + raise IOError(errno.EPERM, "Permission denied") + stobj = self.context._dirobj.pool.get('document.storage') return stobj.set_data(cr, self.context.uid,stor, self, data, self.context.context, fil_obj) @@ -1057,6 +1130,9 @@ class node_file(node_class): if ndir_node.context != self.context: raise NotImplementedError("Cannot move files between contexts") + if (not self.check_perms(8)) and ndir_node.check_perms(2): + raise IOError(errno.EPERM, "Permission denied") + doc_obj = self.context._dirobj.pool.get('ir.attachment') if not fil_obj: dbro = doc_obj.browse(cr, self.context.uid, self.file_id, context=self.context.context) @@ -1072,9 +1148,6 @@ class node_file(node_class): self.parent = self.context.get_dir_node(cr, dbro.parent_id.id) assert self.parent - # TODO: test if parent is writable. - - ret = {} if self.parent != ndir_node: if not (isinstance(self.parent, node_dir) and isinstance(ndir_node, node_dir)): @@ -1118,6 +1191,11 @@ class node_content(node_class): self.write_date = False self.content_length = False self.unixperms = 0640 + if parent: + self.uidperms = parent.uidperms & 14 + self.uuser = parent.uuser + self.ugroup = parent.ugroup + self.extension = cnt.extension self.report_id = cnt.report_id and cnt.report_id.id #self.mimetype = cnt.extension. @@ -1145,6 +1223,9 @@ class node_content(node_class): def get_data(self, cr, fil_obj = None): cntobj = self.context._dirobj.pool.get('document.directory.content') + if not self.check_perms(4): + raise IOError(errno.EPERM, "Permission denied") + ctx = self.context.context.copy() ctx.update(self.dctx) data = cntobj.process_read(cr, self.context.uid, self, ctx) @@ -1159,6 +1240,9 @@ class node_content(node_class): def set_data(self, cr, data, fil_obj = None): cntobj = self.context._dirobj.pool.get('document.directory.content') + if not self.check_perms(2): + raise IOError(errno.EPERM, "Permission denied") + ctx = self.context.context.copy() ctx.update(self.dctx) return cntobj.process_write(cr, self.context.uid, self, data, ctx) From 19e5b1e3d0c5e5aab3d4994b490fa5e98823fb93 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 11 Jul 2010 17:26:17 +0300 Subject: [PATCH 4/4] Document: index the content, when a descriptor closes (eg. FTP) bzr revid: p_christ@hol.gr-20100711142617-ib7nj3sg7nr5jp4a --- addons/document/document_storage.py | 89 +++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py index 48f7ccf2f87..4d7c7d10eda 100644 --- a/addons/document/document_storage.py +++ b/addons/document/document_storage.py @@ -98,15 +98,51 @@ class nodefd_file(nodes.node_descriptor): def __init__(self, parent, path, mode): nodes.node_descriptor.__init__(self, parent) self.__file = open(path, mode) + if mode in ('w', 'w+', 'r+'): + self._need_index = True + else: + self._need_index = False for attr in ('closed', 'read', 'write', 'seek', 'tell'): setattr(self,attr, getattr(self.__file, attr)) def close(self): # TODO: locking in init, close() + fname = self.__file.name self.__file.close() + + if self._need_index: + par = self._get_parent() + cr = pooler.get_db(par.context.dbname).cursor() + icont = '' + mime = '' + filename = par.path + if isinstance(filename, (tuple, list)): + filename = '/'.join(filename) + + try: + mime, icont = cntIndex.doIndex(None, filename=filename, + content_type=None, realfname=fname) + except Exception: + logging.getLogger('document.storage').debug('Cannot index file:', exc_info=True) + pass + + try: + icont_u = ustr(icont) + except UnicodeError: + icont_u = '' + + try: + cr.execute('UPDATE ir_attachment SET index_content = %s, file_type = %s WHERE id = %s', + (icont_u, mime, par.file_id)) + par.content_length = filesize + par.content_type = mime + cr.commit() + cr.close() + except Exception: + logging.getLogger('document.storage').debug('Cannot save file indexed content:', exc_info=True) + - class nodefd_db(StringIO, nodes.node_descriptor): """ A descriptor to db data """ @@ -140,9 +176,30 @@ class nodefd_db(StringIO, nodes.node_descriptor): try: if self.mode in ('w', 'w+', 'r+'): data = self.getvalue() + icont = '' + mime = '' + filename = par.path + if isinstance(filename, (tuple, list)): + filename = '/'.join(filename) + + try: + mime, icont = cntIndex.doIndex(data, filename=filename, + content_type=None, realfname=None) + except Exception: + logging.getLogger('document.storage').debug('Cannot index file:', exc_info=True) + pass + + try: + icont_u = ustr(icont) + except UnicodeError: + icont_u = '' + out = psycopg2.Binary(data) - cr.execute("UPDATE ir_attachment SET db_datas = %s, file_size=%s WHERE id = %s", - (out, len(data), par.file_id)) + cr.execute("UPDATE ir_attachment " \ + "SET db_datas = %s, file_size=%s, " \ + " index_content= %s, file_type=%s " \ + " WHERE id = %s", + (out, len(data), icont_u, mime, par.file_id)) elif self.mode == 'a': data = self.getvalue() out = psycopg2.Binary(data) @@ -191,9 +248,29 @@ class nodefd_db64(StringIO, nodes.node_descriptor): cr = pooler.get_db(par.context.dbname).cursor() try: if self.mode in ('w', 'w+', 'r+'): - out = self.getvalue() - cr.execute('UPDATE ir_attachment SET db_datas = %s::bytea, file_size=%s WHERE id = %s', - (base64.encodestring(out), len(out), par.file_id)) + data = self.getvalue() + icont = '' + mime = '' + filename = par.path + if isinstance(filename, (tuple, list)): + filename = '/'.join(filename) + + try: + mime, icont = cntIndex.doIndex(data, filename=filename, + content_type=None, realfname=None) + except Exception: + logging.getLogger('document.storage').debug('Cannot index file:', exc_info=True) + pass + + try: + icont_u = ustr(icont) + except UnicodeError: + icont_u = '' + + cr.execute('UPDATE ir_attachment SET db_datas = %s::bytea, file_size=%s, ' \ + 'index_content = %s, file_type = %s ' \ + 'WHERE id = %s', + (base64.encodestring(out), len(out), icont_u, mime, par.file_id)) elif self.mode == 'a': out = self.getvalue() # Yes, we're obviously using the wrong representation for storing our