Commit graph

31 commits

Author SHA1 Message Date
de3655ba0b Serve static files with WhiteNoise
The container runs only gunicorn, with no web server in front of it
inside the pod. WhiteNoise serves the collectstatic output
(STATIC_ROOT, configurable) with gzip compression. It uses the
non-hashed storage because the vendor CSS references source maps that
are not shipped. All 20 static files on the landing page are served.

Adds gunicorn and whitenoise to the requirements.
2026-11-06 15:35:33 +03:00
4449a31d3d Add /healthz and /readyz endpoints for Kubernetes probes
They are served by the first middleware, so probes, which use the pod
IP as Host, are not rejected by ALLOWED_HOSTS or redirected to HTTPS.
/healthz only shows the process is serving, so an LDAP outage never
restarts pods. /readyz returns 503 unless the database answers and an
LDAP server accepts the admin bind.
2026-11-06 15:31:38 +03:00
5ac62ec2db Support PostgreSQL configured from the environment
Sessions, password reset tokens and pending registrations live in the
database, so running more than one replica needs a shared one. With
DATABASE_HOST set the app uses PostgreSQL (psycopg 3 binary wheels, no
system libraries) with persistent, health-checked connections.
Otherwise SQLite is used, at a path configurable with SQLITE_PATH so a
single replica can keep it on a volume.

docker-compose.dev.yml gains a PostgreSQL 18 service on
localhost:5433. The full test suite passes on SQLite and PostgreSQL.
2026-11-06 15:31:01 +03:00
d433708f86 Add production HTTPS and proxy settings
- Session and CSRF cookies are secure-only unless DEBUG is on.
- TRUST_X_FORWARDED_PROTO honours X-Forwarded-Proto from a TLS
  terminating ingress. Without it, Django 4+ fails the CSRF origin
  check on every form POST.
- CSRF_TRUSTED_ORIGINS, SECURE_SSL_REDIRECT and SECURE_HSTS_SECONDS are
  configurable and off by default. HSTS for subdomains and preload are
  left to ungleich, since they affect all of *.ungleich.ch.
2026-11-06 15:27:27 +03:00
122bc387d8 Time out requests to the OTP server
None of the calls to the OTP server set a timeout, so a hanging OTP
server blocked a web worker indefinitely. Each call now times out after
OTP_TIMEOUT seconds (default 10).
2026-11-06 15:26:48 +03:00
f8567b2a41 Read all configuration in settings and document it in env.sample
The OTP server variables were read straight from the environment in
views.py. They are now OTP_* settings next to everything else, under
the same environment variable names, so existing deployments are
unaffected.

Remove LDAP_SERVER and LDAP_USERS_DN, which nothing used.
env.sample moves to the repository root (where .env is read from) and
lists every variable settings.py reads, including defaults and which
values are secrets.
2026-11-06 15:26:26 +03:00
38eec4d5c7 Log to stdout instead of rotating files
Containers should log to stdout. The RotatingFileHandler wrote to
logs/debug.log, which is lost with the pod and crashed startup when
the directory was missing.

All logging now goes to a console handler. LOG_LEVEL sets the root
level (default INFO), DJANGO_LOG_LEVEL sets django.* (default INFO),
and LDAP3_LOG=1 turns on ldap3 protocol logging. ENABLE_DEBUG_LOG and
MODULES_TO_LOG are gone.
2026-11-06 15:25:21 +03:00
30703f9fa5 Configure email through Django 6.1 MAILERS
EMAIL_HOST, EMAIL_PORT and EMAIL_USE_TLS are deprecated in 6.1 in favour
of the MAILERS setting. The same environment variables now feed the
default SMTP mailer, so existing deployments keep working. Also adds
optional EMAIL_HOST_USER, EMAIL_HOST_PASSWORD and EMAIL_TIMEOUT
(default 10s; previously no timeout).
2026-11-06 15:23:52 +03:00
187dd8d6cc Support multiple LDAP servers with failover
LDAPSERVER was documented as a space separated list, but the whole
string went into a single ldap3.Server. Logins worked (python-ldap
accepts lists), while registration and every write would have failed
with more than one URI.

