Example #1
0
    def __call__(self, environ, start_response):
        path = environ["PATH_INFO"]

        dav_res = None
        if environ["wsgidav.provider"]:
            dav_res = environ["wsgidav.provider"].get_resource_inst(path, environ)

        if (
            environ["REQUEST_METHOD"] in ("GET", "HEAD")
            and dav_res
            and dav_res.is_collection
        ):

            if util.get_content_length(environ) != 0:
                self._fail(
                    HTTP_MEDIATYPE_NOT_SUPPORTED,
                    "The server does not handle any body content.",
                )

            if environ["REQUEST_METHOD"] == "HEAD":
                return util.send_status_response(
                    environ, start_response, HTTP_OK, is_head=True
                )

            # Support DAV mount (http://www.ietf.org/rfc/rfc4709.txt)
            if self.dir_config.get("davmount") and "davmount" in environ.get(
                "QUERY_STRING", ""
            ):
                collectionUrl = util.make_complete_url(environ)
                collectionUrl = collectionUrl.split("?", 1)[0]
                res = compat.to_bytes(DAVMOUNT_TEMPLATE.format(collectionUrl))
                # TODO: support <dm:open>%s</dm:open>

                start_response(
                    "200 OK",
                    [
                        ("Content-Type", "application/davmount+xml"),
                        ("Content-Length", str(len(res))),
                        ("Cache-Control", "private"),
                        ("Date", util.get_rfc1123_time()),
                    ],
                )
                return [res]

            context = self._get_context(environ, dav_res)

            res = self.template.render(**context)
            res = compat.to_bytes(res)
            start_response(
                "200 OK",
                [
                    ("Content-Type", "text/html"),
                    ("Content-Length", str(len(res))),
                    ("Cache-Control", "private"),
                    ("Date", util.get_rfc1123_time()),
                ],
            )
            return [res]

        return self.next_app(environ, start_response)
Example #2
0
    def __call__(self, environ, start_response):
        path = environ["PATH_INFO"]

        dav_res = None
        if environ["wsgidav.provider"]:
            dav_res = environ["wsgidav.provider"].get_resource_inst(path, environ)

        if (
            environ["REQUEST_METHOD"] in ("GET", "HEAD")
            and dav_res
            and dav_res.is_collection
        ):

            if util.get_content_length(environ) != 0:
                self._fail(
                    HTTP_MEDIATYPE_NOT_SUPPORTED,
                    "The server does not handle any body content.",
                )

            if environ["REQUEST_METHOD"] == "HEAD":
                return util.send_status_response(
                    environ, start_response, HTTP_OK, is_head=True
                )

            # Support DAV mount (http://www.ietf.org/rfc/rfc4709.txt)
            if self.dir_config.get("davmount") and "davmount" in environ.get(
                "QUERY_STRING", ""
            ):
                collectionUrl = util.make_complete_url(environ)
                collectionUrl = collectionUrl.split("?", 1)[0]
                res = compat.to_bytes(DAVMOUNT_TEMPLATE.format(collectionUrl))
                # TODO: support <dm:open>%s</dm:open>

                start_response(
                    "200 OK",
                    [
                        ("Content-Type", "application/davmount+xml"),
                        ("Content-Length", str(len(res))),
                        ("Cache-Control", "private"),
                        ("Date", util.get_rfc1123_time()),
                    ],
                )
                return [res]

            context = self._get_context(environ, dav_res)

            res = self.template.render(**context)
            res = compat.to_bytes(res)
            start_response(
                "200 OK",
                [
                    ("Content-Type", "text/html"),
                    ("Content-Length", str(len(res))),
                    ("Cache-Control", "private"),
                    ("Date", util.get_rfc1123_time()),
                ],
            )
            return [res]

        return self.next_app(environ, start_response)
