1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2010 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
27 from openerp import SUPERUSER_ID
28 from openerp.tools.safe_eval import safe_eval as eval
30 from openerp.addons.document import document as nodes
32 def dict_filter(srcdic, keys, res=None):
33 ''' Return a copy of srcdic that has only keys set.
34 If any of keys are missing from srcdic, the result won't have them,
36 @param res If given, result will be updated there, instead of a new dict.
45 class node_acl_mixin(object):
46 def _get_dav_owner(self, cr):
49 def _get_dav_group(self, cr):
52 def _get_dav_supported_privilege_set(self, cr):
55 def _get_dav_current_user_privilege_set(self, cr):
58 def _get_dav_props_hlpr(self, cr, par_class, prop_model,
59 prop_ref_field, res_id):
60 """ Helper for dav properties, usable in subclasses
62 @param par_class The parent class
63 @param prop_model The name of the orm model holding the properties
64 @param prop_ref_field The name of the field at prop_model pointing to us
65 @param res_id the id of self in the corresponing orm table, that should
66 match prop_model.prop_ref_field
68 ret = par_class.get_dav_props(self, cr)
70 propobj = self.context._dirobj.pool.get(prop_model)
71 uid = self.context.uid
72 ctx = self.context.context.copy()
74 # Not really needed because we don't do eval here:
75 # ctx.update({'uid': uid, 'dbname': self.context.dbname })
76 # dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
77 sdomain = [(prop_ref_field, '=', False),]
79 sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain
80 prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
83 for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
84 ret[pbro.namespace] = ret.get(pbro.namespace, ()) + \
86 # Note that we cannot have properties to conditionally appear
87 # on the context, yet.
91 def _get_dav_eprop_hlpr(self, cr, ns, prop,
92 par_class, prop_model,
93 prop_ref_field, res_id):
94 """ Helper for get dav eprop, usable in subclasses
96 @param namespace the one to search for
97 @param name Name to search for
98 @param par_class The parent class
99 @param prop_model The name of the orm model holding the properties
100 @param prop_ref_field The name of the field at prop_model pointing to us
101 @param res_id the id of self in the corresponing orm table, that should
102 match prop_model.prop_ref_field
104 ret = par_class.get_dav_eprop(self, cr, ns, prop)
108 propobj = self.context._dirobj.pool.get(prop_model)
109 uid = self.context.uid
110 ctx = self.context.context.copy()
111 ctx.update(self.dctx)
112 ctx.update({'uid': uid, 'dbname': self.context.dbname })
113 ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
114 dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
115 sdomain = [(prop_ref_field, '=', False),('namespace', '=', ns), ('name','=', prop)]
117 sdomain = ['|', (prop_ref_field, '=', res_id)] + sdomain
118 prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
120 pbro = propobj.browse(cr, uid, prop_ids[0], context=ctx)
123 if val.startswith("('") and val.endswith(")"):
124 glbls = { 'urlquote': urllib.quote, }
125 val = eval(val, glbls, ctx)
131 def _dav_lock_hlpr(self, cr, lock_data, par_class, prop_model,
132 prop_ref_field, res_id):
133 """ Helper, which uses the dav properties table for placing locks
135 @param lock_data a dictionary of input to this function.
136 @return list of tuples, DAV:activelock _contents_ structure.
137 See webdav.py:class Prop2Xml() for semantics
139 Note: although the DAV response shall be an <activelock/>, this
140 function will only return the elements inside the activelock,
141 because the calling function needs to append the <lockroot/> in
142 it. See webdav.py:mk_lock_response()
144 In order to reuse code, this function can be called with
145 lock_data['unlock_mode']=True, in order to unlock.
147 @return bool in unlock mode, (davstruct, prop_id, token) in lock/refresh,
148 or (False, prop_id, token) if already locked,
149 or (False, False, False) if lock not found to refresh
153 assert isinstance(lock_data, dict), '%r' % lock_data
154 propobj = self.context._dirobj.pool.get(prop_model)
155 uid = self.context.uid
156 ctx = self.context.context.copy()
157 ctx.update(self.dctx)
158 ctx.update({'uid': uid, 'dbname': self.context.dbname })
159 ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
160 dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
161 sdomain = [(prop_ref_field, '=', res_id), ('namespace', '=', 'DAV:'),
162 ('name','=', 'lockdiscovery')]
166 tmout2 = int(lock_data.get('timeout', 3*3600))
168 prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
170 for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
173 if val.startswith("('") and val.endswith(")"):
174 glbls = { 'urlquote': urllib.quote, }
175 val = eval(val, glbls, ctx)
177 # all locks should be at "subst" format
179 if not (val and isinstance(val, tuple)
180 and val[0:2] == ( 'activelock','DAV:')):
181 # print "Value is not activelock:", val
187 # discover the timeout. If anything goes wrong, delete
191 if parm[1] != 'DAV:':
193 if parm[0] == 'timeout':
194 if isinstance(parm[2], basestring) \
195 and parm[2].startswith('Second-'):
196 tmout = int(parm[2][7:])
197 elif parm[0] == 'locktoken':
198 if isinstance(parm[2], basestring):
200 elif isinstance(parm[2], tuple) and \
201 parm[2][0:2] == ('href','DAV:'):
202 old_token = parm[2][2]
204 # print "Mangled token in DAV property: %r" % parm[2]
205 props_to_delete.append(pbro.id)
207 elif parm[0] == 'owner':
208 old_owner = parm[2] # not used yet
210 mdate = pbro.write_date or pbro.create_date
211 mdate = time.mktime(time.strptime(mdate,'%Y-%m-%d %H:%M:%S'))
212 if mdate + tmout < time.time():
213 props_to_delete.append(pbro.id)
216 props_to_delete.append(pbro.id)
219 props_to_delete.append(pbro.id)
222 # A valid lock is found here
223 if lock_data.get('refresh', False):
224 if old_token != lock_data.get('token'):
226 # refresh mode. Just touch anything and the ORM will update
227 # the write uid+date, won't it?
228 # Note: we don't update the owner, because incoming refresh
229 # wouldn't have a body, anyway.
230 propobj.write(cr, uid, [pbro.id,], { 'name': 'lockdiscovery'})
231 elif lock_data.get('unlock_mode', False):
232 if old_token != lock_data.get('token'):
234 props_to_delete.append(pbro.id)
239 if tmout2 > 3*3600: # 3 hours maximum
242 # 5 minutes minimum, but an unlock request can always
243 # break it at any time. Ensures no negative values, either.
247 # explicitly delete, as admin, any of the ids we have identified.
248 propobj.unlink(cr, SUPERUSER_ID, props_to_delete)
250 if lock_data.get('unlock_mode', False):
251 return lock_found and True
252 elif (not lock_found) and not (lock_data.get('refresh', False)):
253 # Create a new lock, attach and return it.
254 new_token = uuid.uuid4().urn
255 lock_val = ('activelock', 'DAV:',
256 [ ('locktype', 'DAV:', (lock_data.get('locktype',False) or 'write','DAV:')),
257 ('lockscope', 'DAV:', (lock_data.get('lockscope',False) or 'exclusive','DAV:')),
258 # ? ('depth', 'DAV:', lock_data.get('depth','0') ),
259 ('timeout','DAV:', 'Second-%d' % tmout2),
260 ('locktoken', 'DAV:', ('href', 'DAV:', new_token)),
261 # ('lockroot', 'DAV: ..., we don't store that, appended by caller
263 new_owner = lock_data.get('lockowner',False) or ctx.get('username', False)
265 lock_val[2].append( ('owner', 'DAV:', new_owner) )
266 prop_id = propobj.create(cr, uid, { prop_ref_field: res_id,
267 'namespace': 'DAV:', 'name': 'lockdiscovery',
268 'do_subst': True, 'value': repr(lock_val) })
269 return (lock_val[2], prop_id, new_token )
270 elif not lock_found: # and refresh
271 return (False, False, False)
272 elif lock_found and not lock_data.get('refresh', False):
274 return (False, lock_found, old_token)
276 return (lock_val[2], lock_found, old_token )
278 class node_dir(node_acl_mixin, nodes.node_dir):
279 """ override node_dir and add DAV functionality
281 DAV_PROPS = { "DAV:": ('owner', 'group',
282 'supported-privilege-set',
283 'current-user-privilege-set'),
285 DAV_M_NS = { "DAV:" : '_get_dav',}
286 http_options = { 'DAV': ['access-control',] }
288 def get_dav_resourcetype(self, cr):
289 return ('collection', 'DAV:')
291 def get_dav_props(self, cr):
292 return self._get_dav_props_hlpr(cr, nodes.node_dir,
293 'document.webdav.dir.property', 'dir_id', self.dir_id)
295 def get_dav_eprop(self, cr, ns, prop):
296 return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_dir,
297 'document.webdav.dir.property', 'dir_id', self.dir_id)
300 class node_file(node_acl_mixin, nodes.node_file):
301 DAV_PROPS = { "DAV:": ('owner', 'group',
302 'supported-privilege-set',
303 'current-user-privilege-set',
306 DAV_M_NS = { "DAV:" : '_get_dav',}
307 http_options = { 'DAV': ['access-control', ] }
310 def get_dav_resourcetype(self, cr):
313 def get_dav_props(self, cr):
314 return self._get_dav_props_hlpr(cr, nodes.node_file,
315 'document.webdav.file.property', 'file_id', self.file_id)
317 def dav_lock(self, cr, lock_data):
318 """ Locks or unlocks the node, using DAV semantics.
320 Unlocking will be done when lock_data['unlock_mode'] == True
322 See _dav_lock_hlpr() for calling details.
324 It is fundamentally OK to use this function from non-DAV endpoints,
325 but they will all have to emulate the tuple-in-list structure of
326 the DAV lock data. RFC if this translation should be done inside
327 the _dav_lock_hlpr (to ease other protocols).
329 return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
330 'document.webdav.file.property', 'file_id', self.file_id)
332 def dav_unlock(self, cr, token):
333 """Releases the token lock held for the node
335 This is a utility complement of dav_lock()
337 lock_data = { 'token': token, 'unlock_mode': True }
338 return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
339 'document.webdav.file.property', 'file_id', self.file_id)
341 def get_dav_eprop(self, cr, ns, prop):
342 if ns == 'DAV:' and prop == 'supportedlock':
343 return [ ('lockentry', 'DAV:',
344 [ ('lockscope','DAV:', ('shared', 'DAV:')),
345 ('locktype','DAV:', ('write', 'DAV:')),
347 ('lockentry', 'DAV:',
348 [ ('lockscope','DAV:', ('exclusive', 'DAV:')),
349 ('locktype','DAV:', ('write', 'DAV:')),
352 return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_file,
353 'document.webdav.file.property', 'file_id', self.file_id)
355 class node_database(nodes.node_database):
356 def get_dav_resourcetype(self, cr):
357 return ('collection', 'DAV:')
359 def get_dav_props(self, cr):
360 return self._get_dav_props_hlpr(cr, nodes.node_database,
361 'document.webdav.dir.property', 'dir_id', False)
363 def get_dav_eprop(self, cr, ns, prop):
364 return self._get_dav_eprop_hlpr(cr, nodes.node_database, ns, prop,
365 'document.webdav.dir.property', 'dir_id', False)
367 class node_res_obj(node_acl_mixin, nodes.node_res_obj):
368 DAV_PROPS = { "DAV:": ('owner', 'group',
369 'supported-privilege-set',
370 'current-user-privilege-set'),
372 DAV_M_NS = { "DAV:" : '_get_dav',}
373 http_options = { 'DAV': ['access-control',] }
375 def get_dav_resourcetype(self, cr):
376 return ('collection', 'DAV:')
378 def get_dav_props(self, cr):
379 return self._get_dav_props_hlpr(cr, nodes.node_res_obj,
380 'document.webdav.dir.property', 'dir_id', self.dir_id)
382 def get_dav_eprop(self, cr, ns, prop):
383 return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_obj,
384 'document.webdav.dir.property', 'dir_id', self.dir_id)
387 class node_res_dir(node_acl_mixin, nodes.node_res_dir):
388 DAV_PROPS = { "DAV:": ('owner', 'group',
389 'supported-privilege-set',
390 'current-user-privilege-set'),
392 DAV_M_NS = { "DAV:" : '_get_dav',}
393 http_options = { 'DAV': ['access-control',] }
394 res_obj_class = node_res_obj
396 def get_dav_resourcetype(self, cr):
397 return ('collection', 'DAV:')
399 def get_dav_props(self, cr):
400 return self._get_dav_props_hlpr(cr, nodes.node_res_dir,
401 'document.webdav.dir.property', 'dir_id', self.dir_id)
403 def get_dav_eprop(self, cr, ns, prop):
404 return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_res_dir,
405 'document.webdav.dir.property', 'dir_id', self.dir_id)
407 # Some copies, so that this module can replace 'from document import nodes'
408 get_node_context = nodes.get_node_context
409 node_context = nodes.node_context
410 node_class = nodes.node_class
411 node_descriptor = nodes.node_descriptor
416 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: