def getLockList(self, path, include_root, include_children, token_only): """Return a list of direct locks for <path>. See wsgidav.lock_storage.LockStorageDict.getLockList() """ path = normalize_lock_root(path) lockRoots = cached_lock.get("*") if not lockRoots: return [] def __appendLocks(toklist): if token_only: lockList.extend(toklist) else: for token in toklist: lock = self.get(token) if lock: lockList.append(lock) lockList = [] if include_root and path in lockRoots: __appendLocks(lockRoots[path]) if include_children: for root, toks in list(lockRoots.items()): if util.is_child_uri(path, root): __appendLocks(toks) return lockList
def get_lock_list(self, path, include_root, include_children, token_only): """Return a list of direct locks for <path>. Expired locks are *not* returned (but may be purged). path: Normalized path (utf8 encoded string, no trailing '/') include_root: False: don't add <path> lock (only makes sense, when include_children is True). include_children: True: Also check all sub-paths for existing locks. token_only: True: only a list of token is returned. This may be implemented more efficiently by some providers. Returns: List of valid lock dictionaries (may be empty). """ assert compat.is_native(path) assert path and path.startswith("/") assert include_root or include_children def __appendLocks(toklist): # Since we can do this quickly, we use self.get() even if # token_only is set, so expired locks are purged. for token in toklist: lock = self.get(token) if lock: if token_only: lockList.append(lock["token"]) else: lockList.append(lock) path = normalize_lock_root(path) self._lock.acquire_read() try: key = "URL2TOKEN:{}".format(path) tokList = self._dict.get(key, []) lockList = [] if include_root: __appendLocks(tokList) if include_children: for u, ltoks in self._dict.items(): if util.is_child_uri(key, u): __appendLocks(ltoks) return lockList finally: self._lock.release()
def get_lock_list(self, path, include_root, include_children, token_only): """Return a list of direct locks for <path>. Expired locks are *not* returned (but may be purged). path: Normalized path (utf8 encoded string, no trailing '/') include_root: False: don't add <path> lock (only makes sense, when include_children is True). include_children: True: Also check all sub-paths for existing locks. token_only: True: only a list of token is returned. This may be implemented more efficiently by some providers. Returns: List of valid lock dictionaries (may be empty). """ assert compat.is_native(path) assert path and path.startswith("/") assert include_root or include_children def __appendLocks(toklist): # Since we can do this quickly, we use self.get() even if # token_only is set, so expired locks are purged. for token in map(lambda x: x.decode("utf-8"), toklist): lock = self._redis.get(self._redis_lock_prefix.format(token)) if lock: lock = pickle.loads(lock) if token_only: lockList.append(lock["token"]) else: lockList.append(lock) path = normalize_lock_root(path) key = self._redis_url2token_prefix.format(path) tokList = self._redis.lrange(key, 0, -1) lockList = [] if include_root: __appendLocks(tokList) if include_children: for u in map(lambda x: x.decode("utf-8"), self._redis.keys(key + "/*")): if util.is_child_uri(key, u): __appendLocks(self._redis.lrange(u, 0, -1)) return lockList
def testBasics(self): """Test basic tool functions.""" assert join_uri("/a/b", "c") == "/a/b/c" assert join_uri("/a/b/", "c") == "/a/b/c" assert join_uri("/a/b", "c", "d") == "/a/b/c/d" assert join_uri("a/b", "c", "d") == "a/b/c/d" assert join_uri("/", "c") == "/c" assert join_uri("", "c") == "/c" assert not is_child_uri("/a/b", "/a/") assert not is_child_uri("/a/b", "/a/b") assert not is_child_uri("/a/b", "/a/b/") assert not is_child_uri("/a/b", "/a/bc") assert not is_child_uri("/a/b", "/a/bc/") assert is_child_uri("/a/b", "/a/b/c") assert is_child_uri("/a/b", "/a/b/c") assert not is_equal_or_child_uri("/a/b", "/a/") assert is_equal_or_child_uri("/a/b", "/a/b") assert is_equal_or_child_uri("/a/b", "/a/b/") assert not is_equal_or_child_uri("/a/b", "/a/bc") assert not is_equal_or_child_uri("/a/b", "/a/bc/") assert is_equal_or_child_uri("/a/b", "/a/b/c") assert is_equal_or_child_uri("/a/b", "/a/b/c") assert lstripstr("/dav/a/b", "/dav") == "/a/b" assert lstripstr("/dav/a/b", "/DAV") == "/dav/a/b" assert lstripstr("/dav/a/b", "/DAV", True) == "/a/b" assert pop_path("/a/b/c") == ("a", "/b/c") assert pop_path("/a/b/") == ("a", "/b/") assert pop_path("/a/") == ("a", "/") assert pop_path("/a") == ("a", "/") assert pop_path("/") == ("", "") assert pop_path("") == ("", "") self.assertEqual(shift_path("", "/a/b/c"), ("a", "/a", "/b/c")) self.assertEqual(shift_path("/a", "/b/c"), ("b", "/a/b", "/c")) self.assertEqual(shift_path("/a/b", "/c"), ("c", "/a/b/c", "")) self.assertEqual(shift_path("/a/b/c", "/"), ("", "/a/b/c", "")) self.assertEqual(shift_path("/a/b/c", ""), ("", "/a/b/c", ""))
def testBasics(self): """Test basic tool functions.""" assert join_uri("/a/b", "c") == "/a/b/c" assert join_uri("/a/b/", "c") == "/a/b/c" assert join_uri("/a/b", "c", "d") == "/a/b/c/d" assert join_uri("a/b", "c", "d") == "a/b/c/d" assert join_uri("/", "c") == "/c" assert join_uri("", "c") == "/c" assert not is_child_uri("/a/b", "/a/") assert not is_child_uri("/a/b", "/a/b") assert not is_child_uri("/a/b", "/a/b/") assert not is_child_uri("/a/b", "/a/bc") assert not is_child_uri("/a/b", "/a/bc/") assert is_child_uri("/a/b", "/a/b/c") assert is_child_uri("/a/b", "/a/b/c") assert not is_equal_or_child_uri("/a/b", "/a/") assert is_equal_or_child_uri("/a/b", "/a/b") assert is_equal_or_child_uri("/a/b", "/a/b/") assert not is_equal_or_child_uri("/a/b", "/a/bc") assert not is_equal_or_child_uri("/a/b", "/a/bc/") assert is_equal_or_child_uri("/a/b", "/a/b/c") assert is_equal_or_child_uri("/a/b", "/a/b/c") assert lstripstr("/dav/a/b", "/dav") == "/a/b" assert lstripstr("/dav/a/b", "/DAV") == "/dav/a/b" assert lstripstr("/dav/a/b", "/DAV", True) == "/a/b" assert pop_path("/a/b/c") == ("a", "/b/c") assert pop_path("/a/b/") == ("a", "/b/") assert pop_path("/a/") == ("a", "/") assert pop_path("/a") == ("a", "/") assert pop_path("/") == ("", "") assert pop_path("") == ("", "") self.assertEqual(shift_path("", "/a/b/c"), ("a", "/a", "/b/c")) self.assertEqual(shift_path("/a", "/b/c"), ("b", "/a/b", "/c")) self.assertEqual(shift_path("/a/b", "/c"), ("c", "/a/b/c", "")) self.assertEqual(shift_path("/a/b/c", "/"), ("", "/a/b/c", "")) self.assertEqual(shift_path("/a/b/c", ""), ("", "/a/b/c", ""))
def check_write_permission(self, url, depth, tokenList, principal): """Check, if <principal> can modify <url>, otherwise raise HTTP_LOCKED. If modifying <url> is prevented by a lock, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting locks. <url> may be modified by <principal>, if it is not currently locked directly or indirectly (i.e. by a locked parent). For depth-infinity operations, <url> also must not have locked children. It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. Because <principal> may run two different applications. See http://www.webdav.org/specs/rfc4918.html#lock-model http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4 TODO: verify assumptions: - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. @param url: URL that shall be modified, created, moved, or deleted @param depth: "0"|"infinity" @param tokenList: list of lock tokens, that the principal submitted in If: header @param principal: name of the principal requesting a lock @return: None or raise error """ assert compat.is_native(url) assert depth in ("0", "infinity") _logger.debug("check_write_permission({}, {}, {}, {})".format( url, depth, tokenList, principal)) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquire_read() try: # Check url and all parents for conflicting locks u = url while u: ll = self.get_url_lock_list(u) _logger.debug(" checking {}".format(u)) for l in ll: _logger.debug(" l={}".format(lock_string(l))) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: inifinity continue elif principal == l["principal"] and l[ "token"] in tokenList: # User owns this lock continue else: # Token is owned by principal, but not passed with lock list _logger.debug( " -> DENIED due to locked parent {}".format( lock_string(l))) errcond.add_href(l["root"]) u = util.get_uri_parent(u) if depth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.get_lock_list(url, includeRoot=False, includeChildren=True, tokenOnly=False) for l in childLocks: assert util.is_child_uri(url, l["root"]) # if util.is_child_uri(url, l["root"]): _logger.debug(" -> DENIED due to locked child {}".format( lock_string(l))) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, errcondition=errcond) return
def _check_lock_permission(self, url, locktype, lockscope, lockdepth, tokenList, principal): """Check, if <principal> can lock <url>, otherwise raise an error. If locking <url> would create a conflict, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting resource. @see http://www.webdav.org/specs/rfc4918.html#lock-model - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive depth-infinity parent locks WILL be conflicting, even if they are owned by <principal>. - Child locks WILL NOT be conflicting, if we request a depth-0 lock. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. (7.7) - It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. (Because <principal> may run two different applications on his client.) - <principal> cannot lock-exclusive, if he holds a parent shared-lock. (This would only make sense, if he was the only shared-lock holder.) - TODO: litmus tries to acquire a shared lock on one resource twice (locks: 27 'double_sharedlock') and fails, when we return HTTP_LOCKED. So we allow multi shared locks on a resource even for the same principal. @param url: URL that shall be locked @param locktype: "write" @param lockscope: "shared"|"exclusive" @param lockdepth: "0"|"infinity" @param tokenList: list of lock tokens, that the user submitted in If: header @param principal: name of the principal requesting a lock @return: None (or raise) """ assert locktype == "write" assert lockscope in ("shared", "exclusive") assert lockdepth in ("0", "infinity") _logger.debug("checkLockPermission({}, {}, {}, {})".format( url, lockscope, lockdepth, principal)) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquire_read() try: # Check url and all parents for conflicting locks u = url while u: ll = self.get_url_lock_list(u) for l in ll: _logger.debug(" check parent {}, {}".format( u, lock_string(l))) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: infinity continue elif l["scope"] == "shared" and lockscope == "shared": # Only compatible with shared locks (even by same # principal) continue # Lock conflict _logger.debug(" -> DENIED due to locked parent {}".format( lock_string(l))) errcond.add_href(l["root"]) u = util.get_uri_parent(u) if lockdepth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.get_lock_list(url, includeRoot=False, includeChildren=True, tokenOnly=False) for l in childLocks: assert util.is_child_uri(url, l["root"]) # if util.is_child_uri(url, l["root"]): _logger.debug(" -> DENIED due to locked child {}".format( lock_string(l))) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, errcondition=errcond) return
def check_write_permission(self, url, depth, token_list, principal): """Check, if <principal> can modify <url>, otherwise raise HTTP_LOCKED. If modifying <url> is prevented by a lock, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting locks. <url> may be modified by <principal>, if it is not currently locked directly or indirectly (i.e. by a locked parent). For depth-infinity operations, <url> also must not have locked children. It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. Because <principal> may run two different applications. See http://www.webdav.org/specs/rfc4918.html#lock-model http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4 TODO: verify assumptions: - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. @param url: URL that shall be modified, created, moved, or deleted @param depth: "0"|"infinity" @param token_list: list of lock tokens, that the principal submitted in If: header @param principal: name of the principal requesting a lock @return: None or raise error """ assert compat.is_native(url) assert depth in ("0", "infinity") _logger.debug( "check_write_permission({}, {}, {}, {})".format( url, depth, token_list, principal ) ) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquire_read() try: # Check url and all parents for conflicting locks u = url while u: ll = self.get_url_lock_list(u) _logger.debug(" checking {}".format(u)) for l in ll: _logger.debug(" l={}".format(lock_string(l))) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: inifinity continue elif principal == l["principal"] and l["token"] in token_list: # User owns this lock continue else: # Token is owned by principal, but not passed with lock list _logger.debug( " -> DENIED due to locked parent {}".format(lock_string(l)) ) errcond.add_href(l["root"]) u = util.get_uri_parent(u) if depth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.get_lock_list( url, include_root=False, include_children=True, token_only=False ) for l in childLocks: assert util.is_child_uri(url, l["root"]) # if util.is_child_uri(url, l["root"]): _logger.debug( " -> DENIED due to locked child {}".format(lock_string(l)) ) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, err_condition=errcond) return
def _check_lock_permission( self, url, lock_type, lock_scope, lock_depth, token_list, principal ): """Check, if <principal> can lock <url>, otherwise raise an error. If locking <url> would create a conflict, DAVError(HTTP_LOCKED) is raised. An embedded DAVErrorCondition contains the conflicting resource. @see http://www.webdav.org/specs/rfc4918.html#lock-model - Parent locks WILL NOT be conflicting, if they are depth-0. - Exclusive depth-infinity parent locks WILL be conflicting, even if they are owned by <principal>. - Child locks WILL NOT be conflicting, if we request a depth-0 lock. - Exclusive child locks WILL be conflicting, even if they are owned by <principal>. (7.7) - It is not enough to check whether a lock is owned by <principal>, but also the token must be passed with the request. (Because <principal> may run two different applications on his client.) - <principal> cannot lock-exclusive, if he holds a parent shared-lock. (This would only make sense, if he was the only shared-lock holder.) - TODO: litmus tries to acquire a shared lock on one resource twice (locks: 27 'double_sharedlock') and fails, when we return HTTP_LOCKED. So we allow multi shared locks on a resource even for the same principal. @param url: URL that shall be locked @param lock_type: "write" @param lock_scope: "shared"|"exclusive" @param lock_depth: "0"|"infinity" @param token_list: list of lock tokens, that the user submitted in If: header @param principal: name of the principal requesting a lock @return: None (or raise) """ assert lock_type == "write" assert lock_scope in ("shared", "exclusive") assert lock_depth in ("0", "infinity") _logger.debug( "checkLockPermission({}, {}, {}, {})".format( url, lock_scope, lock_depth, principal ) ) # Error precondition to collect conflicting URLs errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) self._lock.acquire_read() try: # Check url and all parents for conflicting locks u = url while u: ll = self.get_url_lock_list(u) for l in ll: _logger.debug(" check parent {}, {}".format(u, lock_string(l))) if u != url and l["depth"] != "infinity": # We only consider parents with Depth: infinity continue elif l["scope"] == "shared" and lock_scope == "shared": # Only compatible with shared locks (even by same # principal) continue # Lock conflict _logger.debug( " -> DENIED due to locked parent {}".format(lock_string(l)) ) errcond.add_href(l["root"]) u = util.get_uri_parent(u) if lock_depth == "infinity": # Check child URLs for conflicting locks childLocks = self.storage.get_lock_list( url, include_root=False, include_children=True, token_only=False ) for l in childLocks: assert util.is_child_uri(url, l["root"]) # if util.is_child_uri(url, l["root"]): _logger.debug( " -> DENIED due to locked child {}".format(lock_string(l)) ) errcond.add_href(l["root"]) finally: self._lock.release() # If there were conflicts, raise HTTP_LOCKED for <url>, and pass # conflicting resource with 'no-conflicting-lock' precondition if len(errcond.hrefs) > 0: raise DAVError(HTTP_LOCKED, err_condition=errcond) return