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

feat: add observability for default auth classes #33003

Merged
merged 1 commit into from
Aug 15, 2023
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
9 changes: 8 additions & 1 deletion lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,13 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring
######################### Django Rest Framework ########################

REST_FRAMEWORK = {
# This matches the original DRF default of Session and Basic Authentication, but
# adds observability to help us potentially adjust the defaults. We would like to
# add JwtAuthentication and drop BasicAuthentication, based on our findings.
'DEFAULT_AUTHENTICATION_CLASSES': [
'openedx.core.djangolib.default_auth_classes.DefaultSessionAuthentication',
'openedx.core.djangolib.default_auth_classes.DefaultBasicAuthentication'
],
'DEFAULT_PAGINATION_CLASS': 'edx_rest_framework_extensions.paginators.DefaultPagination',
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
Expand All @@ -3303,7 +3310,7 @@ def _make_locale_paths(settings): # pylint: disable=missing-function-docstring

# .. setting_name: REGISTRATION_VALIDATION_RATELIMIT
# .. setting_default: 30/7d
# .. setting_description: Whenver a user tries to register on edx, the data entered during registration
# .. setting_description: Whenever a user tries to register on edx, the data entered during registration
# is validated via RegistrationValidationView.
# It's POST endpoint is rate-limited up to 30 requests per IP Address in a week by default.
# It was introduced because an attacker can guess or brute force a series of names to enumerate valid users.
Expand Down
87 changes: 87 additions & 0 deletions openedx/core/djangolib/default_auth_classes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""
Default Authentication classes that are ONLY meant to be used by
DEFAULT_AUTHENTICATION_CLASSES for observability purposes.
"""
from edx_django_utils.monitoring import set_custom_attribute
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from rest_framework.authentication import BasicAuthentication, SessionAuthentication


class DefaultSessionAuthentication(SessionAuthentication):
""" Default SessionAuthentication with observability """

def authenticate(self, request):
# .. custom_attribute_name: using_default_auth_classes
# .. custom_attribute_description: This custom attribute will always be
# True (if not NULL), and signifies that a default authentication
# class was used. This can be used to find endpoints using the
# default authentication classes.
set_custom_attribute('using_default_auth_classes', True)

try:
user_and_auth = super().authenticate(request)
if user_and_auth:
# .. custom_attribute_name: session_auth_result
# .. custom_attribute_description: The result of session auth, represented
# by: 'success', 'failure', or 'skipped'.
set_custom_attribute('session_auth_result', 'success')
else:
set_custom_attribute('session_auth_result', 'skipped')
return user_and_auth
except Exception as exception:
set_custom_attribute('session_auth_result', 'failure')
raise


class DefaultBasicAuthentication(BasicAuthentication):
"""
Default BasicAuthentication with observability

Note that BasicAuthentication was a default because it was a DRF default.
Observability will be used to determine if BasicAuthentication could
instead be dropped as a default.
"""

def authenticate(self, request):
# .. custom_attribute_name: using_default_auth_classes
# .. custom_attribute_description: This custom attribute will always be
# True (if not NULL), and signifies that a default authentication
# class was used. This can be used to find endpoints using the
# default authentication classes.
set_custom_attribute('using_default_auth_classes', True)

try:
user_and_auth = super().authenticate(request)
if user_and_auth:
# .. custom_attribute_name: basic_auth_result
# .. custom_attribute_description: The result of basic auth, represented
# by: 'success', 'failure', or 'skipped'.
set_custom_attribute('basic_auth_result', 'success')
else:
set_custom_attribute('basic_auth_result', 'skipped')
return user_and_auth
except Exception as exception:
set_custom_attribute('basic_auth_result', 'failure')
raise


class DefaultJwtAuthentication(JwtAuthentication):
"""
Default JwtAuthentication with observability

Note that the plan is to add JwtAuthentication as a default, but it
is not yet used. This class will be used during the transition.
"""

def authenticate(self, request):
# .. custom_attribute_name: using_default_auth_classes
# .. custom_attribute_description: This custom attribute will always be
# True (if not NULL), and signifies that a default authentication
# class was used. This can be used to find endpoints using the
# default authentication classes.
set_custom_attribute('using_default_auth_classes', True)

# Unlike the other DRF authentication classes, JwtAuthentication already
# includes a jwt_auth_result custom attribute, so we do not need to
# reimplement that observability in this class.
return super().authenticate(request)