Merge pull request #1915 from pbiering/support-1914
Support 1914 by adding support for 'http_remote_user'
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 3.5.9.dev
|
## 3.5.9.dev
|
||||||
|
* Extend: [auth] add support for type http_remote_user
|
||||||
|
|
||||||
## 3.5.8
|
## 3.5.8
|
||||||
* Extend [auth]: re-factor & overhaul LDAP authentication, especially for Python's ldap module
|
* Extend: [auth] re-factor & overhaul LDAP authentication, especially for Python's ldap module
|
||||||
* Fix: out-of-range timestamp on 32-bit systems
|
* Fix: out-of-range timestamp on 32-bit systems
|
||||||
* Feature: extend logging with response size in bytes and flag served as plain or gzip
|
* Feature: extend logging with response size in bytes and flag served as plain or gzip
|
||||||
* Feature: [storage] strict_preconditions: new config option to enforce strict preconditions check on PUT in case item already exists [RFC6352#9.2]
|
* Feature: [storage] strict_preconditions: new config option to enforce strict preconditions check on PUT in case item already exists [RFC6352#9.2]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
2
config
2
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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
radicale/auth/http_remote_user.py
Normal file
36
radicale/auth/http_remote_user.py
Normal file
@@ -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", ""), ""
|
||||||
@@ -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("/", """\
|
||||||
|
|||||||
Reference in New Issue
Block a user