diff --git a/signon/middleware.py b/signon/middleware.py index cb4bcdccdeb767dce44b7018b850a243ffcd64fb..cad880331e3c69b01b0c260298b21d7c3fd7fd2a 100644 --- a/signon/middleware.py +++ b/signon/middleware.py @@ -96,7 +96,13 @@ class SignonMiddleware: for identity in request.signon_identities.values(): if user is None: user = User.objects.create_from_identity(identity) - identity.user = user + log.info("%s: auto created from identity %s", user, identity) + identity.person = user + log.info("%s: auto bound to identity %s", user, identity) + identity.save( + audit_author=User.objects.get_housekeeper(), + audit_notes=f"Auto associated during automatic creation of user {user}", + ) def _signon_auto_bind(self, request): """ diff --git a/signon/tests/test_authentication.py b/signon/tests/test_authentication.py index 8df36978e38a080cfbcc4126d987e333da41eda0..b20f2565367aa66ae0992b7939a4291563634964 100644 --- a/signon/tests/test_authentication.py +++ b/signon/tests/test_authentication.py @@ -2,6 +2,7 @@ from __future__ import annotations from unittest import mock from django.test import TestCase, override_settings from django.urls import reverse +from django.contrib.auth import get_user_model from signon.unittest import SignonFixtureMixin from signon import providers @@ -275,3 +276,79 @@ class TestAuthentication(SignonFixtureMixin, TestCase): self.identities.salsa2.refresh_from_db() self.assertIsNone(self.identities.salsa2.person) + + @override_settings(SIGNON_AUTO_CREATE_USER=True) + def test_auto_create_user_debsso(self): + User = get_user_model() + if not hasattr(User.objects, "create_from_identity"): + self.skipTest("user model manager does not implement create_from_identity") + + # One bound debsso account + self.identities.create( + "debsso", issuer="debsso", subject="new@debian.org", audit_skip=True) + + def _instantiate_identities(_self, request): + request.signon_identities = { + "debsso": self.identities.debsso, + } + + with mock.patch("signon.middleware.SignonMiddleware._instantiate_identities", _instantiate_identities): + client = self.make_test_client(None) + with self.assertLogs() as log: + response = client.get(reverse('signon:whoami')) + + self.identities.debsso.refresh_from_db() + person = self.identities.debsso.person + + self.assertEqual(log.output, [ + f"INFO:signon.middleware:{person}: auto created from identity -:debsso:new@debian.org", + f"INFO:signon.middleware:{person}: auto bound to identity {person}:debsso:new@debian.org", + ]) + + self.assertEqual(response.status_code, 200) + + request = response.wsgi_request + self.assertEqual(request.signon_identities, { + "debsso": self.identities.debsso, + }) + + self.assertIsNotNone(person) + self.assertEqual(list(person.identities.all()), [self.identities.debsso]) + + @override_settings(SIGNON_AUTO_CREATE_USER=True) + def test_auto_create_user_salsa(self): + User = get_user_model() + if not hasattr(User.objects, "create_from_identity"): + self.skipTest("user model manager does not implement create_from_identity") + + # One bound debsso account + self.identities.create( + "salsa", issuer="salsa", subject="2", username="new", audit_skip=True) + + def _instantiate_identities(_self, request): + request.signon_identities = { + "salsa": self.identities.salsa, + } + + with mock.patch("signon.middleware.SignonMiddleware._instantiate_identities", _instantiate_identities): + client = self.make_test_client(None) + with self.assertLogs() as log: + response = client.get(reverse('signon:whoami')) + + self.identities.salsa.refresh_from_db() + person = self.identities.salsa.person + + self.assertEqual(log.output, [ + f"INFO:signon.middleware:{person}: auto created from identity -:salsa:2", + f"INFO:signon.middleware:{person}: auto bound to identity {person}:salsa:2", + ]) + + self.assertEqual(response.status_code, 200) + + request = response.wsgi_request + self.assertEqual(request.signon_identities, { + "salsa": self.identities.salsa, + }) + + self.assertIsNotNone(person) + self.assertEqual(list(person.identities.all()), [self.identities.salsa]) diff --git a/signon/views.py b/signon/views.py index 69342dd87b75d9afd7d2364e50aa6405d2e9878f..1d75e72557e5a974fca42f9955fe39db4111a12f 100644 --- a/signon/views.py +++ b/signon/views.py @@ -42,7 +42,11 @@ class Logout(View): # Log out the Django user if they were logged in. auth.logout(request) - return redirect("home") + next_url = request.POST.get("next") + if next_url is None: + return redirect("home") + else: + return redirect(next_url) class Whoami(View):