-
Notifications
You must be signed in to change notification settings - Fork 16
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
fix!: bug in JWT vs session user id compare #408
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
""" edx Django REST Framework extensions. """ | ||
|
||
__version__ = '8.13.1' # pragma: no cover | ||
__version__ = '9.0.0' # pragma: no cover |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
from edx_rest_framework_extensions.config import ( | ||
ENABLE_FORGIVING_JWT_COOKIES, | ||
ENABLE_SET_REQUEST_USER_FOR_JWT_COOKIE, | ||
VERIFY_LMS_USER_ID_PROPERTY_NAME, | ||
) | ||
from edx_rest_framework_extensions.settings import get_setting | ||
|
||
|
@@ -335,7 +336,7 @@ def _is_jwt_cookie_and_session_user_mismatch(self, request, jwt_user_id): | |
# .. custom_attribute_name: jwt_auth_with_django_request | ||
# .. custom_attribute_description: There exists custom authentication code in the platform that is | ||
# calling JwtAuthentication with a Django request, rather than the expected DRF request. This | ||
# custom attribute could be used to track down those usages and find ways to elimitate custom | ||
# custom attribute could be used to track down those usages and find ways to eliminate custom | ||
# authentication code that lives outside of this library. | ||
set_custom_attribute('jwt_auth_with_django_request', True) | ||
|
||
|
@@ -351,25 +352,74 @@ def _is_jwt_cookie_and_session_user_mismatch(self, request, jwt_user_id): | |
return False | ||
|
||
if user.is_authenticated: | ||
session_user_id = user.id | ||
session_lms_user_id = self._get_lms_user_id_from_user(user) | ||
else: | ||
session_user_id = None | ||
session_lms_user_id = None | ||
|
||
if not session_user_id or session_user_id == jwt_user_id: | ||
if not session_lms_user_id or session_lms_user_id == jwt_user_id: | ||
return False | ||
|
||
# .. custom_attribute_name: jwt_auth_mismatch_session_user_id | ||
# .. custom_attribute_description: The session authentication user id if it | ||
# does not match the JWT cookie user id. If there is no session user, | ||
# or if it matches the JWT cookie user id, this attribute will not be | ||
# included. Session authentication may have completed in middleware | ||
# .. custom_attribute_name: jwt_auth_mismatch_session_lms_user_id | ||
# .. custom_attribute_description: The session authentication LMS user id if it | ||
# does not match the JWT cookie LMS user id. If there is no session user, | ||
# or no LMS user id for the session user, or if it matches the JWT cookie user id, | ||
# this attribute will not be included. Session authentication may have completed in middleware | ||
# before getting to DRF. Although this authentication won't stick, | ||
# because it will be replaced by DRF authentication, we record it, | ||
# because it sometimes does not match the JWT cookie user. | ||
set_custom_attribute('jwt_auth_mismatch_session_user_id', session_user_id) | ||
set_custom_attribute('jwt_auth_mismatch_session_lms_user_id', session_lms_user_id) | ||
|
||
return True | ||
|
||
def _get_lms_user_id_from_user(self, user): | ||
""" | ||
Returns the lms_user_id from the user object if found, or None if not found. | ||
|
||
This is intended for use only by LMS user id matching code, and thus will provide appropriate error | ||
logs in the case of misconfiguration. | ||
""" | ||
# .. custom_attribute_name: jwt_auth_get_lms_user_id_status | ||
# .. custom_attribute_description: This custom attribute is intended to be temporary. It will allow | ||
# us visibility into when and how the LMS user id is being found from the session user, which | ||
# allows us to check the session's LMS user id with the JWT's LMS user id. Possible values include: | ||
# - skip-check (disabled check, useful when lms_user_id would have been available), | ||
# - not-configured (setting was None and lms_user_id is not found), | ||
# - misconfigured (the property name supplied could not be found), | ||
# - id-found (the id was found using the property name), | ||
# - id-not-found (the property exists, but returned None) | ||
|
||
lms_user_id_property_name = get_setting(VERIFY_LMS_USER_ID_PROPERTY_NAME) | ||
|
||
# This special value acts like an emergency disable toggle in the event that the user object has an lms_user_id, | ||
# but this LMS id check starts causing unforeseen issues and needs to be disabled. | ||
skip_check_property_name = 'skip-check' | ||
if lms_user_id_property_name == skip_check_property_name: | ||
set_custom_attribute('jwt_auth_get_lms_user_id_status', skip_check_property_name) | ||
return None | ||
Comment on lines
+393
to
+398
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Author Note: It was taking me equally long trying to justify we we might not want this, and since I want to quickly get to removing the forgiven JWT toggle, I would have had no kill switch for this if it turns out to cause an issue. So, I decided to add this additional bit of complexity. @feanil: Please mark as resolved if you have no response, or start a conversation. Thanks. |
||
|
||
if not lms_user_id_property_name: | ||
if hasattr(user, 'lms_user_id'): | ||
# The custom attribute will be set below. | ||
lms_user_id_property_name = 'lms_user_id' | ||
else: | ||
set_custom_attribute('jwt_auth_get_lms_user_id_status', 'not-configured') | ||
return None | ||
|
||
if not hasattr(user, lms_user_id_property_name): | ||
logger.error(f'Misconfigured VERIFY_LMS_USER_ID_PROPERTY_NAME. User object has no attribute with name' | ||
f' [{lms_user_id_property_name}]. User id validation will be skipped.') | ||
set_custom_attribute('jwt_auth_get_lms_user_id_status', 'misconfigured') | ||
return None | ||
|
||
# If the property is found, but returns None, validation will be skipped with no messaging. | ||
lms_user_id = getattr(user, lms_user_id_property_name, None) | ||
if lms_user_id: | ||
set_custom_attribute('jwt_auth_get_lms_user_id_status', 'id-found') | ||
else: # pragma: no cover | ||
set_custom_attribute('jwt_auth_get_lms_user_id_status', 'id-not-found') | ||
|
||
return lms_user_id | ||
|
||
|
||
_IS_REQUEST_USER_SET_FOR_JWT_AUTH_CACHE_KEY = '_is_request_user_for_jwt_set' | ||
|
||
|
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.
I think the first copy here is supposed to be
jwt_auth_mismatch_session_user_id
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. I created #411.