From 871ca147064ae14f4eb18d73759dab8b682419ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Fri, 29 Sep 2017 19:10:20 -0700 Subject: [PATCH] Support several required fields during User creation Custom User models may have several required attributes, for example a username and an email. If create is called directly based on DJANGO_USER_MAIN_ATTRIBUTE, the NOT NULL constraint will be violated, preventing to save that user. Instead, rely on update_user to call save when most attributes have been defined on the user. A nice side effect is that a database query might be saved, because the INSERT query for new users could contain all attributes when save() is called from update_user, while a first query (INSERT) was issued to create the user, then update user would call save(), issuing an UPDATE query. --- CHANGES | 4 ++++ djangosaml2/backends.py | 12 +++++++----- tests/testprofiles/models.py | 10 ++++++++++ tests/testprofiles/tests.py | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index d2d13a01..a53051c3 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,10 @@ Thanks to plumdog Thanks to plumdog +UNRELEASED +---------- +- Allowed creating Users with multiple required fields. + 0.17.1 (2018-07-16) ---------- - A 403 (permission denied) is now raised if a SAMLResponse is replayed, instead of 500. diff --git a/djangosaml2/backends.py b/djangosaml2/backends.py index bea054d6..f461f3b5 100644 --- a/djangosaml2/backends.py +++ b/djangosaml2/backends.py @@ -147,19 +147,21 @@ def _get_or_create_saml2_user(self, main_attribute, attributes, attribute_mappin main_attribute) django_user_main_attribute = self.get_django_user_main_attribute() user_query_args = self.get_user_query_args(main_attribute) - user_create_defaults = {django_user_main_attribute: main_attribute} User = get_saml_user_model() + built = False try: - user, created = User.objects.get_or_create( - defaults=user_create_defaults, **user_query_args) + user = User.objects.get(**user_query_args) + except User.DoesNotExist: + user = User(**{django_user_main_attribute: main_attribute}) + built = True except MultipleObjectsReturned: logger.error("There are more than one user with %s = %s", django_user_main_attribute, main_attribute) return None - if created: - logger.debug('New user created') + if built: + logger.debug('Configuring new user "%s"', main_attribute) user = self.configure_user(user, attributes, attribute_mapping) else: logger.debug('User updated') diff --git a/tests/testprofiles/models.py b/tests/testprofiles/models.py index 767da2ae..2d01591f 100644 --- a/tests/testprofiles/models.py +++ b/tests/testprofiles/models.py @@ -30,3 +30,13 @@ class StandaloneUserModel(models.Model): USERNAME_FIELD. """ username = models.CharField(max_length=30, unique=True) + + +class RequiredFieldUser(models.Model): + email = models.EmailField(unique=True) + email_verified = models.BooleanField() + + USERNAME_FIELD = 'email' + + def set_unusable_password(self): + pass diff --git a/tests/testprofiles/tests.py b/tests/testprofiles/tests.py index 073bea80..a648f4d6 100644 --- a/tests/testprofiles/tests.py +++ b/tests/testprofiles/tests.py @@ -134,6 +134,26 @@ def test_invalid_model_attribute_log(self): logs.output, ) + @override_settings(AUTH_USER_MODEL='testprofiles.RequiredFieldUser') + def test_create_user_with_required_fields(self): + backend = Saml2Backend() + attribute_mapping = { + 'mail': ['email'], + 'mail_verified': ['email_verified'] + } + attributes = { + 'mail': ['john@example.org'], + 'mail_verified': [True], + } + # User creation does not fail if several fields are required. + user = backend._get_or_create_saml2_user( + 'john@example.org', + attributes, + attribute_mapping, + ) + self.assertEquals(user.email, 'john@example.org') + self.assertIs(user.email_verified, True) + def test_django_user_main_attribute(self): backend = Saml2Backend()