-
Notifications
You must be signed in to change notification settings - Fork 819
fix race condition for AccessToken creation in oauth2_validators #611
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
Conversation
…e same AccessToken
|
After testing this does not fix the problem. Back to the drawing board. @jleclanche: Any suggestions? Maybe add |
oauth2_provider/oauth2_validators.py
Outdated
| expires = make_aware(expires) | ||
|
|
||
| try: | ||
| access_token = AccessToken.objects.select_related("application", "user").get(token=token) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be
access_token = AccessToken.objects.select_related().get_or_create(token=token, user=user, application=None, scope=scope, expires=expires)instead of trying to catch DoesNotExist and then calling get_or_create. This should block any other thread trying to access the same token, I believe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jdelic. Will test and report back.
|
whoops... wow, I must have been asleep when writing this comment :D. Ok, so let's write this up correctly:
So, I wanted to add select_for_update() # we also probably need to ensure this is in an atomic transaction
from django.db import transaction
with transaction.atomic():
access_token = AccessToken.objects.select_related().select_for_update().get_or_create(token=token, user=user, application=None, scope=scope, expires=expires)but I haven't tested any of this yet. This was meant as a "food for thought" comment :) |
|
yeah, sorry for blindly committing. I'm having local troubles just getting various brews of python setup on my mac so that tox can find them all... |
|
[OK, I've fixed my local dev environment so tox works!] But, more importantly, I think I've got this coded correctly now. I'm not sure if |
|
I'll try and add some additional test coverage once we confirm this fix. |
I agree. Looks good.
I believe it's necessary, because Just as a pointer: when you get around to write a test, heed the warning on the Thank you for taking care of this, by the way. |
|
This is not quite enough. The error is now So at least it's not failing with I'll just have to figure out the right settings to make the client wait (or maybe this is a sqlite3 deficiency and I'll have to test with an external database). |
|
Still having a database locked error with sqlite3, but, I found what I think is the correct approach by using |
|
I am unable to reproduce the test case with sqlite3, which is no surprise, but means it will be difficult to write a self-contained test case. However, I was able to reproduce the error with MS SQL Server running in MacOS in a Docker container: I compared toolkit release 1.2.0 vs. e626757 with a test DRF-DJA server running under pycharm with verbose database debugging and it does appear to be doing the right thing w.r.t. setting up transactions in the database server: In the logs for e626757 there's a SAVE TRANSACTION whereas in the 1.2.0 logs there's multiple INSERTs of the same access token followed by: However, I'm not comfortable that I've done extensive-enough testing just yet. We'll do that later this week I hope. I'll scan the code for other cases where |
|
Interestingly, it basically only wraps
awesome! I'll gladly review the PR when it's ready. |
|
@jdelic I've scanned and don't see another obvious instance in |
jdelic
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good to me (reviewed test output, code and context).
And I agree about the discussion in #609... without a repro, I'd say this PR covers the initial report. I'm going to keep my eyes open if I encounter the same pattern anywhere else in the code, too.
|
Hi there, Current versions: I've been forcing this error using this script. |
|
I've not touched the refresh token handling code but perhaps you could take a look at what I did in this PR and try the same approach for the refresh_token code. |
|
@n2ygk great, sorry if my comment was a bit off-topic then :-) |
|
Not at all. It's probably a similar error, just I don't have your environment to test against. It might be happening around here: https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/oauth2_validators.py#L521-L535 |
|
Right, I've been working on that piece, and that's why i ended up in this issue :-) Thanks |
…ngo-oauth#611) * work around race condition in which two threads both try to create the same AccessToken using update_or_create instead of two steps go get_or_create then update + save * update changelog
Addresses #609 in which two near-simultaneous requests both presenting the same previously unseen Bearer token (created by an external OAuth2 AS) try to create it.
I don't know how to write a test case to cause two calls to
_get_token_from_authentication_serverfor which a mocked external introspection endpoint delays the its response. I would appreciate any help in coming up with a test case.As coded, this change does not break existing functionality.