diff options
| author | Harry (OpenERP) <hmo@tinyerp.com> | 2010-11-12 15:18:36 +0530 |
|---|---|---|
| committer | Harry (OpenERP) <hmo@tinyerp.com> | 2010-11-12 15:18:36 +0530 |
| commit | ca33cf5313aa527d7e619b8c2fb9159ce20af108 (patch) | |
| tree | c2247df51f09f05c340d35f3ccf392016668297d /addons/document | |
| parent | 515b856d55203f813a324bb7a6cfa2ba11695d39 (diff) | |
| parent | d3b79b05fcbcbd0da299c09f96edc4d1ac698cc0 (diff) | |
[MERGE]
bzr revid: hmo@tinyerp.com-20101112094836-qy4536d3tjvw04xc
Diffstat (limited to 'addons/document')
| -rw-r--r-- | addons/document/__openerp__.py | 2 | ||||
| -rw-r--r-- | addons/document/doc/access_permissions.rst | 65 | ||||
| -rw-r--r-- | addons/document/document.py | 4 | ||||
| -rw-r--r-- | addons/document/document_data.xml | 20 | ||||
| -rw-r--r-- | addons/document/document_directory.py | 46 | ||||
| -rw-r--r-- | addons/document/document_storage.py | 79 | ||||
| -rw-r--r-- | addons/document/nodes.py | 168 | ||||
| -rw-r--r-- | addons/document/security/document_security.xml | 24 | ||||
| -rw-r--r-- | addons/document/security/ir.model.access.csv | 1 |
9 files changed, 324 insertions, 85 deletions
diff --git a/addons/document/__openerp__.py b/addons/document/__openerp__.py index 5741a7223e0..5d0088579a1 100644 --- a/addons/document/__openerp__.py +++ b/addons/document/__openerp__.py @@ -22,7 +22,7 @@ { 'name': 'Integrated Document Management System', - 'version': '2.0', + 'version': '2.1', 'category': 'Generic Modules/Others', 'description': """This is a complete document management system: * User Authentication diff --git a/addons/document/doc/access_permissions.rst b/addons/document/doc/access_permissions.rst new file mode 100644 index 00000000000..2cdc45dadab --- /dev/null +++ b/addons/document/doc/access_permissions.rst @@ -0,0 +1,65 @@ +Access control in the Document Management system +================================================ + +The purpose is to let the DMS act as a real-life management system for +the file handling of some small business. +The key concept, there, is the separation of access according to users +and groups. + +Fact 1: Users, in general, must NOT see each other's documents, not even +their names (because they usually imply sensitive data, like eg. a doc: +"Acme Company's Patent 012356 about fooobars.odf" ) +Fact 2: Users, sometimes, fail to comprehend complex ACL schemes, so we +owe to keep things simple, a main principle applied all over the place. +Fact 3: our system has both statically placed files and directories, as +well as dynamic (aka "resources" in our terminology) nodes. + +We allow/limit the access based on 3 factors (fields): + - The "owner" field, which holds the user that created or "owns" the + file or directory. + - The "group_ids" field, on directories, which specifieds group-wise + access + - The "company_id" field, for multi-company access rules [1] + +[1] at multi-company setups, we may want the same file hierarchy to apply +to different companies, and also nodes to be company-specific in it. + +Principle of "owner" +---------------------- +Files or directories that have an empty "owner" field are public. All users +will be able to _read_ them. Only the OpenERP Administrator or specified +groups, however, will be able to modify them! +Files or directories that have an "owner" are private. Only their users will +be able to read or modify (including delete) them. +By default, all user's files are created with "owner" field set, thus private. + +Principle of "group_ids" +------------------------- +Directories that have any group ids set will only (apart from their owner) +allow members of these groups to read them. +Directories that are created into the above directories will initially inherit +(that is, copy) the group_ids of their parents, so that they also allow +access to the same users. + +Implementation note +--------------------- +Most of the principles are applied through record rules (see ir.rule object), +so an administrator can actually readjust them. +In order to have logical "areas" of folders, where different policies apply +(like group folders, personal areas), default values for directories' owners +and group_ids can be tuned (through the 'set default' functionality of +fields). + +Summary +-------- + +Table of permissions and behavior + +|| Type | Owner set | Groups set | Description || +|| Public | - | - | Public-readable folders, admin can write || +|| Group | - | X | Group can read, write, delete in them || +|| Group-read | X | X | Group can read[2], owner can write/delete || +|| Private | X | - | Only owner can read, write, delete in. || + +[2] hint: using a wide group, like "Internal users" at this setup creates the + effect of public-readable folders, with write permission to a non-admin user. diff --git a/addons/document/document.py b/addons/document/document.py index 6666af2bc68..e3d810668b4 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -74,7 +74,7 @@ class document_file(osv.osv): 'create_uid': fields.many2one('res.users', 'Creator', readonly=True), 'write_date': fields.datetime('Date Modified', readonly=True), 'write_uid': fields.many2one('res.users', 'Last Modification User', readonly=True), - 'res_model': fields.char('Attached Model', size=64, readonly=True), + 'res_model': fields.char('Attached Model', size=64, readonly=True, change_default=True), 'res_id': fields.integer('Attached ID', readonly=True), # If ir.attachment contained any data before document is installed, preserve @@ -86,7 +86,7 @@ class document_file(osv.osv): 'user_id': fields.many2one('res.users', 'Owner', select=1), # 'group_ids': fields.many2many('res.groups', 'document_group_rel', 'item_id', 'group_id', 'Groups'), # the directory id now is mandatory. It can still be computed automatically. - 'parent_id': fields.many2one('document.directory', 'Directory', select=1, required=True), + 'parent_id': fields.many2one('document.directory', 'Directory', select=1, required=True, change_default=True), 'index_content': fields.text('Indexed Content'), 'partner_id':fields.many2one('res.partner', 'Partner', select=1), 'file_size': fields.integer('File Size', required=True), diff --git a/addons/document/document_data.xml b/addons/document/document_data.xml index ef829b6e258..a4f546cbf83 100644 --- a/addons/document/document_data.xml +++ b/addons/document/document_data.xml @@ -16,13 +16,13 @@ <record model="document.directory" id="dir_root"> <field name="name">Documents</field> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="storage_id" ref="storage_default"/> <field name="ressource_id">0</field> </record> <record model="document.directory" id="dir_my_folder"> - <field name="name">My Folder</field> + <field name="name">Admin Folder</field> <field name="parent_id" ref="dir_root"/> <field name="user_id" ref="base.user_root"/> <field name="ressource_id">0</field> @@ -37,7 +37,7 @@ <field name="ressource_id">0</field> <field name="ressource_type_id" search="[('model','=','res.partner.category')]" /> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> </record> <record model="document.directory" id="dir_partner"> @@ -46,7 +46,7 @@ <field name="domain">[('category_id','in',[active_id])]</field> <field name="ressource_type_id" search="[('model','=','res.partner')]" /> <field name="ressource_parent_type_id" search="[('model','=','res.partner.category')]" /> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="ressource_id">0</field> </record> @@ -55,14 +55,14 @@ <field name="name">Personal Folders</field> <field name="parent_id" ref="dir_root"/> <field name="type">ressource</field> + <field name="user_id" eval="False"/> <field name="ressource_type_id" ref="base.model_res_users" /> <field name="ressource_id">0</field> - </record> <record model="document.directory" id="dir_product"> <field name="name">Products</field> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="parent_id" ref="dir_root"/> <field name="ressource_id">0</field> @@ -70,7 +70,7 @@ <record model="document.directory" id="dir_sale_order"> <field name="name">Sales Order</field> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="parent_id" ref="dir_root"/> <field name="ressource_id">0</field> @@ -78,7 +78,7 @@ <record model="document.directory" id="dir_sale_order_all"> <field name="name">All Sales Order</field> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="parent_id" ref="dir_sale_order"/> <field name="ressource_id">0</field> @@ -86,7 +86,7 @@ <record model="document.directory" id="dir_sale_order_quote"> <field name="name">Quotations</field> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="parent_id" ref="dir_sale_order"/> <field name="ressource_id">0</field> @@ -94,7 +94,7 @@ <record model="document.directory" id="dir_project"> <field name="name">Projects</field> - <field name="user_id" ref="base.user_root"/> + <field name="user_id" eval="False"/> <field name="parent_id" ref="dir_root"/> <field name="ressource_id">0</field> diff --git a/addons/document/document_directory.py b/addons/document/document_directory.py index 9b7ba18b16f..82f53b6120b 100644 --- a/addons/document/document_directory.py +++ b/addons/document/document_directory.py @@ -21,6 +21,7 @@ from osv import osv, fields +from osv.orm import except_orm import os import nodes @@ -38,9 +39,9 @@ class document_directory(osv.osv): 'create_uid': fields.many2one('res.users', 'Creator', readonly=True), 'domain': fields.char('Domain', size=128, help="Use a domain if you want to apply an automatic filter on visible resources."), 'user_id': fields.many2one('res.users', 'Owner'), - 'storage_id': fields.many2one('document.storage', 'Storage'), + 'storage_id': fields.many2one('document.storage', 'Storage', change_default=True), 'group_ids': fields.many2many('res.groups', 'document_directory_group_rel', 'item_id', 'group_id', 'Groups'), - 'parent_id': fields.many2one('document.directory', 'Parent Directory', select=1), + 'parent_id': fields.many2one('document.directory', 'Parent Directory', select=1, change_default=True), 'child_ids': fields.one2many('document.directory', 'parent_id', 'Children'), 'file_ids': fields.one2many('ir.attachment', 'parent_id', 'Files'), 'content_ids': fields.one2many('document.directory.content', 'directory_id', 'Virtual Files'), @@ -48,22 +49,24 @@ class document_directory(osv.osv): ('directory','Static Directory'), ('ressource','Folders per resource'), ], - 'Type', required=True, select=1, + 'Type', required=True, select=1, change_default=True, help="Each directory can either have the type Static or be linked to another resource. A static directory, as with Operating Systems, is the classic directory that can contain a set of files. The directories linked to systems resources automatically possess sub-directories for each of resource types defined in the parent directory."), - 'ressource_type_id': fields.many2one('ir.model', 'Resource model', + 'ressource_type_id': fields.many2one('ir.model', 'Resource model', change_default=True, help="Select an object here and there will be one folder per record of that resource."), 'resource_field': fields.many2one('ir.model.fields', 'Name field', help='Field to be used as name on resource directories. If empty, the "name" will be used.'), 'resource_find_all': fields.boolean('Find all resources', required=True, help="If true, all attachments that match this resource will " \ " be located. If false, only ones that have this as parent." ), - 'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model', + 'ressource_parent_type_id': fields.many2one('ir.model', 'Parent Model', change_default=True, help="If you put an object here, this directory template will appear bellow all of these objects. " \ + "Such directories are \"attached\" to the specific model or record, just like attachments. " \ "Don't put a parent directory if you select a parent model."), - 'ressource_id': fields.integer('Resource ID'), + 'ressource_id': fields.integer('Resource ID', + help="Along with Parent Model, this ID attaches this folder to a specific record of Parent Model."), 'ressource_tree': fields.boolean('Tree Structure', help="Check this if you want to use the same tree structure as the object selected in the system."), 'dctx_ids': fields.one2many('document.directory.dctx', 'dir_id', 'Context fields'), - 'company_id': fields.many2one('res.company', 'Company'), + 'company_id': fields.many2one('res.company', 'Company', change_default=True), } @@ -98,10 +101,10 @@ class document_directory(osv.osv): _defaults = { 'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'document.directory', context=c), 'user_id': lambda self,cr,uid,ctx: uid, - 'domain': lambda self,cr,uid,ctx: '[]', - 'type': lambda *args: 'directory', - 'ressource_id': lambda *a: 0, - 'storage_id': _get_def_storage, + 'domain': '[]', + 'type': 'directory', + 'ressource_id': 0, + 'storage_id': _get_def_storage, # Still, it is bad practice to set it everywhere. 'resource_find_all': True, } _sql_constraints = [ @@ -188,7 +191,6 @@ class document_directory(osv.osv): dbro = self.browse(cr, uid, ids, context=context) if dynamic: - assert dbro.type == 'directory' return nodes.node_res_obj elif dbro.type == 'directory': return nodes.node_dir @@ -211,22 +213,20 @@ class document_directory(osv.osv): """ return - def get_dir_permissions(self, cr, uid, ids ): + def get_dir_permissions(self, cr, uid, ids, context=None ): """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 15 + res = 0 + for pperms in [('read', 5), ('write', 2), ('unlink', 8)]: + try: + self.check_access_rule(cr, uid, ids, pperms[0], context=context) + res |= pperms[1] + except except_orm: + pass + return res def _locate_child(self, cr, uid, root_id, uri,nparent, ncontext): """ try to locate the node in uri, diff --git a/addons/document/document_storage.py b/addons/document/document_storage.py index 467a1d7ff31..aa2edb6b411 100644 --- a/addons/document/document_storage.py +++ b/addons/document/document_storage.py @@ -26,6 +26,7 @@ import tools import base64 import errno import logging +import shutil from StringIO import StringIO import psycopg2 @@ -48,7 +49,7 @@ DMS_ROOT_PATH = tools.config.get('document_path', os.path.join(tools.config.get( We have to consider 3 cases of data /retrieval/: Given (context,path) we need to access the file (aka. node). given (directory, context), we need one of its children (for listings, views) - given (ir.attachment, context), we needs its data and metadata (node). + given (ir.attachment, context), we need its data and metadata (node). For data /storage/ we have the cases: Have (ir.attachment, context), we modify the file (save, update, rename etc). @@ -100,10 +101,17 @@ class nodefd_file(nodes.node_descriptor): if mode.endswith('b'): mode = mode[:-1] self.mode = mode + self._size = os.stat(path).st_size - for attr in ('closed', 'read', 'write', 'seek', 'tell'): + for attr in ('closed', 'read', 'write', 'seek', 'tell', 'next'): setattr(self,attr, getattr(self.__file, attr)) + def size(self): + return self._size + + def __iter__(self): + return self + def close(self): # TODO: locking in init, close() fname = self.__file.name @@ -165,6 +173,7 @@ class nodefd_db(StringIO, nodes.node_descriptor): """ def __init__(self, parent, ira_browse, mode): nodes.node_descriptor.__init__(self, parent) + self._size = 0L if mode.endswith('b'): mode = mode[:-1] @@ -172,6 +181,8 @@ class nodefd_db(StringIO, nodes.node_descriptor): cr = ira_browse._cr # reuse the cursor of the browse object, just now cr.execute('SELECT db_datas FROM ir_attachment WHERE id = %s',(ira_browse.id,)) data = cr.fetchone()[0] + if data: + self._size = len(data) StringIO.__init__(self, data) elif mode in ('w', 'w+'): StringIO.__init__(self, None) @@ -184,6 +195,9 @@ class nodefd_db(StringIO, nodes.node_descriptor): raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode + def size(self): + return self._size + def close(self): # we now open a *separate* cursor, to update the data. # FIXME: this may be improved, for concurrency handling @@ -241,11 +255,15 @@ class nodefd_db64(StringIO, nodes.node_descriptor): """ def __init__(self, parent, ira_browse, mode): nodes.node_descriptor.__init__(self, parent) + self._size = 0L if mode.endswith('b'): mode = mode[:-1] if mode in ('r', 'r+'): - StringIO.__init__(self, base64.decodestring(ira_browse.db_datas)) + data = base64.decodestring(ira_browse.db_datas) + if data: + self._size = len(data) + StringIO.__init__(self, data) elif mode in ('w', 'w+'): StringIO.__init__(self, None) # at write, we start at 0 (= overwrite), but have the original @@ -257,6 +275,9 @@ class nodefd_db64(StringIO, nodes.node_descriptor): raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode + def size(self): + return self._size + def close(self): # we now open a *separate* cursor, to update the data. # FIXME: this may be improved, for concurrency handling @@ -452,11 +473,11 @@ class document_storage(osv.osv): elif boo.type == 'realstore': path, npath = self.__prepare_realpath(cr, file_node, ira, boo.path, - do_create = (mode[1] in ('w','a')) ) + do_create = (mode[0] in ('w','a')) ) fpath = os.path.join(path, npath[-1]) - if (not os.path.exists(fpath)) and mode[1] == 'r': + if (not os.path.exists(fpath)) and mode[0] == 'r': raise IOError("File not found: %s" % fpath) - elif mode[1] in ('w', 'a') and not ira.store_fname: + elif mode[0] in ('w', 'a') and not ira.store_fname: store_fname = os.path.join(*npath) cr.execute('UPDATE ir_attachment SET store_fname = %s WHERE id = %s', (store_fname, ira.id)) @@ -653,7 +674,7 @@ class document_storage(osv.osv): if ktype == 'file': try: os.unlink(fname) - except Exception, e: + except Exception: self._doclog.warning("Could not remove file %s, please remove manually.", fname, exc_info=True) else: self._doclog.warning("Unknown unlink key %s" % ktype) @@ -733,26 +754,34 @@ class document_storage(osv.osv): # nothing to do for a rename, allow to change the db field return { 'parent_id': ndir_bro.id } elif sbro.type == 'realstore': - raise NotImplementedError("Cannot move in realstore, yet") # TODO - fname = fil_bo.store_fname + ira = self.pool.get('ir.attachment').browse(cr, uid, file_node.file_id, context=context) + + path, opath = self.__prepare_realpath(cr, file_node, ira, sbro.path, do_create=False) + fname = ira.store_fname + if not fname: - return ValueError("Tried to rename a non-stored file") - path = sbro.path - oldpath = os.path.join(path, fname) + self._doclog.warning("Trying to rename a non-stored file") + if fname != os.path.join(*opath): + self._doclog.warning("inconsistency in realstore: %s != %s" , fname, repr(opath)) + + oldpath = os.path.join(path, opath[-1]) - for ch in ('*', '|', "\\", '/', ':', '"', '<', '>', '?', '..'): - if ch in new_name: - raise ValueError("Invalid char %s in name %s" %(ch, new_name)) - - file_node.fix_ppath(cr, ira) - npath = file_node.full_path() or [] - dpath = [path,] - dpath.extend(npath[:-1]) - dpath.append(new_name) - newpath = os.path.join(*dpath) - # print "old, new paths:", oldpath, newpath - os.rename(oldpath, newpath) - return { 'name': new_name, 'datas_fname': new_name, 'store_fname': new_name } + npath = [sbro.path,] + (ndir_bro.get_full_path() or []) + npath = filter(lambda x: x is not None, npath) + newdir = os.path.join(*npath) + if not os.path.isdir(newdir): + self._doclog.debug("Must create dir %s", newdir) + os.makedirs(newdir) + npath.append(opath[-1]) + newpath = os.path.join(*npath) + + self._doclog.debug("Going to move %s from %s to %s", opath[-1], oldpath, newpath) + shutil.move(oldpath, newpath) + + store_path = npath[1:] + [opath[-1],] + store_fname = os.path.join(*store_path) + + return { 'store_fname': store_fname } else: raise TypeError("No %s storage" % sbro.type) diff --git a/addons/document/nodes.py b/addons/document/nodes.py index 68220885dff..c0eef134f25 100644 --- a/addons/document/nodes.py +++ b/addons/document/nodes.py @@ -175,6 +175,23 @@ class node_descriptor(object): def write(self, str): raise NotImplementedError + def size(self): + raise NotImplementedError + + def __len__(self): + return self.size() + + def __nonzero__(self): + """ Ensure that a node_descriptor will never equal False + + Since we do define __len__ and __iter__ for us, we must avoid + being regarded as non-true objects. + """ + return True + + def next(self, str): + raise NotImplementedError + class node_class(object): """ this is a superclass for our inodes It is an API for all code that wants to access the document files. @@ -366,12 +383,14 @@ class node_class(object): def create_child(self, cr, path, data=None): """ Create a regular file under this node """ - raise NotImplementedError(repr(self)) + logger.warning("Attempted to create a file under %r, not possible.", self) + raise IOError(errno.EPERM, "Not allowed to create files here") def create_child_collection(self, cr, objname): """ Create a child collection (directory) under self """ - raise NotImplementedError(repr(self)) + logger.warning("Attempted to create a collection under %r, not possible.", self) + raise IOError(errno.EPERM, "Not allowed to create folders here") def rm(self, cr): raise NotImplementedError(repr(self)) @@ -433,12 +452,12 @@ class node_database(node_class): return res[0] return None - def _child_get(self, cr, name=False, parent_id=False, domain=None): + def _child_get(self, cr, name=False, domain=None): dirobj = self.context._dirobj uid = self.context.uid ctx = self.context.context.copy() ctx.update(self.dctx) - where = [('parent_id','=',parent_id)] + where = [('parent_id','=', False), ('ressource_parent_type_id','=',False)] if name: where.append(('name','=',name)) is_allowed = self.check_perms(1) @@ -448,23 +467,14 @@ class node_database(node_class): if not is_allowed: raise IOError(errno.EPERM, "Permission into directory denied") - if not domain: - domain = [] - - where2 = where + domain + ['|', ('type', '=', 'directory'), \ - '&', ('type', '=', 'ressource'), ('ressource_parent_type_id','=',False)] - ids = dirobj.search(cr, uid, where2, context=ctx) + if domain: + where = where + domain + ids = dirobj.search(cr, uid, where, context=ctx) res = [] for dirr in dirobj.browse(cr, uid, ids, context=ctx): klass = dirr.get_node_class(dirr, context=ctx) res.append(klass(dirr.name, self, self.context,dirr)) - fil_obj = dirobj.pool.get('ir.attachment') - ids = fil_obj.search(cr, uid, where, context=ctx) - if ids: - for fil in fil_obj.browse(cr, uid, ids, context=ctx): - klass = self.context.node_file_class - res.append(klass(fil.name, self, self.context, fil)) return res def _file_get(self,cr, nodename=False): @@ -485,6 +495,22 @@ def mkdosname(company_name, default='noname'): return n +def _uid2unixperms(perms, has_owner): + """ Convert the uidperms and the owner flag to full unix bits + """ + res = 0 + if has_owner: + res |= (perms & 0x07) << 6 + res |= (perms & 0x05) << 3 + elif perms & 0x02: + res |= (perms & 0x07) << 6 + res |= (perms & 0x07) << 3 + else: + res |= (perms & 0x07) << 6 + res |= (perms & 0x05) << 3 + res |= 0x05 + return res + class node_dir(node_database): our_type = 'collection' def __init__(self, path, parent, context, dirr, dctx=None): @@ -499,13 +525,13 @@ class node_dir(node_database): # TODO: the write date should be MAX(file.write).. self.write_date = dirr and (dirr.write_date or dirr.create_date) or False self.content_length = 0 - self.unixperms = 040750 try: self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' except Exception: self.uuser = 'nobody' self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup') self.uidperms = dirr.get_dir_permissions() + self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id) if dctx: self.dctx.update(dctx) dc2 = self.context.context @@ -559,7 +585,46 @@ class node_dir(node_database): 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) + dirobj = self.context._dirobj + uid = self.context.uid + ctx = self.context.context.copy() + ctx.update(self.dctx) + where = [('parent_id','=',self.dir_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 = [] + + where2 = where + domain + [('ressource_parent_type_id','=',False)] + ids = dirobj.search(cr, uid, where2, context=ctx) + res = [] + for dirr in dirobj.browse(cr, uid, ids, context=ctx): + klass = dirr.get_node_class(dirr, context=ctx) + res.append(klass(dirr.name, self, self.context,dirr)) + + # Static directories should never return files with res_model/res_id + # because static dirs are /never/ related to a record. + # In fact, files related to some model and parented by the root dir + # (the default), will NOT be accessible in the node system unless + # a resource folder for that model exists (with resource_find_all=True). + # Having resource attachments in a common folder is bad practice, + # because they would be visible to all users, and their names may be + # the same, conflicting. + where += [('res_model', '=', False)] + fil_obj = dirobj.pool.get('ir.attachment') + ids = fil_obj.search(cr, uid, where, context=ctx) + if ids: + for fil in fil_obj.browse(cr, uid, ids, context=ctx): + klass = self.context.node_file_class + res.append(klass(fil.name, self, self.context, fil)) + return res def rmcol(self, cr): uid = self.context.uid @@ -699,13 +764,13 @@ class node_res_dir(node_class): # TODO: the write date should be MAX(file.write).. self.write_date = dirr.write_date or dirr.create_date self.content_length = 0 - self.unixperms = 040750 try: self.uuser = (dirr.user_id and dirr.user_id.login) or 'nobody' except Exception: self.uuser = 'nobody' self.ugroup = mkdosname(dirr.company_id and dirr.company_id.name, default='nogroup') self.uidperms = dirr.get_dir_permissions() + self.unixperms = 040000 | _uid2unixperms(self.uidperms, dirr and dirr.user_id) self.res_model = dirr.ressource_type_id and dirr.ressource_type_id.model or False self.resm_id = dirr.ressource_id self.res_find_all = dirr.resource_find_all @@ -759,7 +824,7 @@ class node_res_dir(node_class): ctx.update(self.dctx) where = [] if self.domain: - app = safe_eval(self.domain, self.dctx) + app = safe_eval(self.domain, ctx) if not app: pass elif isinstance(app, list): @@ -823,8 +888,8 @@ class node_res_obj(node_class): # TODO: the write date should be MAX(file.write).. self.write_date = parent.write_date self.content_length = 0 - self.unixperms = 040750 self.uidperms = parent.uidperms & 15 + self.unixperms = 040000 | _uid2unixperms(self.uidperms, True) self.uuser = parent.uuser self.ugroup = parent.ugroup self.res_model = res_model @@ -953,9 +1018,9 @@ class node_res_obj(node_class): where1 = [] if obj._parent_name in obj.fields_get(cr, uid): where1 = where + [(obj._parent_name, '=', self.res_id)] + namefield = directory.resource_field.name or 'name' resids = obj.search(cr, uid, where1, context=ctx) for bo in obj.browse(cr, uid, resids, context=ctx): - namefield = directory.resource_field.name or 'name' if not bo: continue res_name = getattr(bo, namefield) @@ -963,7 +1028,7 @@ class node_res_obj(node_class): continue # TODO Revise klass = directory.get_node_class(directory, dynamic=True, context=ctx) - res.append(klass(res_name, self.dir_id, self, self.context, self.res_model, res_bo = bo)) + res.append(klass(res_name, dir_id=self.dir_id, parent=self, context=self.context, res_model=self.res_model, res_bo=bo)) where2 = where + [('parent_id','=',self.dir_id) ] @@ -1361,11 +1426,13 @@ class nodefd_content(StringIO, node_descriptor): def __init__(self, parent, cr, mode, ctx): node_descriptor.__init__(self, parent) self._context=ctx + self._size = 0L if mode in ('r', 'r+'): cntobj = parent.context._dirobj.pool.get('document.directory.content') data = cntobj.process_read(cr, parent.context.uid, parent, ctx) if data: + self._size = len(data) parent.content_length = len(data) StringIO.__init__(self, data) elif mode in ('w', 'w+'): @@ -1379,6 +1446,9 @@ class nodefd_content(StringIO, node_descriptor): raise IOError(errno.EINVAL, "Invalid file mode") self.mode = mode + def size(self): + return self._size + def close(self): # we now open a *separate* cursor, to update the data. # FIXME: this may be improved, for concurrency handling @@ -1404,4 +1474,56 @@ class nodefd_content(StringIO, node_descriptor): cr.close() StringIO.close(self) +class nodefd_static(StringIO, node_descriptor): + """ A descriptor to nodes with static data. + """ + def __init__(self, parent, cr, mode, ctx=None): + node_descriptor.__init__(self, parent) + self._context=ctx + self._size = 0L + + if mode in ('r', 'r+'): + data = parent.get_data(cr) + if data: + self._size = len(data) + parent.content_length = len(data) + StringIO.__init__(self, data) + elif mode in ('w', 'w+'): + StringIO.__init__(self, None) + # at write, we start at 0 (= overwrite), but have the original + # data available, in case of a seek() + elif mode == 'a': + StringIO.__init__(self, None) + else: + logging.getLogger('document.nodes').error("Incorrect mode %s specified", mode) + raise IOError(errno.EINVAL, "Invalid file mode") + self.mode = mode + + def size(self): + return self._size + + def close(self): + # we now open a *separate* cursor, to update the data. + # FIXME: this may be improved, for concurrency handling + if self.mode == 'r': + StringIO.close(self) + return + + par = self._get_parent() + # uid = par.context.uid + cr = pooler.get_db(par.context.dbname).cursor() + try: + if self.mode in ('w', 'w+', 'r+'): + data = self.getvalue() + par.set_data(cr, data) + elif self.mode == 'a': + raise NotImplementedError + cr.commit() + except Exception: + logging.getLogger('document.nodes').exception('Cannot update db content #%d for close:', par.cnt_id) + raise + finally: + cr.close() + StringIO.close(self) + #eof
\ No newline at end of file diff --git a/addons/document/security/document_security.xml b/addons/document/security/document_security.xml index 2bf1a2adb57..ca55a8219c1 100644 --- a/addons/document/security/document_security.xml +++ b/addons/document/security/document_security.xml @@ -14,6 +14,28 @@ <field name="groups_id" eval="[(6,0,[ref('base.group_system')])]"/> </record> - + <record id="ir_rule_readpublicdirectories0" model="ir.rule"> + <field name="model_id" ref="document.model_document_directory"/> + <field name="domain_force">['|','|',('group_ids','in',[g.id for g in user.groups_id]), ('user_id', '=', user.id), '&', ('user_id', '=', False), ('group_ids','=',False), '|', ('company_id','=',False), ('company_id','child_of',[user.company_id.id])]</field> + <field name="name">Read public directories</field> + <field eval="0" name="global"/> + <field eval="[(6,0,[ref('base.group_user')])]" name="groups"/> + <field eval="0" name="perm_unlink"/> + <field eval="0" name="perm_write"/> + <field eval="1" name="perm_read"/> + <field eval="0" name="perm_create"/> + </record> + + <record id="ir_rule_documentmodifyowndirectories0" model="ir.rule"> + <field name="model_id" ref="document.model_document_directory"/> + <field name="domain_force">[ '|', ('user_id', '=', user.id), '&', ('group_ids','in',[g.id for g in user.groups_id]), ('user_id','=',False), '|', ('company_id','=',False), ('company_id','child_of',[user.company_id.id])]</field> + <field name="name">Document modify own directories</field> + <field eval="0" name="global"/> + <field eval="[(6,0,[ref('base.group_document_user')])]" name="groups"/> + <field eval="1" name="perm_unlink"/> + <field eval="1" name="perm_write"/> + <field eval="0" name="perm_read"/> + <field eval="1" name="perm_create"/> + </record> </data> </openerp> diff --git a/addons/document/security/ir.model.access.csv b/addons/document/security/ir.model.access.csv index 132daa1b7a6..d7aba7cbde7 100644 --- a/addons/document/security/ir.model.access.csv +++ b/addons/document/security/ir.model.access.csv @@ -1,6 +1,7 @@ "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" "access_document_directory_all","document.directory all","model_document_directory",,1,0,0,0 "access_document_directory_group_document_manager","document.directory document manager","model_document_directory","base.group_system",1,1,1,1 +"access_document_directory_group_knowledge","document.directory modify","model_document_directory","base.group_document_user",1,1,1,1 "access_document_directory_group_system","document.directory group system","model_document_directory","base.group_system",1,1,1,1 "access_document_directory_content_all","document.directory.content all","model_document_directory_content",,1,0,0,0 "access_document_directory_content_group_document_manager","document.directory.content document manager","model_document_directory_content","base.group_system",1,1,1,1 |