Example #3
0
    def send_digest_auth_response(self, environ, start_response):
        realm_name = self.domain_controller.get_domain_realm(
            environ["PATH_INFO"], environ)
        random.seed()
        serverkey = hex(random.getrandbits(32))[2:]
        etagkey = calc_hexdigest(environ["PATH_INFO"])
        timekey = str(time.time())
        nonce_source = timekey + calc_hexdigest(timekey + ":" + etagkey + ":" +
                                                serverkey)
        nonce = calc_base64(nonce_source)
        wwwauthheaders = 'Digest realm="{}", nonce="{}", algorithm=MD5, qop="auth"'.format(
            realm_name, nonce)

        _logger.debug("401 Not Authorized for realm '{}' (digest): {}".format(
            realm_name, wwwauthheaders))

        body = compat.to_bytes(self.get_error_message())
        start_response(
            "401 Not Authorized",
            [
                ("WWW-Authenticate", wwwauthheaders),
                ("Content-Type", "text/html"),
                ("Content-Length", str(len(body))),
                ("Date", util.get_rfc1123_time()),
            ],
        )
        return [body]
Example #4
0
    def send_digest_auth_response(self, environ, start_response):
        realm = self.domain_controller.get_domain_realm(environ["PATH_INFO"], environ)
        random.seed()
        serverkey = hex(random.getrandbits(32))[2:]
        etagkey = calc_hexdigest(environ["PATH_INFO"])
        timekey = str(time.time())
        nonce_source = timekey + calc_hexdigest(
            timekey + ":" + etagkey + ":" + serverkey
        )
        nonce = calc_base64(nonce_source)
        wwwauthheaders = 'Digest realm="{}", nonce="{}", algorithm=MD5, qop="auth"'.format(
            realm, nonce
        )

        _logger.debug(
            "401 Not Authorized for realm '{}' (digest): {}".format(
                realm, wwwauthheaders
            )
        )

        body = compat.to_bytes(self.error_message_401)
        start_response(
            "401 Not Authorized",
            [
                ("WWW-Authenticate", wwwauthheaders),
                ("Content-Type", "text/html"),
                ("Content-Length", str(len(body))),
                ("Date", util.get_rfc1123_time()),
            ],
        )
        return [body]
Example #5
0
    def __call__(self, environ, start_response):
        path = environ["PATH_INFO"]

        # We want to answer OPTIONS(*), even if no handler was registered for
        # the top-level realm (e.g. required to map drive letters).

        provider = environ["wsgidav.provider"]

        # Hotfix for WinXP / Vista: accept '/' for a '*'
        if environ["REQUEST_METHOD"] == "OPTIONS" and path in ("/", "*"):
            # Answer HTTP 'OPTIONS' method on server-level.
            # From RFC 2616:
            # If the Request-URI is an asterisk ("*"), the OPTIONS request is
            # intended to apply to the server in general rather than to a specific
            # resource. Since a server's communication options typically depend on
            # the resource, the "*" request is only useful as a "ping" or "no-op"
            # type of method; it does nothing beyond allowing the client to test the
            # capabilities of the server. For example, this can be used to test a
            # proxy for HTTP/1.1 compliance (or lack thereof).

            dav_compliance_level = "1,2"

            if (
                provider is None
                or provider.is_readonly()
                or provider.lock_manager is None
            ):
                dav_compliance_level = "1"

            headers = [
                ("Content-Type", "text/html"),
                ("Content-Length", "0"),
                ("DAV", dav_compliance_level),
                ("Date", util.get_rfc1123_time()),
            ]

            if environ["wsgidav.config"].get("add_header_MS_Author_Via", False):
                headers.append(("MS-Author-Via", "DAV"))

            start_response("200 OK", headers)
            yield b""
            return

        if provider is None:
            raise DAVError(
                HTTP_NOT_FOUND, "Could not find resource provider for '{}'".format(path)
            )

        # Let the appropriate resource provider for the realm handle the
        # request
        app = RequestServer(provider)
        app_iter = app(environ, start_response)
        for v in app_iter:
            yield v
        if hasattr(app_iter, "close"):
            app_iter.close()
        return
