From d8a83ac270fe51c83d9cdef9f027bb867c4082bd Mon Sep 17 00:00:00 2001 From: Rob Ireton Date: Thu, 24 Sep 2026 13:35:33 -0400 Subject: [PATCH] Remove ChiAuthBackend; 5.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every user_manager consumer (daedalus, email_service, monitor — all on chi-cetus) signs CHI users in through header SSO, so the backend that let the local form accept UC and CHI credentials authenticated no one in production. It was also the second of two paths answering the same questions, which is how #8 happened. The local form stays: it is how daedalus and monitor sign in during development, and /admin/login/ is the documented way in for a local superuser. Both go through Django's ModelBackend, which this does not touch. Gone with the backend, since nothing else read them: CHI_AUTH_API_ACCESS_TOKEN, CHI_AUTH_CHECK_SYSTEMS, CHI_AUTH_TIMEOUT, UC_PASSWORD_MANAGER_URL, the ACCOUNT_LOOKUP_URL and allow_chi_auth_login context variables, the UC/CHI hints and links in login.html, and the requests dependency. New checks for the upgrade: - E003: AUTHENTICATION_BACKENDS still lists ChiAuthBackend. That path is only imported at sign-in, so a missed one fails every login after a healthy-looking deploy; as a check Error it fails `migrate` instead. - W009: one of the three removed settings is still set, and does nothing. Fixes #9. Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 17 ++- README.md | 74 +++++----- pyproject.toml | 1 - tests/settings.py | 1 - tests/test_backends.py | 139 ------------------ tests/test_settings_and_checks.py | 74 ++++++---- user_manager/__init__.py | 2 +- user_manager/authentication_backends.py | 104 ------------- user_manager/checks.py | 38 +++++ user_manager/context_processors.py | 10 -- user_manager/custom_settings.py | 21 +-- user_manager/middleware.py | 4 +- .../templates/user_manager/login.html | 11 -- 13 files changed, 141 insertions(+), 355 deletions(-) delete mode 100644 tests/test_backends.py delete mode 100644 user_manager/authentication_backends.py diff --git a/CLAUDE.md b/CLAUDE.md index fe250e7..c6c8dbb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,10 +30,19 @@ nobody exercises are options nobody tests. ## Authentication -**Two login paths, and they must agree.** `ChiAuthLoginMiddleware` (header SSO) and -`ChiAuthBackend` (password) are separate code that answers the same questions — does this -user exist, may they in, do we create them. Issue #8 was those two drifting apart for -years. When you change one, check the other. +**CHI Auth signs people in one way: header SSO.** `ChiAuthLoginMiddleware` is the only +code that decides whether a CHI Auth user exists here, may come in, or gets created. +Until 5.0.0 `ChiAuthBackend` answered the same questions on a password path, and issue #8 +was the two drifting apart for years. No deployment used the backend, so #9 deleted it +rather than keep reconciling them. Don't bring a second path back without a consumer that +needs it. + +**The local form stays, for local accounts.** With `CHI_AUTH_USE_MIDDLEWARE` off — the +package default, and how daedalus and monitor run in development — `login_view` renders +`login.html` and authenticates through Django's `ModelBackend`. `/admin/login/` uses the +same backend, and it is the documented way in for a local superuser. `user_manager.E003` +catches a consumer still listing the removed backend, because that fails at sign-in, not +at startup. **Usernames match with `__iexact`, and the stored spelling is never rewritten.** The directory behind CHI Auth is case-insensitive and the `SSO-Username` header carries diff --git a/README.md b/README.md index b831f39..01e6f99 100644 --- a/README.md +++ b/README.md @@ -19,23 +19,21 @@ boundary is — so pick it first and read the matching section below. | what secures it | **the network** — see “Header based SSO” | Django’s own session and CSRF handling | | used by | every CHI deployment | development, and non-CHI deployments | -Within local login, `AUTHENTICATION_BACKENDS` decides *which* credentials the form accepts: -Django’s `ModelBackend` alone for local passwords only, plus `ChiAuthBackend` to accept UC -or CHI credentials as well. `CHI_AUTH_CHECK_SYSTEMS` then narrows that further — set it to -`"local"` to accept CHI accounts while leaving UC Active Directory out of it entirely. +Local login accepts **local accounts only**, through Django’s own `ModelBackend`. UC and CHI +credentials are CHI Auth’s to check, on its own page, under header SSO. Until 5.0.0 a +`ChiAuthBackend` let the local form accept them too; no deployment used it (issue #9). Supports Django 6.0 and 6.1 on Python 3.12+. -Projects using it: daedalus, ocr_importer, monitor, neurords, rap_subsystem, fcc_tracker, -email_service, covidicus. Daedalus is the most recently updated and is the best worked -example to copy from. +Projects using it: daedalus, email_service and monitor, all deployed on chi-cetus. +email_service is the best worked example to copy from. ## Setup Install from GitHub with pip ```shell -pip install "user_manager @ git+https://github.com/Center-for-Health-Informatics/django_user_manager.git@v4.0.1" +pip install "user_manager @ git+https://github.com/Center-for-Health-Informatics/django_user_manager.git@v5.0.0" ``` Check the tag against the latest release — this snippet is hand-maintained and has been @@ -43,7 +41,7 @@ stale before. or add to a `requirements.txt` file ``` -user_manager @ git+https://github.com/Center-for-Health-Informatics/django_user_manager.git@v4.0.1 +user_manager @ git+https://github.com/Center-for-Health-Informatics/django_user_manager.git@v5.0.0 ``` Add `user_manager` app to your installed apps @@ -60,17 +58,9 @@ Set the user manager model as your User model AUTH_USER_MODEL = "user_manager.User" ``` -Select the authentication backends you want to use -```python -# select auth backends to use when authenticating inside the app for development -# the following will first try local authentication, then try CHI Auth authentication -# checking both CHI Accounts and UC AD Accounts. On successful CHI Auth authentication, -# the local user will be created if they don’t exist yet. -AUTHENTICATION_BACKENDS = [ - "django.contrib.auth.backends.ModelBackend", - "user_manager.authentication_backends.ChiAuthBackend", -] -``` +Leave `AUTHENTICATION_BACKENDS` at Django’s default, `ModelBackend`. It is what the local +form and `/admin/login/` authenticate against. Listing the removed +`user_manager.authentication_backends.ChiAuthBackend` is `user_manager.E003`. Define an abstract user - `user_manager` uses this as the base to build its concrete `User` model, so this is where you add any custom fields or methods. @@ -86,7 +76,7 @@ class AbstractCustomUser(AbstractUser): abstract = True ``` -Register the context processor, so the login page can see `SITE_TITLE`, `CONTACT_EMAIL` and the CHI Auth help links. Without this the login page still renders, but with those values blank. +Register the context processor, so the login page can see `SITE_TITLE`, `CONTACT_EMAIL` and `ASSETS_URL`. Without this the login page still renders, but with those values blank. ```python TEMPLATES = [ @@ -119,21 +109,9 @@ environment itself, the same way it does for everything else it configures, so t # user_manager.W007. CHI_AUTH_URL = "/auth/" -# you need to provide an access token if using CHI_Auth -CHI_AUTH_API_ACCESS_TOKEN = "🤫" - -# Which CHI_AUTH systems do you want to use for authentication, in order? -# ucad is UC Active Directory, local is CHI_AUTH credentials for non-UC users. -# The order decides which one authenticates a password when an account exists in both, -# not merely which is asked first. Set it to "local" to turn UC AD off altogether. -CHI_AUTH_CHECK_SYSTEMS = 'ucad, local' - -# Provision a local account for someone CHI_AUTH authenticates but this app has never -# seen? Applies to both login paths. Defaults True; set False for closed provisioning. +# Provision a local account for someone CHI_AUTH signs in but this app has never seen? +# Read by the header SSO middleware. Defaults True; set False for closed provisioning. CHI_AUTH_AUTOCREATE_LOCAL_USER = True - -# seconds to wait on any call out to CHI Auth before giving up and failing the login -CHI_AUTH_TIMEOUT = 5 ``` Set login/logout paths. These are the same in both modes — under header SSO the login view @@ -161,9 +139,6 @@ SITE_TITLE = "Center for Health Informatics" # who to email for help CONTACT_EMAIL = "combmichi@uc.edu" -# where to change a UC password -UC_PASSWORD_MANAGER_URL = "https://www.uc.edu/sspr" - # base URL of the shared CHI asset library, trailing slash included — the sign-in # page links its stylesheet and favicon from here. Set it to "/assets/" where the # library is served from this same host, and the assets stop being cross-origin. @@ -383,6 +358,29 @@ MIDDLEWARE = [..., "user_manager.middleware.InspectHeadersMiddleware"] SPECIAL_LOG_FOLDER = "/var/log/myproject/" ``` +## Upgrading from 4.0 to 5.0 + +**`ChiAuthBackend` is gone** (#9). It let the local sign-in form accept UC and CHI +credentials by calling CHI Auth’s `api/authenticate`. Every consumer signs CHI users in +through header SSO instead, so in production it authenticated no one. The local form stays, +for local accounts through `ModelBackend` — which is how development signs in, and what +`/admin/login/` uses. + +In each consumer, in the same change as the pin bump: + +- **Delete `"user_manager.authentication_backends.ChiAuthBackend"` from + `AUTHENTICATION_BACKENDS`.** Left in, every sign-in fails importing it, so + `user_manager.E003` makes `manage.py check` — and `migrate` — fail instead. That stops the + deploy only where the entrypoint runs under `set -e`; email_service’s does not today, so + check it by hand there. +- **Delete `CHI_AUTH_API_ACCESS_TOKEN`, `CHI_AUTH_CHECK_SYSTEMS` and `CHI_AUTH_TIMEOUT`** + from `settings.py`, `example.settings.env` and the deployed `settings.env`. Nothing reads + them now; `user_manager.W009` names any still set. +- `UC_PASSWORD_MANAGER_URL` and the `ACCOUNT_LOOKUP_URL` / `allow_chi_auth_login` context + variables are gone with it. No consumer template used them. + +A consumer still on 3.2 takes the 4.0 notes below as well. + ## Upgrading from 4.0.0 to 4.0.1 - **The sign-in page no longer carries an inline `