Browse Source

add support for http_remote_user

Peter Bieringer 3 months ago
parent
commit
bf8619a41c

+ 8 - 1
DOCUMENTATION.md

@@ -902,8 +902,15 @@ Available types are:
   Requires validation, otherwise clients can supply the header themselves,
   Requires validation, otherwise clients can supply the header themselves,
   which then is unconditionally trusted.
   which then is unconditionally trusted.
 
 
+* `http_remote_user` _(>= 3.5.9)_
+  Takes the username from the Remote-User HTTP header `HTTP_REMOTE_USER` and disables
+  Radicale's internal HTTP authentication. This can be used to provide the
+  username from a reverse proxy which authenticated the client upfront.
+  Requires validation, otherwise clients can supply the header themselves,
+  which then is unconditionally trusted.
+
 * `http_x_remote_user`  
 * `http_x_remote_user`  
-  Takes the username from the `X-Remote-User` HTTP header and disables
+  Takes the username from the X-Remote-User HTTP header `HTTP_X_REMOTE_USER` and disables
   Radicale's internal HTTP authentication. This can be used to provide the
   Radicale's internal HTTP authentication. This can be used to provide the
   username from a reverse proxy which authenticated the client upfront.
   username from a reverse proxy which authenticated the client upfront.
   Requires validation, otherwise clients can supply the header themselves,
   Requires validation, otherwise clients can supply the header themselves,

+ 1 - 1
config

@@ -63,7 +63,7 @@
 [auth]
 [auth]
 
 
 # Authentication method
 # Authentication method
-# Value: none | htpasswd | remote_user | http_x_remote_user | dovecot | ldap | oauth2 | pam | denyall
+# Value: none | htpasswd | remote_user | http_remote_user | http_x_remote_user | dovecot | ldap | oauth2 | pam | denyall
 #type = denyall
 #type = denyall
 
 
 # Cache logins for until expiration time
 # Cache logins for until expiration time

+ 1 - 1
radicale/app/__init__.py

@@ -275,7 +275,7 @@ class Application(ApplicationPartDelete, ApplicationPartHead,
                 logger.debug("Called by reverse proxy, remove base prefix %r from path: %r => %r", base_prefix, path, path_new)
                 logger.debug("Called by reverse proxy, remove base prefix %r from path: %r => %r", base_prefix, path, path_new)
                 path = path_new
                 path = path_new
             else:
             else:
-                if self._auth_type in ['remote_user', 'http_x_remote_user'] and self._web_type == 'internal':
+                if self._auth_type in ['remote_user', 'http_remote_user', 'http_x_remote_user'] and self._web_type == 'internal':
                     logger.warning("Called by reverse proxy, cannot remove base prefix %r from path: %r as not matching (may cause authentication issues using internal WebUI)", base_prefix, path)
                     logger.warning("Called by reverse proxy, cannot remove base prefix %r from path: %r as not matching (may cause authentication issues using internal WebUI)", base_prefix, path)
                 else:
                 else:
                     logger.debug("Called by reverse proxy, cannot remove base prefix %r from path: %r as not matching", base_prefix, path)
                     logger.debug("Called by reverse proxy, cannot remove base prefix %r from path: %r as not matching", base_prefix, path)

+ 3 - 1
radicale/auth/__init__.py

@@ -23,7 +23,7 @@ Authentication module.
 
 
 Authentication is based on usernames and passwords. If something more
 Authentication is based on usernames and passwords. If something more
 advanced is needed an external WSGI server or reverse proxy can be used
 advanced is needed an external WSGI server or reverse proxy can be used
-(see ``remote_user`` or ``http_x_remote_user`` backend).
+(see ``remote_user``, ``http_remote_user`` or ``http_x_remote_user`` backend).
 
 
 Take a look at the class ``BaseAuth`` if you want to implement your own.
 Take a look at the class ``BaseAuth`` if you want to implement your own.
 
 
@@ -40,6 +40,7 @@ from radicale import config, types, utils
 from radicale.log import logger
 from radicale.log import logger
 
 
 INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user",
 INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user",
+                                 "http_remote_user",
                                  "denyall",
                                  "denyall",
                                  "htpasswd",
                                  "htpasswd",
                                  "ldap",
                                  "ldap",
@@ -59,6 +60,7 @@ CACHE_LOGIN_TYPES: Sequence[str] = (
 
 
 INSECURE_IF_NO_LOOPBACK_TYPES: Sequence[str] = (
 INSECURE_IF_NO_LOOPBACK_TYPES: Sequence[str] = (
                                     "remote_user",
                                     "remote_user",
+                                    "http_remote_user",
                                     "http_x_remote_user",
                                     "http_x_remote_user",
                                    )
                                    )
 
 

+ 36 - 0
radicale/auth/http_remote_user.py

@@ -0,0 +1,36 @@
+# This file is part of Radicale - CalDAV and CardDAV server
+# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Authentication backend that takes the username from the
+``HTTP_REMOTE_USER`` header.
+
+It's intended for use with a reverse proxy. Be aware as this will be insecure
+if the reverse proxy is not configured properly.
+
+"""
+
+from typing import Tuple, Union
+
+from radicale import types
+from radicale.auth import none
+
+
+class Auth(none.Auth):
+
+    def get_external_login(self, environ: types.WSGIEnviron) -> Union[
+            Tuple[()], Tuple[str, str]]:
+        return environ.get("HTTP_REMOTE_USER", ""), ""

+ 17 - 0
radicale/tests/test_auth.py

@@ -263,6 +263,23 @@ class TestBaseAuthRequests(BaseTest):
         href_element = prop.find(xmlutils.make_clark("D:href"))
         href_element = prop.find(xmlutils.make_clark("D:href"))
         assert href_element is not None and href_element.text == "/test/"
         assert href_element is not None and href_element.text == "/test/"
 
 
+    def test_http_remote_user(self) -> None:
+        self.configure({"auth": {"type": "http_remote_user"}})
+        _, responses = self.propfind("/", """\
+<?xml version="1.0" encoding="utf-8"?>
+<propfind xmlns="DAV:">
+    <prop>
+        <current-user-principal />
+    </prop>
+</propfind>""", HTTP_REMOTE_USER="test")
+        assert responses is not None
+        response = responses["/"]
+        assert not isinstance(response, int)
+        status, prop = response["D:current-user-principal"]
+        assert status == 200
+        href_element = prop.find(xmlutils.make_clark("D:href"))
+        assert href_element is not None and href_element.text == "/test/"
+
     def test_http_x_remote_user(self) -> None:
     def test_http_x_remote_user(self) -> None:
         self.configure({"auth": {"type": "http_x_remote_user"}})
         self.configure({"auth": {"type": "http_x_remote_user"}})
         _, responses = self.propfind("/", """\
         _, responses = self.propfind("/", """\