Example #6
0
    def __call__(self, environ, start_response):
        path = environ["PATH_INFO"]

        # We want to answer OPTIONS(*), even if no handler was registered for
        # the top-level realm (e.g. required to map drive letters).

        provider = environ["wsgidav.provider"]

        # Hotfix for WinXP / Vista: accept '/' for a '*'
        if environ["REQUEST_METHOD"] == "OPTIONS" and path in ("/", "*"):
            # Answer HTTP 'OPTIONS' method on server-level.
            # From RFC 2616:
            # If the Request-URI is an asterisk ("*"), the OPTIONS request is
            # intended to apply to the server in general rather than to a specific
            # resource. Since a server's communication options typically depend on
            # the resource, the "*" request is only useful as a "ping" or "no-op"
            # type of method; it does nothing beyond allowing the client to test the
            # capabilities of the server. For example, this can be used to test a
            # proxy for HTTP/1.1 compliance (or lack thereof).

            dav_compliance_level = "1,2"

            if (
                provider is None
                or provider.is_readonly()
                or provider.lock_manager is None
            ):
                dav_compliance_level = "1"

            headers = [
                ("Content-Type", "text/html"),
                ("Content-Length", "0"),
                ("DAV", dav_compliance_level),
                ("Date", util.get_rfc1123_time()),
            ]

            if environ["wsgidav.config"].get("add_header_MS_Author_Via", False):
                headers.append(("MS-Author-Via", "DAV"))

            start_response("200 OK", headers)
            yield b""
            return

        if provider is None:
            raise DAVError(
                HTTP_NOT_FOUND, "Could not find resource provider for '{}'".format(path)
            )

        # Let the appropriate resource provider for the realm handle the
        # request
        app = RequestServer(provider)
        app_iter = app(environ, start_response)
        for v in app_iter:
            yield v
        if hasattr(app_iter, "close"):
            app_iter.close()
        return
Example #7
0
    def send_basic_auth_response(self, environ, start_response):
        realmname = self._domaincontroller.get_domain_realm(
            environ["PATH_INFO"], environ)
        _logger.debug(
            "401 Not Authorized for realm '{}' (basic)".format(realmname))
        wwwauthheaders = "Basic realm=\"" + realmname + "\""

        body = compat.to_bytes(self.get_error_message())
        start_response("401 Not Authorized", [
            ("WWW-Authenticate", wwwauthheaders),
            ("Content-Type", "text/html"),
            ("Content-Length", str(len(body))),
            ("Date", util.get_rfc1123_time()),
        ])
        return [body]
Example #8
0
    def send_basic_auth_response(self, environ, start_response):
        realm = self.domain_controller.get_domain_realm(environ["PATH_INFO"], environ)
        _logger.debug("401 Not Authorized for realm '{}' (basic)".format(realm))
        wwwauthheaders = 'Basic realm="' + realm + '"'

        body = compat.to_bytes(self.error_message_401)
        start_response(
            "401 Not Authorized",
            [
                ("WWW-Authenticate", wwwauthheaders),
                ("Content-Type", "text/html"),
                ("Content-Length", str(len(body))),
                ("Date", util.get_rfc1123_time()),
            ],
        )
        return [body]
Example #9
0
    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
Example #10
0
    def __call__(self, environ, start_response):
        realm_name = self.domain_controller.get_domain_realm(
            environ["PATH_INFO"], environ)

        _logger.debug("realm '{}'".format(realm_name))
        # _logger.debug("{}".format(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.domain_controller.require_authentication(
                realm_name, environ):
            # no authentication needed
            _logger.debug(
                "No authorization required for realm '{}'".format(realm_name))
            environ["http_authenticator.realm"] = realm_name
            environ["http_authenticator.user_name"] = ""
            return self.next_app(environ, start_response)

        if self._trusted_auth_header and environ.get(
                self._trusted_auth_header):
            # accept a user_name that was injected by a trusted upstream server
            _logger.debug(
                "Accept trusted user_name {}='{}'for realm '{}'".format(
                    self._trusted_auth_header,
                    environ.get(self._trusted_auth_header),
                    realm_name,
                ))
            environ["http_authenticator.realm"] = realm_name
            environ["http_authenticator.user_name"] = environ.get(
                self._trusted_auth_header)
            return self.next_app(environ, start_response)

        if "HTTP_AUTHORIZATION" in environ:
            authheader = environ["HTTP_AUTHORIZATION"]
            authmatch = self._header_method.search(authheader)
            authmethod = "None"
            if authmatch:
                authmethod = authmatch.group(1).lower()

            if authmethod == "digest" and self._accept_digest:
                return self.auth_digest_auth_request(environ, start_response)
            elif authmethod == "digest" and self._accept_basic:
                return self.send_basic_auth_response(environ, start_response)
            elif authmethod == "basic" and self._accept_basic:
                return self.auth_basic_auth_request(environ, start_response)

            # The requested auth method is not supported.
            elif self._default_digest and self._accept_digest:
                return self.send_digest_auth_response(environ, start_response)
            elif self._accept_basic:
                return self.send_basic_auth_response(environ, start_response)

            _logger.warn(
                "HTTPAuthenticator: respond with 400 Bad request; Auth-Method: {}"
                .format(authmethod))

            start_response(
                "400 Bad Request",
                [("Content-Length", "0"), ("Date", util.get_rfc1123_time())],
            )
            return [""]

        if self._default_digest:
            return self.send_digest_auth_response(environ, start_response)
        return self.send_basic_auth_response(environ, start_response)
