From 4dd200c0133fdb4433b52ea3f3a6f7209e71147b Mon Sep 17 00:00:00 2001 From: Ivan Lukyanets Date: Mon, 2 Oct 2023 17:44:06 +0300 Subject: [PATCH 1/4] Update oauth2_validators.py --- oauth2_provider/oauth2_validators.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index cecb843c5..c85911170 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -333,6 +333,19 @@ def validate_client_id(self, client_id, request, *args, **kwargs): def get_default_redirect_uri(self, client_id, request, *args, **kwargs): return request.client.default_redirect_uri + def get_or_create_user_from_content(self, content): + """ + An optional layer to define where to store the profile in `UserModel` or a separate model. For example `UserOAuth`, where `user = models.OneToOneField(UserModel)` . + + The function is called after checking that username is in the content. + + Returns an UserModel instance; + """ + user, _ = UserModel.objects.get_or_create( + **{UserModel.USERNAME_FIELD: content["username"]} + ) + return user + def _get_token_from_authentication_server( self, token, introspection_url, introspection_token, introspection_credentials ): @@ -383,9 +396,7 @@ def _get_token_from_authentication_server( if "active" in content and content["active"] is True: if "username" in content: - user, _created = UserModel.objects.get_or_create( - **{UserModel.USERNAME_FIELD: content["username"]} - ) + user = self.get_or_create_user_from_content(content) else: user = None From cc3620f5c5bc0eac8aae3c6abe8acf28030661af Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:50:43 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/oauth2_validators.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/oauth2_provider/oauth2_validators.py b/oauth2_provider/oauth2_validators.py index c85911170..829cde25f 100644 --- a/oauth2_provider/oauth2_validators.py +++ b/oauth2_provider/oauth2_validators.py @@ -341,9 +341,7 @@ def get_or_create_user_from_content(self, content): Returns an UserModel instance; """ - user, _ = UserModel.objects.get_or_create( - **{UserModel.USERNAME_FIELD: content["username"]} - ) + user, _ = UserModel.objects.get_or_create(**{UserModel.USERNAME_FIELD: content["username"]}) return user def _get_token_from_authentication_server( From df674e751e1197e670fbf666006fa387796fe112 Mon Sep 17 00:00:00 2001 From: Ivan Lukyanets Date: Mon, 13 May 2024 12:41:42 +0300 Subject: [PATCH 3/4] add docs & tests --- AUTHORS | 1 + CHANGELOG.md | 1 + docs/oidc.rst | 11 +++++++++++ tests/test_oauth2_validators.py | 10 ++++++++++ 4 files changed, 23 insertions(+) diff --git a/AUTHORS b/AUTHORS index 3443635b6..52a3693af 100644 --- a/AUTHORS +++ b/AUTHORS @@ -60,6 +60,7 @@ Hasan Ramezani Hiroki Kiyohara Hossein Shakiba Islam Kamel +Ivan Lukyanets Jadiel Teófilo Jens Timmerman Jerome Leclanche diff --git a/CHANGELOG.md b/CHANGELOG.md index 45414b083..d9fe0ac91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1337 Gracefully handle expired or deleted refresh tokens, in `validate_user`. * #1350 Support Python 3.12 and Django 5.0 * #1249 Add code_challenge_methods_supported property to auto discovery information, per [RFC 8414 section 2](https://www.rfc-editor.org/rfc/rfc8414.html#page-7) +* #1328 Adds the ability to define how to store a user profile ### Fixed diff --git a/docs/oidc.rst b/docs/oidc.rst index bbb4651bd..ac9c97161 100644 --- a/docs/oidc.rst +++ b/docs/oidc.rst @@ -404,6 +404,17 @@ In the docs below, it assumes that you have mounted the the URLs accordingly. +Define where to store the profile +================================= + +.. py:function:: OAuth2Validator.get_or_create_user_from_content(content) + +An optional layer to define where to store the profile in ``UserModel`` or a separate model. For example ``UserOAuth``, where ``user = models.OneToOneField(UserModel)``. + +The function is called after checking that the username is present in the content. + +:return: An instance of the ``UserModel`` representing the user fetched or created. + ConnectDiscoveryInfoView ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py index cb734a9b2..645d7c16d 100644 --- a/tests/test_oauth2_validators.py +++ b/tests/test_oauth2_validators.py @@ -335,6 +335,16 @@ def test_save_bearer_token__with_new_token__calls_methods_to_create_access_and_r assert create_access_token_mock.call_count == 1 assert create_refresh_token_mock.call_count == 1 + def test_get_or_create_user_from_content(self): + content = { + "username": "test_user" + } + UserModel.objects.filter(username=content["username"]).delete() + user = self.validator.get_or_create_user_from_content(content) + + self.assertIsNotNone(user) + self.assertEqual(content["username"], user.username) + class TestOAuth2ValidatorProvidesErrorData(TransactionTestCase): """These test cases check that the recommended error codes are returned From a0ba9d773aa5c80caf84089d06c866d8fe152476 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 09:43:12 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_oauth2_validators.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_oauth2_validators.py b/tests/test_oauth2_validators.py index 645d7c16d..ca80aedb0 100644 --- a/tests/test_oauth2_validators.py +++ b/tests/test_oauth2_validators.py @@ -336,9 +336,7 @@ def test_save_bearer_token__with_new_token__calls_methods_to_create_access_and_r assert create_refresh_token_mock.call_count == 1 def test_get_or_create_user_from_content(self): - content = { - "username": "test_user" - } + content = {"username": "test_user"} UserModel.objects.filter(username=content["username"]).delete() user = self.validator.get_or_create_user_from_content(content)