예제 #1
0
    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)
예제 #2
0
 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)
예제 #3
0
 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)
예제 #4
0
 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")
예제 #5
0
 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 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)
예제 #8
0
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 _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)
예제 #11
0
    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
예제 #12
0
    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