Example #11
0
    def __call__(self, environ, start_response):
        realm = self.domain_controller.get_domain_realm(environ["PATH_INFO"], environ)

        environ["wsgidav.auth.realm"] = realm
        environ["wsgidav.auth.user_name"] = ""
        # The domain controller MAY set those values depending on user's
        # authorization:
        environ["wsgidav.auth.roles"] = None
        environ["wsgidav.auth.permissions"] = None

        # _logger.debug(
        #     "HTTPAuthenticator realm({}): '{}'".format(environ["PATH_INFO"], realm)
        # )
        # _logger.debug("{}".format(environ))

        force_logout = False
        if "logout" in environ.get("QUERY_STRING", ""):
            force_logout = True
            _logger.warning("Force logout")

        force_allow = False
        if self.win_accept_anonymous_options and environ["REQUEST_METHOD"] == "OPTIONS":
            _logger.warning("No authorization required for OPTIONS method")
            force_allow = True

        if force_allow or not self.domain_controller.require_authentication(
            realm, environ
        ):
            # No authentication needed
            # _logger.debug("No authorization required for realm '{}'".format(realm))
            # environ["wsgidav.auth.realm"] = realm
            # environ["wsgidav.auth.user_name"] = ""
            return self.next_app(environ, start_response)

        if self.trusted_auth_header and environ.get(self.trusted_auth_header):
            # accept a user_name that was injected by a trusted upstream server
            _logger.debug(
                "Accept trusted user_name {}='{}'for realm '{}'".format(
                    self.trusted_auth_header,
                    environ.get(self.trusted_auth_header),
                    realm,
                )
            )
            # environ["wsgidav.auth.realm"] = realm
            environ["wsgidav.auth.user_name"] = environ.get(self.trusted_auth_header)
            return self.next_app(environ, start_response)

        if "HTTP_AUTHORIZATION" in environ and not force_logout:
            auth_header = environ["HTTP_AUTHORIZATION"]
            auth_match = self._header_method.search(auth_header)
            auth_method = "None"
            if auth_match:
                auth_method = auth_match.group(1).lower()

            if auth_method == "digest" and self.accept_digest:
                return self.handle_digest_auth_request(environ, start_response)
            elif auth_method == "digest" and self.accept_basic:
                return self.send_basic_auth_response(environ, start_response)
            elif auth_method == "basic" and self.accept_basic:
                return self.handle_basic_auth_request(environ, start_response)

            # The requested auth method is not supported.
            elif self.default_to_digest and self.accept_digest:
                return self.send_digest_auth_response(environ, start_response)
            elif self.accept_basic:
                return self.send_basic_auth_response(environ, start_response)

            _logger.warning(
                "HTTPAuthenticator: respond with 400 Bad request; Auth-Method: {}".format(
                    auth_method
                )
            )

            start_response(
                "400 Bad Request",
                [("Content-Length", "0"), ("Date", util.get_rfc1123_time())],
            )
            return [""]

        if self.default_to_digest:
            return self.send_digest_auth_response(environ, start_response)
        return self.send_basic_auth_response(environ, start_response)
