Skip to content

Commit d167364

Browse files
authored
Merge pull request #171 from eduNEXT/mgs/tpa_middleware
feat(TPA): Add middleware to handle TPA exceptions
2 parents 8e8c8a6 + 6eb9427 commit d167364

File tree

7 files changed

+97
-2
lines changed

7 files changed

+97
-2
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Change Log
1111
.. There should always be an "Unreleased" section for changes pending release.
1212
Unreleased
1313
----------
14+
* Add new middleware to catch unhandled exceptions during the third
15+
party authentication.
1416

1517
[4.15.1] - 2021-08-13
1618
---------------------
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Backend for the third party authentication exception middleware."""
2+
3+
4+
def get_tpa_exception_middleware():
5+
"""Get the ExceptionMiddleware class."""
6+
try:
7+
from third_party_auth.middleware import ExceptionMiddleware # pylint: disable=import-outside-toplevel
8+
except ImportError:
9+
from django.utils.deprecation import MiddlewareMixin as ExceptionMiddleware # pylint: disable=import-outside-toplevel
10+
return ExceptionMiddleware
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""
2+
Third Party Auth Exception Middleware definitions.
3+
"""
4+
5+
from importlib import import_module
6+
7+
from django.conf import settings
8+
9+
10+
def get_tpa_exception_middleware():
11+
"""Get the ExceptionMiddleware class."""
12+
backend_function = settings.EOX_CORE_THIRD_PARTY_AUTH_BACKEND
13+
backend = import_module(backend_function)
14+
return backend.get_tpa_exception_middleware()

eox_core/middleware.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,42 @@
88
"""
99
import logging
1010
import re
11+
from urllib.parse import urlparse
1112

1213
import six
1314
from django.conf import settings
1415
from django.contrib.auth import REDIRECT_FIELD_NAME
1516
from django.contrib.auth.views import redirect_to_login
17+
from django.db import IntegrityError
1618
from django.db.models.signals import post_save
1719
from django.dispatch import receiver
1820
from django.http import Http404, HttpResponseRedirect
21+
from django.urls import reverse
1922
from django.utils.deprecation import MiddlewareMixin
23+
from requests.exceptions import HTTPError
2024

2125
from eox_core.edxapp_wrapper.configuration_helpers import get_configuration_helper
26+
from eox_core.edxapp_wrapper.third_party_auth import get_tpa_exception_middleware
2227
from eox_core.models import Redirection
2328
from eox_core.utils import cache, fasthash
2429

30+
try:
31+
from social_core.exceptions import AuthUnreachableProvider, AuthAlreadyAssociated, AuthFailed
32+
except ImportError:
33+
AuthUnreachableProvider = Exception
34+
AuthAlreadyAssociated = Exception
35+
AuthFailed = Exception
36+
37+
try:
38+
from eox_tenant.pipeline import EoxTenantAuthException
39+
except ImportError:
40+
41+
class EoxTenantAuthException:
42+
"""Dummy eox-tenant Exception."""
43+
44+
2545
configuration_helper = get_configuration_helper() # pylint: disable=invalid-name
46+
ExceptionMiddleware = get_tpa_exception_middleware()
2647

2748
LOG = logging.getLogger(__name__)
2849

@@ -229,3 +250,45 @@ def clear_cache(sender, instance, **kwargs): # pylint: disable=unused-argument
229250
"""
230251
cache_key = "redirect_cache." + fasthash(instance.domain)
231252
cache.delete(cache_key) # pylint: disable=maybe-no-member
253+
254+
255+
class TPAExceptionMiddleware(ExceptionMiddleware):
256+
"""Middleware to handle exceptions not catched by Social Django"""
257+
258+
def process_exception(self, request, exception):
259+
"""
260+
Handle exceptions raised during the authentication process.
261+
"""
262+
referer_url = request.META.get('HTTP_REFERER', '')
263+
referer_url = urlparse(referer_url).path
264+
265+
if referer_url != reverse('signin_user') and request.view_name not in ['auth', 'complete']:
266+
return super().process_exception(request, exception)
267+
268+
if isinstance(exception, EoxTenantAuthException):
269+
new_exception = AuthFailed(
270+
exception.backend,
271+
str(exception),
272+
)
273+
LOG.error("%s", exception)
274+
return super().process_exception(request, new_exception)
275+
276+
if isinstance(exception, IntegrityError):
277+
backend = getattr(request, 'backend', None)
278+
new_exception = AuthAlreadyAssociated(
279+
backend,
280+
"The given email address is associated with another account",
281+
)
282+
LOG.error("%s", exception)
283+
return super().process_exception(request, new_exception)
284+
285+
if isinstance(exception, HTTPError):
286+
backend = getattr(request, 'backend', None)
287+
new_exception = AuthUnreachableProvider(
288+
backend,
289+
"Unable to connect with the external provider",
290+
)
291+
LOG.error("%s", exception)
292+
return super().process_exception(request, new_exception)
293+
294+
return super().process_exception(request, exception)

