mirror of https://github.com/snachodog/mybuddy.git
Add forward auth by way of remote user
* Add forward auth related settings * Document forward auth settings * Rearrange code to match preference * Adjust forward auth configuration * Add tests for reverse proxy auth Closes #517 Co-authored-by: Christopher C. Wells <git-2022@chris-wells.net>
This commit is contained in:
parent
2690ab4876
commit
46159850c4
|
@ -1,4 +1,5 @@
|
||||||
import time
|
from os import getenv
|
||||||
|
from time import time
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
@ -6,6 +7,7 @@ from django.conf import settings
|
||||||
from django.utils import timezone, translation
|
from django.utils import timezone, translation
|
||||||
from django.conf.locale.en import formats as formats_en_us
|
from django.conf.locale.en import formats as formats_en_us
|
||||||
from django.conf.locale.en_GB import formats as formats_en_gb
|
from django.conf.locale.en_GB import formats as formats_en_gb
|
||||||
|
from django.contrib.auth.middleware import RemoteUserMiddleware
|
||||||
|
|
||||||
|
|
||||||
def update_en_us_date_formats():
|
def update_en_us_date_formats():
|
||||||
|
@ -134,12 +136,20 @@ class RollingSessionMiddleware:
|
||||||
session_refresh = request.session.get("session_refresh")
|
session_refresh = request.session.get("session_refresh")
|
||||||
if session_refresh:
|
if session_refresh:
|
||||||
try:
|
try:
|
||||||
delta = int(time.time()) - session_refresh
|
delta = int(time()) - session_refresh
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
delta = settings.ROLLING_SESSION_REFRESH + 1
|
delta = settings.ROLLING_SESSION_REFRESH + 1
|
||||||
if delta > settings.ROLLING_SESSION_REFRESH:
|
if delta > settings.ROLLING_SESSION_REFRESH:
|
||||||
request.session["session_refresh"] = int(time.time())
|
request.session["session_refresh"] = int(time())
|
||||||
request.session.set_expiry(settings.SESSION_COOKIE_AGE)
|
request.session.set_expiry(settings.SESSION_COOKIE_AGE)
|
||||||
else:
|
else:
|
||||||
request.session["session_refresh"] = int(time.time())
|
request.session["session_refresh"] = int(time())
|
||||||
return self.get_response(request)
|
return self.get_response(request)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomRemoteUser(RemoteUserMiddleware):
|
||||||
|
"""
|
||||||
|
Middleware used for remote authentication when `REVERSE_PROXY_AUTH` is True.
|
||||||
|
"""
|
||||||
|
|
||||||
|
header = getenv("PROXY_HEADER", "HTTP_REMOTE_USER")
|
||||||
|
|
|
@ -143,6 +143,14 @@ LOGIN_URL = "babybuddy:login"
|
||||||
|
|
||||||
LOGOUT_REDIRECT_URL = "babybuddy:login"
|
LOGOUT_REDIRECT_URL = "babybuddy:login"
|
||||||
|
|
||||||
|
REVERSE_PROXY_AUTH = bool(strtobool(os.environ.get("REVERSE_PROXY_AUTH") or "False"))
|
||||||
|
|
||||||
|
# Use remote user middleware when reverse proxy auth is enabled.
|
||||||
|
if REVERSE_PROXY_AUTH:
|
||||||
|
# Must appear AFTER AuthenticationMiddleware.
|
||||||
|
MIDDLEWARE.append("babybuddy.middleware.CustomRemoteUser")
|
||||||
|
AUTHENTICATION_BACKENDS.append("django.contrib.auth.backends.RemoteUserBackend")
|
||||||
|
|
||||||
|
|
||||||
# Timezone
|
# Timezone
|
||||||
# https://docs.djangoproject.com/en/4.0/topics/i18n/timezones/
|
# https://docs.djangoproject.com/en/4.0/topics/i18n/timezones/
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.test import Client as HttpClient, TestCase, modify_settings
|
||||||
|
|
||||||
|
|
||||||
|
class ReverseProxyAuthTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Notes:
|
||||||
|
- A class method cannot be used to establish the HTTP client because of the
|
||||||
|
settings overrides required for these tests.
|
||||||
|
- Overriding the `REVERSE_PROXY_AUTH` environment variable directly is not
|
||||||
|
possible because environments variables are only evaluated once for the run.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_remote_user_authentication_disabled(self):
|
||||||
|
call_command("migrate", verbosity=0)
|
||||||
|
c = HttpClient()
|
||||||
|
response = c.get("/welcome/", HTTP_REMOTE_USER="admin", follow=True)
|
||||||
|
self.assertRedirects(response, "/login/?next=/welcome/")
|
||||||
|
|
||||||
|
@modify_settings(
|
||||||
|
MIDDLEWARE={"append": "babybuddy.middleware.CustomRemoteUser"},
|
||||||
|
AUTHENTICATION_BACKENDS={
|
||||||
|
"append": "django.contrib.auth.backends.RemoteUserBackend"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def test_remote_user_authentication_enabled(self):
|
||||||
|
call_command("migrate", verbosity=0)
|
||||||
|
c = HttpClient()
|
||||||
|
response = c.get("/welcome/", HTTP_REMOTE_USER="admin")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
|
@ -38,6 +38,34 @@ Each entry must contain both the scheme (http, https) and fully-qualified domain
|
||||||
- [`ALLOWED_HOSTS`](#allowed_hosts)
|
- [`ALLOWED_HOSTS`](#allowed_hosts)
|
||||||
- [`SECURE_PROXY_SSL_HEADER`](#secure_proxy_ssl_header)
|
- [`SECURE_PROXY_SSL_HEADER`](#secure_proxy_ssl_header)
|
||||||
|
|
||||||
|
## `PROXY_HEADER`
|
||||||
|
|
||||||
|
*Default:* `HTTP_REMOTE_USER`
|
||||||
|
|
||||||
|
Sets the header to read the authenticated username from when
|
||||||
|
`REVERSE_PROXY_AUTH` has been enabled.
|
||||||
|
|
||||||
|
**Example value**
|
||||||
|
|
||||||
|
HTTP_X_AUTH_USER
|
||||||
|
|
||||||
|
**See also**
|
||||||
|
|
||||||
|
- [Django's documentation on the `REMOTE_USER` authentication method](https://docs.djangoproject.com/en/4.1/howto/auth-remote-user/)
|
||||||
|
- [`REVERSE_PROXY_AUTH`](#reverse_proxy_auth)
|
||||||
|
|
||||||
|
## `REVERSE_PROXY_AUTH`
|
||||||
|
|
||||||
|
*Default:* `False`
|
||||||
|
|
||||||
|
Enable use of `PROXY_HEADER` to pass the username of an authenticated user.
|
||||||
|
This setting should *only* be used with a properly configured reverse proxy to
|
||||||
|
ensure the headers are not forwarded from sources other than your proxy.
|
||||||
|
|
||||||
|
**See also**
|
||||||
|
|
||||||
|
- [`PROXY_HEADER`](#proxy_header)
|
||||||
|
|
||||||
## `SECRET_KEY`
|
## `SECRET_KEY`
|
||||||
|
|
||||||
*Default:* `None`
|
*Default:* `None`
|
||||||
|
|
Loading…
Reference in New Issue