summaryrefslogtreecommitdiff
path: root/addons/document
diff options
context:
space:
mode:
authorHarry (OpenERP) <hmo@tinyerp.com>2010-11-12 15:18:36 +0530
committerHarry (OpenERP) <hmo@tinyerp.com>2010-11-12 15:18:36 +0530
commitca33cf5313aa527d7e619b8c2fb9159ce20af108 (patch)
treec2247df51f09f05c340d35f3ccf392016668297d /addons/document
parent515b856d55203f813a324bb7a6cfa2ba11695d39 (diff)
parentd3b79b05fcbcbd0da299c09f96edc4d1ac698cc0 (diff)
[MERGE]
bzr revid: hmo@tinyerp.com-20101112094836-qy4536d3tjvw04xc
Diffstat (limited to 'addons/document')
-rw-r--r--addons/document/__openerp__.py2
-rw-r--r--addons/document/doc/access_permissions.rst65
-rw-r--r--addons/document/document.py4
-rw-r--r--addons/document/document_data.xml20
-rw-r--r--addons/document/document_directory.py46
-rw-r--r--addons/document/document_storage.py79
-rw-r--r--addons/document/nodes.py168
-rw-r--r--addons/document/security/document_security.xml24
-rw-r--r--addons/document/security/ir.model.access.csv1
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), '&amp;', ('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), '&amp;', ('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