doc webdav: Implement locking support
This replaces the pseydo-locking of python-webdav library with a real db-based locking. Locks are stored as DAV properties, which will effectively also be listed in the PROPFIND response of the nodes. With locking in place, Office suites can collaborate on documents online. bzr revid: p_christ@hol.gr-20101207134041-8negkvxrbscv7fs7
This commit is contained in:
parent
9a4c999803
commit
6b2c832f5f
|
@ -35,6 +35,9 @@ import urllib
|
|||
from DAV.davcmd import copyone, copytree, moveone, movetree, delone, deltree
|
||||
from cache import memoize
|
||||
from tools import misc
|
||||
|
||||
from webdav import mk_lock_response
|
||||
|
||||
try:
|
||||
from tools.dict_tools import dict_merge2
|
||||
except ImportError:
|
||||
|
@ -209,18 +212,20 @@ class openerp_dav_handler(dav_interface):
|
|||
self.parent.log_error("Cannot %s: %s", opname, err.strerror)
|
||||
self.parent.log_message("Exc: %s",traceback.format_exc())
|
||||
raise default_exc(err.strerror)
|
||||
except Exception,e:
|
||||
except Exception, e:
|
||||
import traceback
|
||||
if cr: cr.close()
|
||||
self.parent.log_error("Cannot %s: %s", opname, str(e))
|
||||
self.parent.log_message("Exc: %s",traceback.format_exc())
|
||||
raise default_exc("Operation failed")
|
||||
|
||||
#def _get_dav_lockdiscovery(self, uri):
|
||||
# raise DAV_NotFound
|
||||
def _get_dav_lockdiscovery(self, uri):
|
||||
""" We raise that so that the node API is used """
|
||||
raise DAV_NotFound
|
||||
|
||||
#def A_get_dav_supportedlock(self, uri):
|
||||
# raise DAV_NotFound
|
||||
def _get_dav_supportedlock(self, uri):
|
||||
""" We raise that so that the node API is used """
|
||||
raise DAV_NotFound
|
||||
|
||||
def match_prop(self, uri, match, ns, propname):
|
||||
if self.M_NS.has_key(ns):
|
||||
|
@ -911,6 +916,91 @@ class openerp_dav_handler(dav_interface):
|
|||
cr.close()
|
||||
return result
|
||||
|
||||
def unlock(self, uri, token):
|
||||
""" Unlock a resource from that token
|
||||
|
||||
@return True if unlocked, False if no lock existed, Exceptions
|
||||
"""
|
||||
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
|
||||
if not dbname:
|
||||
if cr: cr.close()
|
||||
raise DAV_Error, 409
|
||||
|
||||
node = self.uri2object(cr, uid, pool, uri2)
|
||||
try:
|
||||
node_fn = node.dav_unlock
|
||||
except AttributeError:
|
||||
# perhaps the node doesn't support locks
|
||||
cr.close()
|
||||
raise DAV_Error(400, 'No locks for this resource')
|
||||
|
||||
res = self._try_function(node_fn, (cr, token), "unlock %s" % uri, cr=cr)
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return res
|
||||
|
||||
def lock(self, uri, lock_data):
|
||||
""" Lock (may create) resource.
|
||||
Data is a dict, may contain:
|
||||
depth, token, refresh, lockscope, locktype, owner
|
||||
"""
|
||||
cr, uid, pool, dbname, uri2 = self.get_cr(uri)
|
||||
created = False
|
||||
if not dbname:
|
||||
if cr: cr.close()
|
||||
raise DAV_Error, 409
|
||||
|
||||
try:
|
||||
node = self.uri2object(cr, uid, pool, uri2[:])
|
||||
except Exception:
|
||||
node = False
|
||||
|
||||
objname = misc.ustr(uri2[-1])
|
||||
|
||||
if not node:
|
||||
dir_node = self.uri2object(cr, uid, pool, uri2[:-1])
|
||||
if not dir_node:
|
||||
cr.close()
|
||||
raise DAV_NotFound('Parent folder not found')
|
||||
|
||||
# We create a new node (file) but with empty data=None,
|
||||
# as in RFC4918 p. 9.10.4
|
||||
node = self._try_function(dir_node.create_child, (cr, objname, None),
|
||||
"create %s" % objname, cr=cr)
|
||||
if not node:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
raise DAV_Error(400, "Failed to create resource")
|
||||
|
||||
created = True
|
||||
|
||||
try:
|
||||
node_fn = node.dav_lock
|
||||
except AttributeError:
|
||||
# perhaps the node doesn't support locks
|
||||
cr.close()
|
||||
raise DAV_Error(400, 'No locks for this resource')
|
||||
|
||||
# Obtain the lock on the node
|
||||
lres, pid, token = self._try_function(node_fn, (cr, lock_data), "lock %s" % objname, cr=cr)
|
||||
|
||||
if not lres:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
raise DAV_Error(423, "Resource already locked")
|
||||
|
||||
assert isinstance(lres, list), 'lres: %s' % repr(lres)
|
||||
|
||||
try:
|
||||
data = mk_lock_response(self, uri, lres)
|
||||
cr.commit()
|
||||
except Exception:
|
||||
cr.close()
|
||||
raise
|
||||
|
||||
cr.close()
|
||||
return created, data, token
|
||||
|
||||
@memoize(CACHE_SIZE)
|
||||
def is_collection(self, uri):
|
||||
""" test if the given uri is a collection """
|
||||
|
|
|
@ -22,14 +22,14 @@
|
|||
|
||||
from document import nodes
|
||||
from tools.safe_eval import safe_eval as eval
|
||||
import time
|
||||
import urllib
|
||||
import uuid
|
||||
try:
|
||||
from tools.dict_tools import dict_filter
|
||||
except ImportError:
|
||||
from document.dict_tools import dict_filter
|
||||
|
||||
import urllib
|
||||
|
||||
|
||||
class node_acl_mixin(object):
|
||||
def _get_dav_owner(self, cr):
|
||||
return self.uuser
|
||||
|
@ -116,6 +116,153 @@ class node_acl_mixin(object):
|
|||
return val
|
||||
return None
|
||||
|
||||
def _dav_lock_hlpr(self, cr, lock_data, par_class, prop_model,
|
||||
prop_ref_field, res_id):
|
||||
""" Helper, which uses the dav properties table for placing locks
|
||||
|
||||
@param lock_data a dictionary of input to this function.
|
||||
@return list of tuples, DAV:activelock _contents_ structure.
|
||||
See webdav.py:class Prop2Xml() for semantics
|
||||
|
||||
Note: although the DAV response shall be an <activelock/>, this
|
||||
function will only return the elements inside the activelock,
|
||||
because the calling function needs to append the <lockroot/> in
|
||||
it. See webdav.py:mk_lock_response()
|
||||
|
||||
In order to reuse code, this function can be called with
|
||||
lock_data['unlock_mode']=True, in order to unlock.
|
||||
|
||||
@return bool in unlock mode, (davstruct, prop_id, token) in lock/refresh,
|
||||
or (False, prop_id, token) if already locked,
|
||||
or (False, False, False) if lock not found to refresh
|
||||
"""
|
||||
assert prop_model
|
||||
assert res_id
|
||||
assert isinstance(lock_data, dict), '%r' % lock_data
|
||||
propobj = self.context._dirobj.pool.get(prop_model)
|
||||
uid = self.context.uid
|
||||
ctx = self.context.context.copy()
|
||||
ctx.update(self.dctx)
|
||||
ctx.update({'uid': uid, 'dbname': self.context.dbname })
|
||||
ctx['node_classname'] = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
|
||||
dict_filter(self.context.extra_ctx, ['username', 'groupname', 'webdav_path'], ctx)
|
||||
sdomain = [(prop_ref_field, '=', res_id), ('namespace', '=', 'DAV:'),
|
||||
('name','=', 'lockdiscovery')]
|
||||
props_to_delete = []
|
||||
lock_found = False
|
||||
lock_val = None
|
||||
tmout2 = int(lock_data.get('timeout', 3*3600))
|
||||
|
||||
prop_ids = propobj.search(cr, uid, sdomain, context=ctx)
|
||||
if prop_ids:
|
||||
for pbro in propobj.browse(cr, uid, prop_ids, context=ctx):
|
||||
val = pbro.value
|
||||
if pbro.do_subst:
|
||||
if val.startswith("('") and val.endswith(")"):
|
||||
glbls = { 'urlquote': urllib.quote, }
|
||||
val = eval(val, glbls, ctx)
|
||||
else:
|
||||
# all locks should be at "subst" format
|
||||
continue
|
||||
if not (val and isinstance(val, tuple)
|
||||
and val[0:2] == ( 'activelock','DAV:')):
|
||||
# print "Value is not activelock:", val
|
||||
continue
|
||||
|
||||
old_token = False
|
||||
old_owner = False
|
||||
try:
|
||||
# discover the timeout. If anything goes wrong, delete
|
||||
# the lock (cleanup)
|
||||
tmout = False
|
||||
for parm in val[2]:
|
||||
if parm[1] != 'DAV:':
|
||||
continue
|
||||
if parm[0] == 'timeout':
|
||||
if isinstance(parm[2], basestring) \
|
||||
and parm[2].startswith('Second-'):
|
||||
tmout = int(parm[2][7:])
|
||||
elif parm[0] == 'locktoken':
|
||||
if isinstance(parm[2], basestring):
|
||||
old_token = parm[2]
|
||||
elif isinstance(parm[2], tuple) and \
|
||||
parm[2][0:2] == ('href','DAV:'):
|
||||
old_token = parm[2][2]
|
||||
else:
|
||||
# print "Mangled token in DAV property: %r" % parm[2]
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
elif parm[0] == 'owner':
|
||||
old_owner = parm[2] # not used yet
|
||||
if tmout:
|
||||
mdate = pbro.write_date or pbro.create_date
|
||||
mdate = time.mktime(time.strptime(mdate,'%Y-%m-%d %H:%M:%S'))
|
||||
if mdate + tmout < time.time():
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
else:
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
except ValueError:
|
||||
props_to_delete.append(pbro.id)
|
||||
continue
|
||||
|
||||
# A valid lock is found here
|
||||
if lock_data.get('refresh', False):
|
||||
if old_token != lock_data.get('token'):
|
||||
continue
|
||||
# refresh mode. Just touch anything and the ORM will update
|
||||
# the write uid+date, won't it?
|
||||
# Note: we don't update the owner, because incoming refresh
|
||||
# wouldn't have a body, anyway.
|
||||
propobj.write(cr, uid, [pbro.id,], { 'name': 'lockdiscovery'})
|
||||
elif lock_data.get('unlock_mode', False):
|
||||
if old_token != lock_data.get('token'):
|
||||
continue
|
||||
props_to_delete.append(pbro.id)
|
||||
|
||||
lock_found = pbro.id
|
||||
lock_val = val
|
||||
|
||||
if tmout2 > 3*3600: # 3 hours maximum
|
||||
tmout2 = 3*3600
|
||||
elif tmout2 < 300:
|
||||
# 5 minutes minimum, but an unlock request can always
|
||||
# break it at any time. Ensures no negative values, either.
|
||||
tmout2 = 300
|
||||
|
||||
if props_to_delete:
|
||||
# explicitly delete, as admin, any of the ids we have identified.
|
||||
propobj.unlink(cr, 1, props_to_delete)
|
||||
|
||||
if lock_data.get('unlock_mode', False):
|
||||
return lock_found and True
|
||||
elif (not lock_found) and not (lock_data.get('refresh', False)):
|
||||
# Create a new lock, attach and return it.
|
||||
new_token = uuid.uuid4().urn
|
||||
lock_val = ('activelock', 'DAV:',
|
||||
[ ('locktype', 'DAV:', (lock_data.get('locktype',False) or 'write','DAV:')),
|
||||
('lockscope', 'DAV:', (lock_data.get('lockscope',False) or 'exclusive','DAV:')),
|
||||
# ? ('depth', 'DAV:', lock_data.get('depth','0') ),
|
||||
('timeout','DAV:', 'Second-%d' % tmout2),
|
||||
('locktoken', 'DAV:', ('href', 'DAV:', new_token)),
|
||||
# ('lockroot', 'DAV: ..., we don't store that, appended by caller
|
||||
])
|
||||
new_owner = lock_data.get('lockowner',False) or ctx.get('username', False)
|
||||
if new_owner:
|
||||
lock_val[2].append( ('owner', 'DAV:', new_owner) )
|
||||
prop_id = propobj.create(cr, uid, { prop_ref_field: res_id,
|
||||
'namespace': 'DAV:', 'name': 'lockdiscovery',
|
||||
'do_subst': True, 'value': repr(lock_val) })
|
||||
return (lock_val[2], prop_id, new_token )
|
||||
elif not lock_found: # and refresh
|
||||
return (False, False, False)
|
||||
elif lock_found and not lock_data.get('refresh', False):
|
||||
# already locked
|
||||
return (False, lock_found, old_token)
|
||||
else:
|
||||
return (lock_val[2], lock_found, old_token )
|
||||
|
||||
class node_dir(node_acl_mixin, nodes.node_dir):
|
||||
""" override node_dir and add DAV functionality
|
||||
"""
|
||||
|
@ -141,7 +288,8 @@ class node_dir(node_acl_mixin, nodes.node_dir):
|
|||
class node_file(node_acl_mixin, nodes.node_file):
|
||||
DAV_PROPS = { "DAV:": ('owner', 'group',
|
||||
'supported-privilege-set',
|
||||
'current-user-privilege-set'),
|
||||
'current-user-privilege-set',
|
||||
),
|
||||
}
|
||||
DAV_M_NS = { "DAV:" : '_get_dav',}
|
||||
http_options = { 'DAV': ['access-control', ] }
|
||||
|
@ -152,10 +300,45 @@ class node_file(node_acl_mixin, nodes.node_file):
|
|||
|
||||
def get_dav_props(self, cr):
|
||||
return self._get_dav_props_hlpr(cr, nodes.node_dir,
|
||||
None, 'file_id', self.file_id)
|
||||
#'document.webdav.dir.property', 'dir_id', self.dir_id)
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
#def get_dav_eprop(self, cr, ns, prop):
|
||||
def dav_lock(self, cr, lock_data):
|
||||
""" Locks or unlocks the node, using DAV semantics.
|
||||
|
||||
Unlocking will be done when lock_data['unlock_mode'] == True
|
||||
|
||||
See _dav_lock_hlpr() for calling details.
|
||||
|
||||
It is fundamentally OK to use this function from non-DAV endpoints,
|
||||
but they will all have to emulate the tuple-in-list structure of
|
||||
the DAV lock data. RFC if this translation should be done inside
|
||||
the _dav_lock_hlpr (to ease other protocols).
|
||||
"""
|
||||
return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
def dav_unlock(self, cr, token):
|
||||
"""Releases the token lock held for the node
|
||||
|
||||
This is a utility complement of dav_lock()
|
||||
"""
|
||||
lock_data = { 'token': token, 'unlock_mode': True }
|
||||
return self._dav_lock_hlpr(cr, lock_data, nodes.node_file,
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
def get_dav_eprop(self, cr, ns, prop):
|
||||
if ns == 'DAV:' and prop == 'supportedlock':
|
||||
return [ ('lockentry', 'DAV:',
|
||||
[ ('lockscope','DAV:', ('shared', 'DAV:')),
|
||||
('locktype','DAV:', ('write', 'DAV:')),
|
||||
]),
|
||||
('lockentry', 'DAV:',
|
||||
[ ('lockscope','DAV:', ('exclusive', 'DAV:')),
|
||||
('locktype','DAV:', ('write', 'DAV:')),
|
||||
] )
|
||||
]
|
||||
return self._get_dav_eprop_hlpr(cr, ns, prop, nodes.node_file,
|
||||
'document.webdav.file.property', 'file_id', self.file_id)
|
||||
|
||||
class node_database(nodes.node_database):
|
||||
def get_dav_resourcetype(self, cr):
|
||||
|
|
|
@ -268,6 +268,39 @@ def mk_propname_response(self,uri,propnames,doc):
|
|||
PROPFIND.mk_prop_response = mk_prop_response
|
||||
PROPFIND.mk_propname_response = mk_propname_response
|
||||
|
||||
def mk_lock_response(self, uri, props):
|
||||
""" Prepare the data response to a DAV LOCK command
|
||||
|
||||
This function is here, merely to be in the same file as the
|
||||
ones above, that have similar code.
|
||||
"""
|
||||
doc = domimpl.createDocument('DAV:', "D:prop", None)
|
||||
ms = doc.documentElement
|
||||
ms.setAttribute("xmlns:D", "DAV:")
|
||||
# ms.tagName = 'D:multistatus'
|
||||
namespaces = []
|
||||
nsnum = 0
|
||||
propgen = Prop2xml(doc, namespaces, nsnum)
|
||||
# write href information
|
||||
uparts=urlparse.urlparse(uri)
|
||||
fileloc=uparts[2]
|
||||
if isinstance(fileloc, unicode):
|
||||
fileloc = fileloc.encode('utf-8')
|
||||
davpath = self.parent.get_davpath()
|
||||
if uparts[0] and uparts[1]:
|
||||
hurl = '%s://%s%s%s' % (uparts[0], uparts[1], davpath, urllib.quote(fileloc))
|
||||
else:
|
||||
# When the request has been relative, we don't have enough data to
|
||||
# reply with absolute url here.
|
||||
hurl = '%s%s' % (davpath, urllib.quote(fileloc))
|
||||
|
||||
props.append( ('lockroot', 'DAV:', ('href', 'DAV:', (hurl))))
|
||||
pld = doc.createElement('D:lockdiscovery')
|
||||
ms.appendChild(pld)
|
||||
propgen._prop_child(pld, 'DAV:', 'activelock', props)
|
||||
|
||||
return doc.toxml(encoding="utf-8")
|
||||
|
||||
super_create_prop = REPORT.create_prop
|
||||
|
||||
def create_prop(self):
|
||||
|
|
|
@ -38,7 +38,9 @@ import urllib
|
|||
import re
|
||||
from string import atoi
|
||||
from DAV.errors import *
|
||||
from DAV.utils import IfParser, TagList
|
||||
# from DAV.constants import DAV_VERSION_1, DAV_VERSION_2
|
||||
from xml.dom import minidom
|
||||
|
||||
khtml_re = re.compile(r' KHTML/([0-9\.]+) ')
|
||||
|
||||
|
@ -103,6 +105,7 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
|
|||
self.headers['Destination'] = up.path[len(self.davpath):]
|
||||
else:
|
||||
raise DAV_Forbidden("Not allowed to copy/move outside webdav path")
|
||||
# TODO: locks
|
||||
DAVRequestHandler.copymove(self, CLASS)
|
||||
|
||||
def get_davpath(self):
|
||||
|
@ -262,6 +265,139 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler):
|
|||
except DAV_Error, (ec, dd):
|
||||
return self.send_status(ec)
|
||||
|
||||
def do_UNLOCK(self):
|
||||
""" Unlocks given resource """
|
||||
|
||||
dc = self.IFACE_CLASS
|
||||
self.log_message('UNLOCKing resource %s' % self.headers)
|
||||
|
||||
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
|
||||
uri = urllib.unquote(uri)
|
||||
|
||||
token = self.headers.get('Lock-Token', False)
|
||||
if token:
|
||||
token = token.strip()
|
||||
if token[0] == '<' and token[-1] == '>':
|
||||
token = token[1:-1]
|
||||
else:
|
||||
token = False
|
||||
|
||||
if not token:
|
||||
return self.send_status(400, 'Bad lock token')
|
||||
|
||||
try:
|
||||
res = dc.unlock(uri, token)
|
||||
except DAV_Error, (ec, dd):
|
||||
return self.send_status(ec, dd)
|
||||
|
||||
if res == True:
|
||||
self.send_body(None, '204', 'OK', 'Resource unlocked.')
|
||||
else:
|
||||
# We just differentiate the description, for debugging purposes
|
||||
self.send_body(None, '204', 'OK', 'Resource not locked.')
|
||||
|
||||
def do_LOCK(self):
|
||||
""" Attempt to place a lock on the given resource.
|
||||
"""
|
||||
|
||||
dc = self.IFACE_CLASS
|
||||
lock_data = {}
|
||||
|
||||
self.log_message('LOCKing resource %s' % self.headers)
|
||||
|
||||
body = None
|
||||
if self.headers.has_key('Content-Length'):
|
||||
l = self.headers['Content-Length']
|
||||
body = self.rfile.read(atoi(l))
|
||||
|
||||
depth = self.headers.get('Depth', 'infinity')
|
||||
|
||||
uri = urlparse.urljoin(self.get_baseuri(dc), self.path)
|
||||
uri = urllib.unquote(uri)
|
||||
self.log_message('do_LOCK: uri = %s' % uri)
|
||||
|
||||
ifheader = self.headers.get('If')
|
||||
|
||||
if ifheader:
|
||||
ldif = IfParser(ifheader)
|
||||
if isinstance(ldif, list):
|
||||
if len(ldif) !=1 or (not isinstance(ldif[0], TagList)) \
|
||||
or len(ldif[0].list) != 1:
|
||||
raise DAV_Error(400, "Cannot accept multiple tokens")
|
||||
ldif = ldif[0].list[0]
|
||||
if ldif[0] == '<' and ldif[-1] == '>':
|
||||
ldif = ldif[1:-1]
|
||||
|
||||
lock_data['token'] = ldif
|
||||
|
||||
if not body:
|
||||
lock_data['refresh'] = True
|
||||
else:
|
||||
lock_data['refresh'] = False
|
||||
lock_data.update(self._lock_unlock_parse(body))
|
||||
|
||||
if lock_data['refresh'] and not lock_data.get('token', False):
|
||||
raise DAV_Error(400, 'Lock refresh must specify token')
|
||||
|
||||
lock_data['depth'] = depth
|
||||
|
||||
try:
|
||||
created, data, lock_token = dc.lock(uri, lock_data)
|
||||
except DAV_Error, (ec, dd):
|
||||
return self.send_status(ec, dd)
|
||||
|
||||
headers = {}
|
||||
if not lock_data['refresh']:
|
||||
headers['Lock-Token'] = '<%s>' % lock_token
|
||||
|
||||
if created:
|
||||
self.send_body(data, '201', 'Created', ctype='text/xml', headers=headers)
|
||||
else:
|
||||
self.send_body(data, '200', 'OK', ctype='text/xml', headers=headers)
|
||||
|
||||
def _lock_unlock_parse(self, body):
|
||||
# Override the python-webdav function, with some improvements
|
||||
# Unlike the py-webdav one, we also parse the owner minidom elements into
|
||||
# pure pythonic struct.
|
||||
doc = minidom.parseString(body)
|
||||
|
||||
data = {}
|
||||
owners = []
|
||||
for info in doc.getElementsByTagNameNS('DAV:', 'lockinfo'):
|
||||
for scope in info.getElementsByTagNameNS('DAV:', 'lockscope'):
|
||||
for scc in scope.childNodes:
|
||||
if scc.nodeType == info.ELEMENT_NODE \
|
||||
and scc.namespaceURI == 'DAV:':
|
||||
data['lockscope'] = scc.localName
|
||||
break
|
||||
for ltype in info.getElementsByTagNameNS('DAV:', 'locktype'):
|
||||
for ltc in ltype.childNodes:
|
||||
if ltc.nodeType == info.ELEMENT_NODE \
|
||||
and ltc.namespaceURI == 'DAV:':
|
||||
data['locktype'] = ltc.localName
|
||||
break
|
||||
for own in info.getElementsByTagNameNS('DAV:', 'owner'):
|
||||
for ono in own.childNodes:
|
||||
if ono.nodeType == info.TEXT_NODE:
|
||||
if ono.data:
|
||||
owners.append(ono.data)
|
||||
elif ono.nodeType == info.ELEMENT_NODE \
|
||||
and ono.namespaceURI == 'DAV:' \
|
||||
and ono.localName == 'href':
|
||||
href = ''
|
||||
for hno in ono.childNodes:
|
||||
if hno.nodeType == info.TEXT_NODE:
|
||||
href += hno.data
|
||||
owners.append(('href','DAV:', href))
|
||||
|
||||
if len(owners) == 1:
|
||||
data['lockowner'] = owners[0]
|
||||
elif not owners:
|
||||
pass
|
||||
else:
|
||||
data['lockowner'] = owners
|
||||
return data
|
||||
|
||||
from service.http_server import reg_http_service,OpenERPAuthProvider
|
||||
|
||||
class DAVAuthProvider(OpenERPAuthProvider):
|
||||
|
|
Loading…
Reference in New Issue