Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds the ability to define how to store a user #1328

Merged
merged 4 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Hasan Ramezani
Hiroki Kiyohara
Hossein Shakiba
Islam Kamel
Ivan Lukyanets
Jadiel Teófilo
Jens Timmerman
Jerome Leclanche
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions docs/oidc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
15 changes: 12 additions & 3 deletions oauth2_provider/oauth2_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@ 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
):
Expand Down Expand Up @@ -383,9 +394,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

Expand Down
8 changes: 8 additions & 0 deletions tests/test_oauth2_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,14 @@ 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
Expand Down
Loading