Example #12
0
    def __call__(self, environ, start_response):
        # Intercept start_response
        sub_app_start_response = util.SubAppStartResponse()

        try:
            try:
                # request_server app may be a generator (for example the GET handler)
                # So we must iterate - not return self.next_app(..)!
                # Otherwise the we could not catch exceptions here.
                response_started = False
                app_iter = self.next_app(environ, sub_app_start_response)
                for v in app_iter:
                    # Start response (the first time)
                    if not response_started:
                        # Success!
                        start_response(
                            sub_app_start_response.status,
                            sub_app_start_response.response_headers,
                            sub_app_start_response.exc_info,
                        )
                    response_started = True

                    yield v

                # Close out iterator
                if hasattr(app_iter, "close"):
                    app_iter.close()

                # Start response (if it hasn't been done yet)
                if not response_started:
                    # Success!
                    start_response(
                        sub_app_start_response.status,
                        sub_app_start_response.response_headers,
                        sub_app_start_response.exc_info,
                    )

                return
            except DAVError as e:
                _logger.debug("re-raising {}".format(e))
                raise
            except Exception as e:
                # Caught a non-DAVError
                if self._catch_all_exceptions:
                    # Catch all exceptions to return as 500 Internal Error
                    # traceback.print_exc(10, environ.get("wsgi.errors") or sys.stderr)
                    _logger.error("{}".format(traceback.format_exc(10)))
                    raise as_DAVError(e)
                else:
                    _logger.error("Caught Exception\n{}".format(
                        traceback.format_exc(10)))
                    # traceback.print_exc(10, sys.stderr)
                    raise
        except DAVError as e:
            _logger.debug("caught {}".format(e))

            status = get_http_status_string(e)
            # Dump internal errors to console
            if e.value == HTTP_INTERNAL_ERROR:
                tb = traceback.format_exc(10)
                _logger.error(
                    "Caught HTTPRequestException(HTTP_INTERNAL_ERROR)\n{}".
                    format(tb))
                # traceback.print_exc(10, environ.get("wsgi.errors") or sys.stdout)
                _logger.error("e.srcexception:\n{}".format(e.srcexception))
            elif e.value in (HTTP_NOT_MODIFIED, HTTP_NO_CONTENT):
                # _logger.warn("Forcing empty error response for {}".format(e.value))
                # See paste.lint: these code don't have content
                start_response(status, [("Content-Length", "0"),
                                        ("Date", util.get_rfc1123_time())])
                yield b""
                return

            # If exception has pre-/post-condition: return as XML response,
            # else return as HTML
            content_type, body = e.get_response_page()

            # TODO: provide exc_info=sys.exc_info()?
            start_response(
                status,
                [
                    ("Content-Type", content_type),
                    ("Content-Length", str(len(body))),
                    ("Date", util.get_rfc1123_time()),
                ],
            )
            yield body
            return
Example #13
0
    def __call__(self, environ, start_response):
        # Intercept start_response
        sub_app_start_response = util.SubAppStartResponse()

        try:
            try:
                # request_server app may be a generator (for example the GET handler)
                # So we must iterate - not return self.next_app(..)!
                # Otherwise the we could not catch exceptions here.
                response_started = False
                app_iter = self.next_app(environ, sub_app_start_response)
                for v in app_iter:
                    # Start response (the first time)
                    if not response_started:
                        # Success!
                        start_response(
                            sub_app_start_response.status,
                            sub_app_start_response.response_headers,
                            sub_app_start_response.exc_info,
                        )
                    response_started = True

                    yield v

                # Close out iterator
                if hasattr(app_iter, "close"):
                    app_iter.close()

                # Start response (if it hasn't been done yet)
                if not response_started:
                    # Success!
                    start_response(
                        sub_app_start_response.status,
                        sub_app_start_response.response_headers,
                        sub_app_start_response.exc_info,
                    )

                return
            except DAVError as e:
                _logger.debug("re-raising {}".format(e))
                raise
            except Exception as e:
                # Caught a non-DAVError
                if self.catch_all_exceptions:
                    # Catch all exceptions to return as 500 Internal Error
                    # traceback.print_exc(10, environ.get("wsgi.errors") or sys.stderr)
                    _logger.error("{}".format(traceback.format_exc(10)))
                    raise as_DAVError(e)
                else:
                    _logger.error(
                        "Caught Exception\n{}".format(traceback.format_exc(10))
                    )
                    # traceback.print_exc(10, sys.stderr)
                    raise
        except DAVError as e:
            _logger.debug("caught {}".format(e))

            status = get_http_status_string(e)
            # Dump internal errors to console
            if e.value == HTTP_INTERNAL_ERROR:
                tb = traceback.format_exc(10)
                _logger.error(
                    "Caught HTTPRequestException(HTTP_INTERNAL_ERROR)\n{}".format(tb)
                )
                # traceback.print_exc(10, environ.get("wsgi.errors") or sys.stdout)
                _logger.error("e.src_exception:\n{}".format(e.src_exception))
            elif e.value in (HTTP_NOT_MODIFIED, HTTP_NO_CONTENT):
                # _logger.warning("Forcing empty error response for {}".format(e.value))
                # See paste.lint: these code don't have content
                start_response(
                    status, [("Content-Length", "0"), ("Date", util.get_rfc1123_time())]
                )
                yield b""
                return

            # If exception has pre-/post-condition: return as XML response,
            # else return as HTML
            content_type, body = e.get_response_page()

            # TODO: provide exc_info=sys.exc_info()?
            start_response(
                status,
                [
                    ("Content-Type", content_type),
                    ("Content-Length", str(len(body))),
                    ("Date", util.get_rfc1123_time()),
                ],
            )
            yield body
            return