settings.LDAP_SERVERS is now the split list, used through one ldap3
ServerPool per process: requests go to the first reachable server, a
server that is down is skipped for 60s, connections time out after 5s,
and with all servers down the pool gives up after two passes instead
of retrying forever.
2026-11-06 15:21:45 +03:00
ab45535f38 Replace django-auth-ldap and python-ldap with an ldap3 backend
python-ldap is a C extension that needs the OpenLDAP headers to build
(and needed a workaround in the Dockerfile). ldap3, which the app
already used for all writes, is pure Python.

dal.backends.LdapBackend keeps the django-auth-ldap behaviour: search
the uid in every LDAPSEARCH base as the admin DN, require exactly one
match, log in with the user's DN, and mirror givenName, sn and mail into
the local Django user. Empty passwords are rejected (LDAP treats them
as anonymous logins, which succeed).

LDAP_USE_TLS now applies StartTLS to every connection, including the
admin connection used for writes, not just logins.
2026-11-06 15:15:03 +03:00
bf7c6bf239 Remove APIs that Django 5.0 and 5.1 drop
Remove USE_L10N; localized formatting is always on from 5.0. Replace
User.objects.make_random_password() (removed in 5.1) with a
secrets-based generator for the create API's initial password, using
the same unambiguous alphabet and 16 characters instead of 10.
2026-11-06 15:10:43 +03:00
3ceff001bb Upgrade to Django 3.2 LTS
Step 1 of the upgrade (Python 3.9). Dependencies are bumped to their
newest versions compatible with Django 3.2.

DEFAULT_AUTO_FIELD is set to AutoField, which matches the existing
tables, so no migration is needed and the W042 warnings go away.
2026-11-06 15:07:37 +03:00
0cb0344ac8 Compute the next uidNumber from LDAP instead of a local file
The highest uidNumber was cached in dal/ldap_max_uid_file. That breaks
with more than one replica and is lost on every container restart. The
uniqueness check behind it searched base '' (the root DSE), which
OpenLDAP never matches, so nothing caught a stale file. The error path
also referenced a setting that does not exist.

Now a paged search over ENTIRE_SEARCH_BASE finds the highest uidNumber
on each activation. Numbering is unchanged: the next number is above
both LDAP_DEFAULT_START_UID and every existing uidNumber.
LDAP_MAX_UID_FILE_PATH is no longer used.
2026-11-06 15:03:57 +03:00
54aa8f474e added rest framework conf 2019-05-05 17:16:14 -04:00
PCoder
bf3b3b364f Get ENTIRE_SEARCH_BASE from env 2019-02-24 22:21:55 +01:00
PCoder
0591a186d8 Add EMAIL settings 2019-02-24 19:28:09 +01:00
PCoder
9ce45e6f56 Set EMAIL_FROM_ADDRESS from env 2019-02-24 18:41:07 +01:00
PCoder
7d09819ccb Improve logging 2019-02-24 17:23:41 +01:00
PCoder
0976d0dbcb Obtain some LDAP configs from env 2019-02-23 21:25:58 +01:00
PCoder
122ee37e48 Use LdapManager to change password 2019-02-23 19:50:42 +01:00
PCoder
113fa2c454 Pickup env variables and use them 2019-02-23 18:45:21 +01:00
PCoder
a3dca06c35 Use config instead of os.environ 2019-02-23 09:28:56 +01:00
PCoder
e8f12e18d2 Obtain ALLOWED_HOSTS from config 2019-02-22 06:06:36 +01:00
PCoder
00bc02541d Add basic AUTH_LDAP_USER_ATTR_MAP 2019-02-19 23:40:09 +01:00
PCoder
b60ffe6b58 Use python-decouple instead of django-dotenv + add logging 2019-02-19 23:15:07 +01:00
PCoder
c571cf5c1d Add bootstrap3 to INSTALLED_APPS 2019-02-18 22:01:42 +01:00
PCoder
c4c990a98d More correction 2019-02-17 18:25:12 +01:00
PCoder
876567f933 Correct the way we get ALLOWED_HOSTS 2019-02-17 18:20:56 +01:00
PCoder
5ec63d8536 Get ALLOWED_HOSTS and DEBUG from .env 2019-02-17 18:08:36 +01:00
PCoder
8b61db8dee Simplify getting LDAP_USE_TLS 2019-02-17 17:57:48 +01:00
PCoder
ace2fa6eb9 Move project files to root directory 2019-02-17 17:43:06 +01:00
Renamed from dal/dal/settings.py (Browse further)