def __call__(self, environ, start_response): realmname = self._domaincontroller.getDomainRealm( environ["PATH_INFO"], environ) _logger.debug("realm '%s'" % realmname) # _logger.debug("%s" % environ) force_allow = False if HOTFIX_WIN_AcceptAnonymousOptions and environ["REQUEST_METHOD"] == "OPTIONS": _logger.warning("No authorization required for OPTIONS method") force_allow = True if force_allow or not self._domaincontroller.requireAuthentication(realmname, environ): # no authentication needed _logger.debug( "No authorization required for realm '%s'" % realmname) environ["http_authenticator.realm"] = realmname environ["http_authenticator.username"] = "" return self._application(environ, start_response) if self._trusted_auth_header and environ.get(self._trusted_auth_header): # accept a username that was injected by a trusted upstream server _logger.debug("Accept trusted username %s='%s'for realm '%s'" % ( self._trusted_auth_header, environ.get(self._trusted_auth_header), realmname)) environ["http_authenticator.realm"] = realmname environ["http_authenticator.username"] = environ.get( self._trusted_auth_header) return self._application(environ, start_response) if "HTTP_AUTHORIZATION" in environ: authheader = environ["HTTP_AUTHORIZATION"] authmatch = self._headermethod.search(authheader) authmethod = "None" if authmatch: authmethod = authmatch.group(1).lower() if authmethod == "digest" and self._acceptdigest: return self.authDigestAuthRequest(environ, start_response) elif authmethod == "digest" and self._acceptbasic: return self.sendBasicAuthResponse(environ, start_response) elif authmethod == "basic" and self._acceptbasic: return self.authBasicAuthRequest(environ, start_response) # The requested auth method is not supported. elif self._defaultdigest and self._acceptdigest: return self.sendDigestAuthResponse(environ, start_response) elif self._acceptbasic: return self.sendBasicAuthResponse(environ, start_response) util.log( "HTTPAuthenticator: respond with 400 Bad request; Auth-Method: %s" % authmethod) start_response("400 Bad Request", [("Content-Length", "0"), ("Date", util.getRfc1123Time()), ]) return [""] if self._defaultdigest: return self.sendDigestAuthResponse(environ, start_response) return self.sendBasicAuthResponse(environ, start_response)
def __init__(self, options): super(MongoResourceProvider, self).__init__() self.options = options self.conn = pymongo.Connection(options.get("host"), options.get("port")) if options.get("user"): # If credentials are passed, acquire root access db = self.conn["admin"] res = db.authenticate(options.get("user"), options.get("pwd")) if not res: raise RuntimeError("Failed to logon to db %s as user %s" % (db.name, options.get("user"))) util.log("Logged on to mongo db '%s' as user '%s'" % (db.name, options.get("user"))) util.log("MongoResourceProvider connected to %s" % self.conn)
def _connect(self): opts = self.options self.conn = pymongo.Connection(opts.get("host"), opts.get("port")) _logger.debug(self.conn.server_info()) self.db = self.conn[opts.get("dbName", "wsgidav-props")] # If credentials are passed, logon to the property storage db if opts.get("user"): if not self.db.authenticate(opts.get("user"), opts.get("pwd")): raise RuntimeError("Failed to logon to db %s as user %s" % (self.db.name, opts.get("user"))) util.log("Logged on to mongo db '%s' as user '%s'" % (self.db.name, opts.get("user"))) self.collection = self.db["properties"] util.log("MongoPropertyManager connected %r" % self.collection) _res = self.collection.ensure_index("_url")
def start_response_wrapper(status, response_headers, exc_info=None): # TODO: not fully understood: if exc_info is not None: util.log("DebugFilter got exc_info", exc_info) # # Dump response headers # if dumpResponse: # print >> self.out, "<%s> --- %s Response(%s): ---" % (threading._get_ident(), method, status) # headersdict = dict(response_headers) # for envitem in headersdict.keys(): # print >> self.out, "\t%s:\t'%s'" % (envitem, repr(headersdict[envitem])) # print >> self.out, "\n" # Store response headers environ["wsgidav.response_status"] = status environ["wsgidav.response_headers"] = response_headers return start_response(status, response_headers, exc_info)
def _initConfig(): """Setup configuration dictionary from default, command line and configuration file.""" cmdLineOpts, args = _initCommandLineOptions() # Set config defaults config = DEFAULT_CONFIG.copy() if cmdLineOpts["verbose"] is None: temp_verbose = config["verbose"] else: temp_verbose = cmdLineOpts["verbose"] #_loadSeafileSettings(config) # Command line options if cmdLineOpts.get("port"): config["port"] = cmdLineOpts.get("port") if cmdLineOpts.get("host"): config["host"] = cmdLineOpts.get("host") if cmdLineOpts.get("verbose") is not None: config["verbose"] = cmdLineOpts.get("verbose") log_path = cmdLineOpts.get("log_path", "") if log_path: log_path = os.path.abspath(log_path) config["log_path"] = log_path util.initLogging(config["verbose"], config.get("log_path", ""), config.get("enable_loggers", [])) util.log("Default encoding: %s (file system: %s)" % (sys.getdefaultencoding(), sys.getfilesystemencoding())) _loadSeafileSettings(config) pid_file = cmdLineOpts.get("pid_file", "") if pid_file: pid_file = os.path.abspath(pid_file) config["pid_file"] = pid_file if not config["provider_mapping"]: print >>sys.stderr, "ERROR: No DAV provider defined. Try --help option." sys.exit(-1) return config, args
def _connect(self): opts = self.options if opts.get("url"): self.couch = couchdb.Server(opts.get("url")) else: self.couch = couchdb.Server() dbName = opts.get("dbName", "wsgidav_props") if dbName in self.couch: self.db = self.couch[dbName] util.log("CouchPropertyManager connected to %s v%s" % (self.db, self.couch.version())) else: self.db = self.couch.create(dbName) util.log("CouchPropertyManager created new db %s v%s" % (self.db, self.couch.version())) # Ensure that we have a permanent view if not "_design/properties" in self.db: map = """ function(doc) { if(doc.type == 'properties') { emit(doc.url, { 'id': doc._id, 'url': doc.url }); } } """ designDoc = { "_id": "_design/properties", # "_rev": "42351258", "language": "javascript", "views": { "titles": { "map": "function(doc) { emit(null, { 'id': doc._id, 'title': doc.title }); }" }, # http://127.0.0.1:5984/wsgidav_props/_design/properties/_view/by_url "by_url": { "map": map } } } self.db.save(designDoc)
def __call__(self, environ, start_response): # util.log("SCRIPT_NAME='%s', PATH_INFO='%s'" % ( # environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) path = environ["PATH_INFO"] # (#73) Failed on processing non-iso-8859-1 characters on Python 3 # # Note: we encode using UTF-8 here (falling back to ISO-8859-1)! # This seems to be wrong, since per PEP 3333 PATH_INFO is always ISO-8859-1 encoded # (see https://www.python.org/dev/peps/pep-3333/#unicode-issues). # But also seems to resolve errors when accessing resources with Chinese characters, for # example. # This is done by default for Python 3, but can be turned off in settings. re_encode_path_info = self.config.get("re_encode_path_info") if re_encode_path_info is None: re_encode_path_info = compat.PY3 if re_encode_path_info: b = compat.wsgi_to_bytes(path).decode() path = environ["PATH_INFO"] = b # We optionally unquote PATH_INFO here, although this should already be # done by the server (#8). if self.config.get("unquote_path_info", False): path = compat.unquote(environ["PATH_INFO"]) # GC issue 22: Pylons sends root as u'/' # if isinstance(path, unicode): if not compat.is_native(path): util.log("Got non-native PATH_INFO: %r" % path) # path = path.encode("utf8") path = compat.to_native(path) # Always adding these values to environ: environ["wsgidav.config"] = self.config environ["wsgidav.provider"] = None environ["wsgidav.verbose"] = self._verbose # Find DAV provider that matches the share # sorting share list by reverse length # shareList = self.providerMap.keys() # shareList.sort(key=len, reverse=True) shareList = sorted(self.providerMap.keys(), key=len, reverse=True) share = None for r in shareList: # @@: Case sensitivity should be an option of some sort here; # os.path.normpath might give the preferred case for a filename. if r == "/": share = r break elif path.upper() == r.upper() or path.upper().startswith(r.upper() + "/"): share = r break # Note: we call the next app, even if provider is None, because OPTIONS # must still be handled. # All other requests will result in '404 Not Found' if share is not None: share_data = self.providerMap.get(share) environ["wsgidav.provider"] = share_data['provider'] # TODO: test with multi-level realms: 'aa/bb' # TODO: test security: url contains '..' # Transform SCRIPT_NAME and PATH_INFO # (Since path and share are unquoted, this also fixes quoted values.) if share == "/" or not share: environ["PATH_INFO"] = path else: environ["SCRIPT_NAME"] += share environ["PATH_INFO"] = path[len(share):] # util.log("--> SCRIPT_NAME='%s', PATH_INFO='%s'" % (environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) # assert isinstance(path, str) assert compat.is_native(path) # See http://mail.python.org/pipermail/web-sig/2007-January/002475.html # for some clarification about SCRIPT_NAME/PATH_INFO format # SCRIPT_NAME starts with '/' or is empty assert environ["SCRIPT_NAME"] == "" or environ[ "SCRIPT_NAME"].startswith("/") # SCRIPT_NAME must not have a trailing '/' assert environ["SCRIPT_NAME"] in ( "", "/") or not environ["SCRIPT_NAME"].endswith("/") # PATH_INFO starts with '/' assert environ["PATH_INFO"] == "" or environ[ "PATH_INFO"].startswith("/") start_time = time.time() def _start_response_wrapper(status, response_headers, exc_info=None): # Postprocess response headers headerDict = {} for header, value in response_headers: if header.lower() in headerDict: util.warn("Duplicate header in response: %s" % header) headerDict[header.lower()] = value # Check if we should close the connection after this request. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 forceCloseConnection = False currentContentLength = headerDict.get("content-length") statusCode = int(status.split(" ", 1)[0]) contentLengthRequired = (environ["REQUEST_METHOD"] != "HEAD" and statusCode >= 200 and not statusCode in (204, 304)) # print(environ["REQUEST_METHOD"], statusCode, contentLengthRequired) if contentLengthRequired and currentContentLength in (None, ""): # A typical case: a GET request on a virtual resource, for which # the provider doesn't know the length util.warn( "Missing required Content-Length header in %s-response: closing connection" % statusCode) forceCloseConnection = True elif not type(currentContentLength) is str: util.warn("Invalid Content-Length header in response (%r): closing connection" % headerDict.get("content-length")) forceCloseConnection = True # HOTFIX for Vista and Windows 7 (GC issue 13, issue 23) # It seems that we must read *all* of the request body, otherwise # clients may miss the response. # For example Vista MiniRedir didn't understand a 401 response, # when trying an anonymous PUT of big files. As a consequence, it # doesn't retry with credentials and the file copy fails. # (XP is fine however). util.readAndDiscardInput(environ) # Make sure the socket is not reused, unless we are 100% sure all # current input was consumed if(util.getContentLength(environ) != 0 and not environ.get("wsgidav.all_input_read")): util.warn( "Input stream not completely consumed: closing connection") forceCloseConnection = True if forceCloseConnection and headerDict.get("connection") != "close": util.warn("Adding 'Connection: close' header") response_headers.append(("Connection", "close")) # Log request if self._verbose >= 1: userInfo = environ.get("http_authenticator.username") if not userInfo: userInfo = "(anonymous)" threadInfo = "" if self._verbose >= 1: threadInfo = "<%s> " % threading.currentThread().ident extra = [] if "HTTP_DESTINATION" in environ: extra.append('dest="%s"' % environ.get("HTTP_DESTINATION")) if environ.get("CONTENT_LENGTH", "") != "": extra.append("length=%s" % environ.get("CONTENT_LENGTH")) if "HTTP_DEPTH" in environ: extra.append("depth=%s" % environ.get("HTTP_DEPTH")) if "HTTP_RANGE" in environ: extra.append("range=%s" % environ.get("HTTP_RANGE")) if "HTTP_OVERWRITE" in environ: extra.append("overwrite=%s" % environ.get("HTTP_OVERWRITE")) if self._verbose >= 1 and "HTTP_EXPECT" in environ: extra.append('expect="%s"' % environ.get("HTTP_EXPECT")) if self._verbose >= 2 and "HTTP_CONNECTION" in environ: extra.append('connection="%s"' % environ.get("HTTP_CONNECTION")) if self._verbose >= 2 and "HTTP_USER_AGENT" in environ: extra.append('agent="%s"' % environ.get("HTTP_USER_AGENT")) if self._verbose >= 2 and "HTTP_TRANSFER_ENCODING" in environ: extra.append('transfer-enc=%s' % environ.get("HTTP_TRANSFER_ENCODING")) if self._verbose >= 1: extra.append('elap=%.3fsec' % (time.time() - start_time)) extra = ", ".join(extra) # This is the CherryPy format: # 127.0.0.1 - - [08/Jul/2009:17:25:23] "GET /loginPrompt?redirect=/renderActionList%3Frelation%3Dpersonal%26key%3D%26filter%3DprivateSchedule&reason=0 HTTP/1.1" 200 1944 "http://127.0.0.1:8002/command?id=CMD_Schedule" "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1) Gecko/20090624 Firefox/3.5" # print >>sys.stderr, '%s - %s - [%s] "%s" %s -> %s' % ( print('%s - %s - [%s] "%s" %s -> %s' % ( threadInfo + environ.get("REMOTE_ADDR", ""), userInfo, util.getLogTime(), environ.get("REQUEST_METHOD") + " " + safeReEncode(environ.get("PATH_INFO", ""), sys.stdout.encoding or "ASCII"), extra, status, # response_headers.get(""), # response Content-Length # referer ), file=sys.stdout) return start_response(status, response_headers, exc_info) # Call next middleware app_iter = self._application(environ, _start_response_wrapper) for v in app_iter: yield v if hasattr(app_iter, "close"): app_iter.close() return
def __init__(self, config): self.config = config util.initLogging(config["verbose"], config.get("enable_loggers", [])) util.log("Default encoding: %s (file system: %s)" % (sys.getdefaultencoding(), sys.getfilesystemencoding())) # Evaluate configuration and set defaults _checkConfig(config) provider_mapping = self.config["provider_mapping"] # response_trailer = config.get("response_trailer", "") self._verbose = config.get("verbose", 2) lockStorage = config.get("locksmanager") if lockStorage is True: lockStorage = LockStorageDict() if not lockStorage: locksManager = None else: locksManager = LockManager(lockStorage) propsManager = config.get("propsmanager") if not propsManager: # Normalize False, 0 to None propsManager = None elif propsManager is True: propsManager = PropertyManager() mount_path = config.get("mount_path") # Instantiate DAV resource provider objects for every share self.providerMap = {} for (share, provider) in provider_mapping.items(): # Make sure share starts with, or is, '/' share = "/" + share.strip("/") # We allow a simple string as 'provider'. In this case we interpret # it as a file system root folder that is published. if compat.is_basestring(provider): provider = FilesystemProvider(provider) assert isinstance(provider, DAVProvider) provider.setSharePath(share) if mount_path: provider.setMountPath(mount_path) # TODO: someday we may want to configure different lock/prop # managers per provider provider.setLockManager(locksManager) provider.setPropManager(propsManager) self.providerMap[share] = { "provider": provider, "allow_anonymous": False} # Define WSGI application stack application = RequestResolver() domain_controller = None dir_browser = config.get("dir_browser", {}) middleware_stack = config.get("middleware_stack", []) # Replace WsgiDavDirBrowser to custom class for backward compatibility only # In normal way you should insert it into middleware_stack if dir_browser.get("enable", True) and "app_class" in dir_browser.keys(): config["middleware_stack"] = [m if m != WsgiDavDirBrowser else dir_browser[ 'app_class'] for m in middleware_stack] for mw in middleware_stack: if mw.isSuitable(config): if self._verbose >= 2: print("Middleware %s is suitable" % mw) application = mw(application, config) if issubclass(mw, HTTPAuthenticator): domain_controller = application.getDomainController() # check anonymous access for share, data in self.providerMap.items(): if application.allowAnonymousAccess(share): data['allow_anonymous'] = True else: if self._verbose >= 2: print("Middleware %s is not suitable" % mw) # Print info if self._verbose >= 2: print("Using lock manager: %r" % locksManager) print("Using property manager: %r" % propsManager) print("Using domain controller: %s" % domain_controller) print("Registered DAV providers:") for share, data in self.providerMap.items(): hint = " (anonymous)" if data['allow_anonymous'] else "" print(" Share '%s': %s%s" % (share, provider, hint)) if self._verbose >= 1: for share, data in self.providerMap.items(): if data['allow_anonymous']: # TODO: we should only warn here, if --no-auth is not given print("WARNING: share '%s' will allow anonymous access." % share) self._application = application