Example #14
0
    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
Example #15
0
    def __call__(self, environ, start_response):
        realm = self.domain_controller.get_domain_realm(environ["PATH_INFO"], environ)

        environ["wsgidav.auth.realm"] = realm
        environ["wsgidav.auth.user_name"] = ""
        # The domain controller MAY set those values depending on user's
        # authorization:
        environ["wsgidav.auth.roles"] = None
        environ["wsgidav.auth.permissions"] = None

        # _logger.debug(
        #     "HTTPAuthenticator realm({}): '{}'".format(environ["PATH_INFO"], realm)
        # )
        # _logger.debug("{}".format(environ))

        force_logout = False
        if "logout" in environ.get("QUERY_STRING", ""):
            force_logout = True
            _logger.warning("Force logout")

        force_allow = False
        if self.win_accept_anonymous_options and environ["REQUEST_METHOD"] == "OPTIONS":
            _logger.warning("No authorization required for OPTIONS method")
            force_allow = True

        if force_allow or not self.domain_controller.require_authentication(
            realm, environ
        ):
            # No authentication needed
            # _logger.debug("No authorization required for realm '{}'".format(realm))
            # environ["wsgidav.auth.realm"] = realm
            # environ["wsgidav.auth.user_name"] = ""
            return self.next_app(environ, start_response)

        if self.trusted_auth_header and environ.get(self.trusted_auth_header):
            # accept a user_name that was injected by a trusted upstream server
            _logger.debug(
                "Accept trusted user_name {}='{}'for realm '{}'".format(
                    self.trusted_auth_header,
                    environ.get(self.trusted_auth_header),
                    realm,
                )
            )
            # environ["wsgidav.auth.realm"] = realm
            environ["wsgidav.auth.user_name"] = environ.get(self.trusted_auth_header)
            return self.next_app(environ, start_response)

        if "HTTP_AUTHORIZATION" in environ and not force_logout:
            auth_header = environ["HTTP_AUTHORIZATION"]
            auth_match = self._header_method.search(auth_header)
            auth_method = "None"
            if auth_match:
                auth_method = auth_match.group(1).lower()

            if auth_method == "digest" and self.accept_digest:
                return self.handle_digest_auth_request(environ, start_response)
            elif auth_method == "digest" and self.accept_basic:
                return self.send_basic_auth_response(environ, start_response)
            elif auth_method == "basic" and self.accept_basic:
                return self.handle_basic_auth_request(environ, start_response)

            # The requested auth method is not supported.
            elif self.default_to_digest and self.accept_digest:
                return self.send_digest_auth_response(environ, start_response)
            elif self.accept_basic:
                return self.send_basic_auth_response(environ, start_response)

            _logger.warning(
                "HTTPAuthenticator: respond with 400 Bad request; Auth-Method: {}".format(
                    auth_method
                )
            )

            start_response(
                "400 Bad Request",
                [("Content-Length", "0"), ("Date", util.get_rfc1123_time())],
            )
            return [""]

        if self.default_to_digest:
            return self.send_digest_auth_response(environ, start_response)
        return self.send_basic_auth_response(environ, start_response)
Example #16
0
    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