eox_core/settings/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def plugin_settings(settings):
4949
settings.EOX_CORE_USER_UPDATE_SAFE_FIELDS = ["is_active", "password", "fullname", "mailing_address", "year_of_birth", "gender", "level_of_education", "city", "country", "goals", "bio", "phone_number"]
5050
settings.EOX_CORE_BEARER_AUTHENTICATION = 'eox_core.edxapp_wrapper.backends.bearer_authentication_j_v1'
5151
settings.EOX_CORE_ASYNC_TASKS = []
52+
settings.EOX_CORE_THIRD_PARTY_AUTH_BACKEND = 'eox_core.edxapp_wrapper.backends.third_party_auth_j_v1'
5253

5354
if settings.EOX_CORE_USER_ENABLE_MULTI_TENANCY:
5455
settings.EOX_CORE_USER_ORIGIN_SITE_SOURCES = [

eox_core/settings/production.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ def plugin_settings(settings): # pylint: disable=function-redefined
9595
'EOX_CORE_COURSE_MANAGEMENT_REQUEST_TIMEOUT',
9696
settings.EOX_CORE_COURSE_MANAGEMENT_REQUEST_TIMEOUT
9797
)
98-
98+
settings.EOX_CORE_THIRD_PARTY_AUTH_BACKEND = getattr(settings, 'ENV_TOKENS', {}).get(
99+
'EOX_CORE_THIRD_PARTY_AUTH_BACKEND',
100+
settings.EOX_CORE_THIRD_PARTY_AUTH_BACKEND
101+
)
99102
settings.EOX_CORE_USER_ENABLE_MULTI_TENANCY = getattr(settings, 'ENV_TOKENS', {}).get(
100103
'EOX_CORE_USER_ENABLE_MULTI_TENANCY',
101104
settings.EOX_CORE_USER_ENABLE_MULTI_TENANCY
@@ -119,7 +122,8 @@ def plugin_settings(settings): # pylint: disable=function-redefined
119122
if settings.EOX_CORE_APPEND_LMS_MIDDLEWARE_CLASSES:
120123
settings.MIDDLEWARE += [
121124
'eox_core.middleware.PathRedirectionMiddleware',
122-
'eox_core.middleware.RedirectionsMiddleware'
125+
'eox_core.middleware.RedirectionsMiddleware',
126+
'eox_core.middleware.TPAExceptionMiddleware'
123127
]
124128

125129
# Sentry Integration

eox_core/settings/test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def plugin_settings(settings): # pylint: disable=function-redefined
3232
settings.EOX_CORE_ENABLE_UPDATE_USERS = True
3333
settings.EOX_CORE_USER_UPDATE_SAFE_FIELDS = ["is_active", "password", "fullname"]
3434
settings.EOX_CORE_BEARER_AUTHENTICATION = 'eox_core.edxapp_wrapper.backends.bearer_authentication_j_v1_test'
35+
settings.EOX_CORE_THIRD_PARTY_AUTH_BACKEND = 'eox_core.edxapp_wrapper.backends.third_party_auth_j_v1'
3536

3637

3738
SETTINGS = SettingsClass()

0 commit comments

Comments
 (0)