diff --git a/src/authentic2_auth_oidc/backends.py b/src/authentic2_auth_oidc/backends.py index 7181e1c26..6fa3962af 100644 --- a/src/authentic2_auth_oidc/backends.py +++ b/src/authentic2_auth_oidc/backends.py @@ -239,6 +239,11 @@ class OIDCBackend(ModelBackend): linked = True except User.DoesNotExist: pass + except User.MultipleObjectsReturned: + logger.error('auth_oidc: cannot create user with sub "%s", ' + 'too many users with the same email "%s" in ou "%s"', + id_token.sub, email, provider.ou) + return if not user: user = User.objects.create(ou=provider.ou) user.set_unusable_password() diff --git a/tests/test_auth_oidc.py b/tests/test_auth_oidc.py index 8e2e2a832..cfd5e6669 100644 --- a/tests/test_auth_oidc.py +++ b/tests/test_auth_oidc.py @@ -862,3 +862,51 @@ def test_save_account_on_delete_user(db): 'sub': '1234', } ] + + +def test_multiple_users_with_same_email(app, caplog, code, oidc_provider_jwkset, hooks): + oidc_provider = make_oidc_provider(idtoken_algo=OIDCProvider.ALGO_HMAC) + ou = get_default_ou() + ou.email_is_unique = True + ou.save() + + user1 = User.objects.create(ou=ou, email='john.doe@example.com') + + assert OIDCAccount.objects.count() == 0 + + response = app.get('/').maybe_follow() + assert oidc_provider.name in response.text + response = response.click(oidc_provider.name) + location = urlparse.urlparse(response.location) + query = QueryDict(location.query) + state = query['state'] + nonce = query['nonce'] + + # sub=john.doe, MUST not work + with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, nonce=nonce): + response = app.get(login_callback_url(oidc_provider), params={'code': code, 'state': state}) + + assert app.session['_auth_user_id'] == str(user1.id) + assert OIDCAccount.objects.count() == 1 + + app.session.flush() + OIDCAccount.objects.all().delete() + User.objects.create(ou=ou, email='john.doe@example.com') + + response = app.get('/').maybe_follow() + assert oidc_provider.name in response.text + response = response.click(oidc_provider.name) + location = urlparse.urlparse(response.location) + query = QueryDict(location.query) + state = query['state'] + nonce = query['nonce'] + + assert OIDCAccount.objects.count() == 0 + + # sub=john.doe, MUST not work + with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, nonce=nonce): + response = app.get(login_callback_url(oidc_provider), params={'code': code, 'state': state}) + + assert '_auth_user_id' not in app.session + assert OIDCAccount.objects.count() == 0 + assert 'too many users' in caplog.records[-1].message