def get_indirect_url_lock_list(self, url, principal=None): """Return a list of valid lockDicts, that protect <path> directly or indirectly. If a principal is given, only locks owned by this principal are returned. Side effect: expired locks for this path and all parents are purged. """ url = normalize_lock_root(url) lockList = [] u = url while u: ll = self.storage.get_lock_list(u, includeRoot=True, includeChildren=False, tokenOnly=False) for l in ll: if u != url and l["depth"] != "infinity": continue # We only consider parents with Depth: infinity # TODO: handle shared locks in some way? # if (l["scope"] == "shared" and lockscope == "shared" # and principal != l["principal"]): # continue # Only compatible with shared locks by other users if principal is None or principal == l["principal"]: lockList.append(l) u = util.get_uri_parent(u) return lockList
def get_file_type(self, path: str): normed = os.path.normpath(path) # TODO: driveclient stat if normed == '/': return EntryType.Dir basename = os.path.basename(normed) try: metadata = self._get_metadata(get_uri_parent(normed)) def contains(l): return any( filter(lambda x: x.name == basename, l)) if contains(metadata.files): return EntryType.File if contains(metadata.dirs): return EntryType.Dir return EntryType.Enoent except DriveNotFoundException: return EntryType.Enoent
def get_indirect_url_lock_list(self, url, principal=None): """Return a list of valid lockDicts, that protect <path> directly or indirectly. If a principal is given, only locks owned by this principal are returned. Side effect: expired locks for this path and all parents are purged. """ url = normalize_lock_root(url) lockList = [] u = url while u: ll = self.storage.get_lock_list( u, include_root=True, include_children=False, token_only=False ) for l in ll: if u != url and l["depth"] != "infinity": continue # We only consider parents with Depth: infinity # TODO: handle shared locks in some way? # if (l["scope"] == "shared" and lock_scope == "shared" # and principal != l["principal"]): # continue # Only compatible with shared locks by other users if principal is None or principal == l["principal"]: lockList.append(l) u = util.get_uri_parent(u) return lockList
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 _get_context(self, environ, davres): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert davres.is_collection dirConfig = environ["wsgidav.config"].get("dir_browser", {}) isReadOnly = environ["wsgidav.provider"].is_readonly() context = { "htdocs": (self.config.get("mount_path") or "") + ASSET_SHARE, "rows": [], "version": __version__, "displaypath": compat.unquote(davres.get_href()), "url": davres.get_href(), # util.make_complete_url(environ), "parentUrl": util.get_uri_parent(davres.get_href()), "config": dirConfig, "is_readonly": isReadOnly, } trailer = dirConfig.get("response_trailer") if trailer is True: trailer = "${version} - ${time}" if trailer: trailer = trailer.replace( "${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format( __version__ ), ) trailer = trailer.replace("${time}", util.get_rfc1123_time()) context["trailer"] = trailer rows = context["rows"] # Ask collection for member info list dirInfoList = davres.get_directory_info() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = davres.get_descendants(depth="1", addSelf=False) for res in childList: di = res.get_display_info() href = res.get_href() classes = [] if res.is_collection: classes.append("directory") if not isReadOnly and not res.is_collection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: if dirConfig.get("ms_sharepoint_plugin"): classes.append("msoffice") elif dirConfig.get("ms_sharepoint_urls"): href = "ms-{}:ofe|u|{}".format(officeType, href) entry = { "href": href, "class": " ".join(classes), "displayName": res.get_display_name(), "lastModified": res.get_last_modified(), "is_collection": res.is_collection, "contentLength": res.get_content_length(), "displayType": di.get("type"), "displayTypeComment": di.get("typeComment"), } dirInfoList.append(entry) # ignore_patterns = dirConfig.get("ignore", []) if compat.is_basestring(ignore_patterns): ignore_patterns = ignore_patterns.split(",") for entry in dirInfoList: # Skip ignore patterns ignore = False for pat in ignore_patterns: if fnmatch(entry["displayName"], pat): _logger.debug("Ignore {}".format(entry["displayName"])) ignore = True break if ignore: continue # lastModified = entry.get("lastModified") if lastModified is None: entry["strModified"] = "" else: entry["strModified"] = util.get_rfc1123_time(lastModified) entry["strSize"] = "-" if not entry.get("is_collection"): contentLength = entry.get("contentLength") if contentLength is not None: entry["strSize"] = util.byte_number_string(contentLength) rows.append(entry) # sort sort = "name" if sort == "name": rows.sort( key=lambda v: "{}{}".format( not v["is_collection"], v["displayName"].lower() ) ) if "http_authenticator.username" in environ: context["username"] = ( environ.get("http_authenticator.username") or "anonymous" ) context["realm"] = environ.get("http_authenticator.realm") return context
def _clear_cache(self, path: str): dirname = get_uri_parent(path) normed = os.path.normpath(dirname) if normed in self.metadata_cache: del self.metadata_cache[normed]
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
def _get_context(self, environ, dav_res): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert dav_res.is_collection is_readonly = environ["wsgidav.provider"].is_readonly() context = { "htdocs": (self.config.get("mount_path") or "") + ASSET_SHARE, "rows": [], "version": __version__, "display_path": compat.unquote(dav_res.get_href()), "url": dav_res.get_href(), # util.make_complete_url(environ), "parent_url": util.get_uri_parent(dav_res.get_href()), "config": self.dir_config, "is_readonly": is_readonly, "access": "read-only" if is_readonly else "read-write", "is_authenticated": False, } trailer = self.dir_config.get("response_trailer") if trailer is True: #trailer = "${version} - ${time}" trailer = "Shihira Fung - ${time}" if trailer: trailer = trailer.replace( "${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format( __version__ ), ) trailer = trailer.replace("${time}", util.get_rfc1123_time()) context["trailer"] = trailer rows = context["rows"] # Ask collection for member info list dirInfoList = dav_res.get_directory_info() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = dav_res.get_descendants(depth="1", add_self=False) for res in childList: di = res.get_display_info() href = res.get_href() ofe_prefix = None tr_classes = [] a_classes = [] if res.is_collection: tr_classes.append("directory") if not is_readonly and not res.is_collection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: if self.dir_config.get("ms_sharepoint_support"): ofe_prefix = "ms-{}:ofe|u|".format(officeType) a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_plugin"): # a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_urls"): # href = "ms-{}:ofe|u|{}".format(officeType, href) entry = { "href": href, "ofe_prefix": ofe_prefix, "a_class": " ".join(a_classes), "tr_class": " ".join(tr_classes), "display_name": res.get_display_name(), "last_modified": res.get_last_modified(), "is_collection": res.is_collection, "content_length": res.get_content_length(), "display_type": di.get("type"), "display_type_comment": di.get("typeComment"), } dirInfoList.append(entry) # ignore_patterns = self.dir_config.get("ignore", []) if compat.is_basestring(ignore_patterns): ignore_patterns = ignore_patterns.split(",") ignored_list = [] for entry in dirInfoList: # Skip ignore patterns ignore = False for pat in ignore_patterns: if fnmatch(entry["display_name"], pat): ignored_list.append(entry["display_name"]) # _logger.debug("Ignore {}".format(entry["display_name"])) ignore = True break if ignore: continue # last_modified = entry.get("last_modified") if last_modified is None: entry["str_modified"] = "" else: import time entry["str_modified"] = time.strftime("%b %d %Y, %H:%M:%S", time.localtime(last_modified)) entry["str_size"] = "-" if not entry.get("is_collection"): content_length = entry.get("content_length") if content_length is not None: for unit in ['Bytes', 'KiB', 'MiB', 'GiB']: if content_length < 1024: content_length = "%.2f %s" % (content_length, unit) break content_length /= 1024.0 entry["str_size"] = content_length rows.append(entry) if ignored_list: _logger.debug( "Dir browser ignored {} entries: {}".format( len(ignored_list), ignored_list ) ) # sort sort = "name" if sort == "name": rows.sort( key=lambda v: "{}{}".format( not v["is_collection"], v["display_name"].lower() ) ) if "wsgidav.auth.user_name" in environ: context.update( { "is_authenticated": True, "user_name": (environ.get("wsgidav.auth.user_name") or "anonymous"), "realm": environ.get("wsgidav.auth.realm"), "user_roles": ", ".join(environ.get("wsgidav.auth.roles") or []), "user_permissions": ", ".join( environ.get("wsgidav.auth.permissions") or [] ), } ) return context
def _get_context(self, environ, dav_res): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert dav_res.is_collection is_readonly = environ["wsgidav.provider"].is_readonly() context = { "htdocs": (self.config.get("mount_path") or "") + ASSET_SHARE, "rows": [], "version": __version__, "display_path": compat.unquote(dav_res.get_href()), "url": dav_res.get_href(), # util.make_complete_url(environ), "parent_url": util.get_uri_parent(dav_res.get_href()), "config": self.dir_config, "is_readonly": is_readonly, "access": "read-only" if is_readonly else "read-write", "is_authenticated": False, } trailer = self.dir_config.get("response_trailer") if trailer is True: trailer = "${version} - ${time}" if trailer: trailer = trailer.replace( "${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format( __version__ ), ) trailer = trailer.replace("${time}", util.get_rfc1123_time()) context["trailer"] = trailer rows = context["rows"] # Ask collection for member info list dirInfoList = dav_res.get_directory_info() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = dav_res.get_descendants(depth="1", add_self=False) for res in childList: di = res.get_display_info() href = res.get_href() ofe_prefix = None tr_classes = [] a_classes = [] if res.is_collection: tr_classes.append("directory") if not is_readonly and not res.is_collection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: if self.dir_config.get("ms_sharepoint_support"): ofe_prefix = "ms-{}:ofe|u|".format(officeType) a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_plugin"): # a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_urls"): # href = "ms-{}:ofe|u|{}".format(officeType, href) entry = { "href": href, "ofe_prefix": ofe_prefix, "a_class": " ".join(a_classes), "tr_class": " ".join(tr_classes), "display_name": res.get_display_name(), "last_modified": res.get_last_modified(), "is_collection": res.is_collection, "content_length": res.get_content_length(), "display_type": di.get("type"), "display_type_comment": di.get("typeComment"), } dirInfoList.append(entry) # ignore_patterns = self.dir_config.get("ignore", []) if compat.is_basestring(ignore_patterns): ignore_patterns = ignore_patterns.split(",") ignored_list = [] for entry in dirInfoList: # Skip ignore patterns ignore = False for pat in ignore_patterns: if fnmatch(entry["display_name"], pat): ignored_list.append(entry["display_name"]) # _logger.debug("Ignore {}".format(entry["display_name"])) ignore = True break if ignore: continue # last_modified = entry.get("last_modified") if last_modified is None: entry["str_modified"] = "" else: entry["str_modified"] = util.get_rfc1123_time(last_modified) entry["str_size"] = "-" if not entry.get("is_collection"): content_length = entry.get("content_length") if content_length is not None: entry["str_size"] = util.byte_number_string(content_length) rows.append(entry) if ignored_list: _logger.debug( "Dir browser ignored {} entries: {}".format( len(ignored_list), ignored_list ) ) # sort sort = "name" if sort == "name": rows.sort( key=lambda v: "{}{}".format( not v["is_collection"], v["display_name"].lower() ) ) if "wsgidav.auth.user_name" in environ: context.update( { "is_authenticated": True, "user_name": (environ.get("wsgidav.auth.user_name") or "anonymous"), "realm": environ.get("wsgidav.auth.realm"), "user_roles": ", ".join(environ.get("wsgidav.auth.roles") or []), "user_permissions": ", ".join( environ.get("wsgidav.auth.permissions") or [] ), } ) return context