Integrate Hanko SSO login without the framework upgrade - #545
Conversation
Adds hotosm-auth alongside the existing OSM OAuth2 login, switchable with AUTH_PROVIDER. Hanko middleware maps the session onto a Django user so login_required and the permission classes keep working unchanged. Installs hotosm-auth without the [django] extra: that extra only pins django>=4.2 and drf>=3.14, which this project already provides at its own versions, so pulling it would force an unrelated framework upgrade. The OAuth2 provider routes stay mounted under both providers -- they are the public API's authorization server for third parties, not part of user login. Also fixes the login view to preserve ?next=, to route unmapped users through onboarding instead of dropping them on the home page, to reject off-site redirect targets, and to url-encode return_to so an OAuth2 target's own query string survives.
Replaces the OAuth2 login button with the shared hotosm-auth component when running under Hanko, and adds the hotosm-tool-menu component. API calls go through buildAuthConfig, which sends cookies under Hanko and keeps the bearer header under the legacy provider.
Covers the auth helpers, the status and onboarding views, and the DRF authentication class.
Compose stack and dev Dockerfile used to run the Hanko branch on export.testlogin.hotosm.org against dev.login. Redis host and the public raw-data-api URL now come from the environment so the containers can reach them, and ADMIN_EMAILS moves out of the compose file into .env. Also fixes a broken condition in the task runner: the SUBMITTED/RUNNING check used or, which is always true.
|
Thanks for making this - makes sense to avoid the out of scope upgrades if not needed. Hopefully this is a bit easier to review & debug when we need to fix anything on prod deploy 👍 |
spwoodcock
left a comment
There was a problem hiding this comment.
Much tighter PR focusing only on the auth integration.
Looks good to me:
- I reviewed the backend code.
- The frontend code I skimmed and it looks fine, but there is a lot of view every line.
- As for the tests, were the written post-update, or pre-update? It would be great if these were tests that checked the existing functionality on prod, then we tested that nothing broke in the tests after implementing the auth. No biggie, as the tests are win either way I guess
| ) | ||
|
|
||
| CORS_ALLOW_CREDENTIALS = True | ||
| CORS_ALLOWED_ORIGIN_REGEXES = [ |
There was a problem hiding this comment.
Should these probably be set by env vars, instead of hardcoded as insecure defaults?
When we deploy, we can be sure to set in the .env:
- localhost is fine for the test server
r"^https://.*\.hotosm\.org$",only for prod
There was a problem hiding this comment.
Is this only for local dev too, or the actual production nginx config? Probably worth clarifying in a comment at the top 🙏
If it's prod, we could possibly tighten it up a little with extra headers, compression, caching etc.
| if not user: | ||
| user = job.user | ||
| if job.last_run_status != "SUBMITTED" or job.last_run_status != "RUNNING": | ||
| if job.last_run_status != "SUBMITTED" and job.last_run_status != "RUNNING": |
There was a problem hiding this comment.
Just leaving a comment here to say this was a fix from the previous PR.
The conditional always resulted in True previously, now it doesn't.
The .save can be safely removed as .create automatically calls .save when used.
Integrates the shared HOT login (Hanko SSO) into the Export Tool, as an
alternative to #538.
This branch builds directly on @andrea-chirillano's work in #538 — the auth
helpers, middleware, onboarding flow, frontend integration and tests all come
from there. What changes is the dependency footprint and a handful of fixes
found while reviewing it.
Why a second PR
#538 upgrades the framework along with the login change:
~=3.2.22→~=4.2~=3.2.22(unchanged)1.3.2→>=2.0.01.3.2(unchanged)~=3.11.0→>=3.14.0~=3.11.0(unchanged)~=0.15→>=1.0~=0.15(unchanged)The framework upgrade turned out not to be necessary.
hotosm-auth[django]declares
django>=4.2anddrf>=3.14, but that's the extra's pin, not arequirement of the code:
hotosm_auth_djangoonly importsAppConfig,settings,connection,JsonResponse,SimpleLazyObject,csrf_exempt,pathand DRF'sAPIView/Response/status— all present in 3.2. Installinghotosm-authwithout the extra pulls the same wheel, and Django and DRFstay at the versions this project already provides.
The practical difference is the database. django-oauth-toolkit 1.3.2 ships 2
migrations; 2.x ships 13. Production is on those same 2. #538 would apply 11
schema migrations to the OAuth2 tables, which a revert doesn't undo. This
branch applies exactly one new migration,
hotosm_auth_django.0001, whichcreates a table and touches nothing existing.
Fixes on top of #538
get_user_permissionsreturned[]for every non-superuser under Hanko.It only read the superuser branch and never looked at
user_permissionsorgroups, so users with HDX or partner rights lost those menus. Both providers
now resolve permissions the same way — the middleware maps the session onto a
real Django user, so per-user and group permissions still apply.
:has()block with!importanthid HDX, Partners, Admin, Workers, Status and Stats foreveryone under Hanko. It was compensating for the empty permissions above;
with those fixed it isn't needed, and it would have overridden them anyway.
/o/under Hanko,which takes down the public API's authorization server — the application
console and token revocation that Help → API links to. Those routes are not
part of user login and now stay mounted under both providers.
loginview: preserves?next=, routes users without a mapping throughonboarding instead of dropping them on the home page, rejects off-site
redirect targets, and url-encodes
return_toso an OAuth2 target's own querystring survives (an unescaped
&truncated it).ADMIN_EMAILSmoved out of the compose file into.env, next toSECRET_KEYandCOOKIE_SECRET. Changing who is an admin no longer needs acommit.
!= "SUBMITTED" or != "RUNNING"is always true; nowand.Also left out of this branch, as unrelated to login: the test-fixture changes
(
conftest.py,setup.cfg,FeatureSelection→ constant), ayarn.lockthatmoved without
package.json(addinglit,preact,classnames— nonedeclared or imported, the components load from CDN),
requireAuthon/exportsand/configurationswhich are public routes on main, and anavigation change on configuration save.
Tested on export.testlogin.hotosm.org
Deployed from this branch against dev.login, with a clean database.
django 3.2.25,drf 3.11.2,DOT 1.3.2.oauth2_providermigrations: 2, same as production.those 6 and
is_superuser: false, and sees HDX and Partners in the navbar —while Admin, Workers, Status and Stats stay hidden, since those need
auth.add_user.ADMIN_EMAILSgrants superuser per request without persisting it to thedatabase.
/o/applications/, authorized it through the Hanko login, and the resultingbearer token returned that user's private configuration from
/api/configurations— while no token, an invalid token and an expired tokenall returned nothing. Hanko authenticates people, OAuth2 keeps authorizing
machines.
Behaviour changes worth knowing
/api/permissionsreturns 401 instead of a 302 to the login page,and some DRF endpoints return 403 where they returned 401 (the Hanko
authenticator doesn't set a
WWW-Authenticateheader). Both still deny;clients that follow the redirect or distinguish the codes are affected.
CORS_ORIGIN_ALLOW_ALLto a regex allowlist(
*.hotosm.org,*.hotosm.test, localhost) — credentialed CORS can't useallow-all.
ADMIN_EMAILSoverridesis_superuserper request. It must be populatedbefore switching a deployment to Hanko, or current admins lose access.
Not yet verified
from an empty database. Production has users with
social_authrecords, andonboarding matches them by OSM id and falls back to email. This needs a run
against a production dump before going live — including that their existing
OAuth2 applications stay attached to the same Django user.
ci.yamlrunsmake test,which is a single HDX test file. The suite is under the
test_alltarget.the test environment; the production deploy needs its own env wiring.
AUTH_PROVIDERdefaults tolegacy, and in that mode this branch behaves likemain — so it can be deployed without changing anything for users, and Hanko
switched on afterwards with one variable.