Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8ac1425
feat: adds custom endpoint for TPA disconnect
wgu-jesse-stewart Jul 31, 2025
a5cbb37
Merge branch 'openedx:master' into fix-disconnect-cors
wgu-jesse-stewart Jul 31, 2025
991c774
feat: address feedback
wgu-jesse-stewart Jul 31, 2025
a0f0e05
Merge branch 'fix-disconnect-cors' of https://github.com/WGU-Open-edX…
wgu-jesse-stewart Jul 31, 2025
445468c
feat: remove GET from endpoint
wgu-jesse-stewart Jul 31, 2025
65c4d22
fix: lint files
wgu-jesse-stewart Jul 31, 2025
093c562
fix: lint errors
wgu-jesse-stewart Aug 4, 2025
01a7363
fix: update tests
wgu-jesse-stewart Aug 4, 2025
43478e1
fix: address feedback
wgu-jesse-stewart Aug 4, 2025
fd4d760
fix: remove comment
wgu-jesse-stewart Aug 4, 2025
835cc24
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 4, 2025
ba17be8
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 5, 2025
b0f41bc
feat: removes comment
wgu-jesse-stewart Aug 5, 2025
56bbcbe
feat: adds backend_instance
wgu-jesse-stewart Aug 7, 2025
eb8dde0
fix: lint
wgu-jesse-stewart Aug 7, 2025
eecf1d4
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 7, 2025
7ed0f07
feat: pr feedback
wgu-jesse-stewart Aug 7, 2025
bb5c61e
fix: linting
wgu-jesse-stewart Aug 8, 2025
f276260
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 8, 2025
d3af088
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 11, 2025
da307cf
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 12, 2025
338ebed
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 13, 2025
63dfe98
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 14, 2025
ac89165
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 14, 2025
6f65452
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 18, 2025
0e115db
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 19, 2025
cc14612
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 20, 2025
39b254f
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Aug 26, 2025
df037f5
feat: add logging to exception and return generic error
wgu-jesse-stewart Aug 29, 2025
6c9c748
Merge branch 'master' into fix-disconnect-cors
wgu-jesse-stewart Sep 11, 2025
f847c19
Update common/djangoapps/third_party_auth/views.py
wgu-jesse-stewart Sep 12, 2025
498b5d7
Update common/djangoapps/third_party_auth/views.py
wgu-jesse-stewart Sep 12, 2025
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
104 changes: 104 additions & 0 deletions common/djangoapps/third_party_auth/custom_disconnect_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""
Custom disconnect view that returns JSON instead of redirecting to avoid CORS issues.
"""
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.core.exceptions import ValidationError, PermissionDenied
from django.db import DatabaseError
from social_django.utils import psa
from social_django.models import UserSocialAuth


@login_required
@require_http_methods(["POST"])
@psa()
def disconnect_json_view(request, backend, association_id=None):
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
"""
Custom disconnect view that returns JSON response instead of redirecting.
This prevents CORS issues when called from MFE frontends.
"""
user = request.user

# Check URL parameter first, then POST parameter, and fallback to GET parameter for backward compatibility
if not association_id:
association_id = request.POST.get('association_id') or request.GET.get('association_id')
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved

try:
if association_id:
# Disconnect specific association by ID
association = UserSocialAuth.objects.get(
id=association_id,
user=user,
provider=backend
)
association.delete()
else:
# Disconnect all associations for this backend
UserSocialAuth.objects.filter(
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
user=user,
provider=backend
).delete()

return JsonResponse({
'success': True,
'message': 'Account successfully disconnected',
'backend': backend,
'association_id': association_id
})

except UserSocialAuth.DoesNotExist:
return JsonResponse({
'success': False,
'error': 'Social auth association not found',
'backend': backend,
'association_id': association_id
}, status=404)

except ValueError as e:
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
return JsonResponse({
'success': False,
'error': 'Invalid parameter value provided',
'backend': backend,
'association_id': association_id
}, status=400)

except TypeError as e:
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
return JsonResponse({
'success': False,
'error': 'Invalid parameter type provided',
'backend': backend,
'association_id': association_id
}, status=400)

except DatabaseError as e:
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
return JsonResponse({
'success': False,
'error': 'Database operation failed',
'backend': backend,
'association_id': association_id
}, status=500)

except ValidationError as e:
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
return JsonResponse({
'success': False,
'error': 'Validation failed',
'backend': backend,
'association_id': association_id
}, status=400)

except PermissionDenied as e:
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
return JsonResponse({
'success': False,
'error': 'Permission denied',
'backend': backend,
'association_id': association_id
}, status=403)

except Exception as e:
return JsonResponse({
'success': False,
'error': 'An unexpected error occurred',
Comment thread
wgu-jesse-stewart marked this conversation as resolved.
Resolved
'backend': backend,
'association_id': association_id
}, status=500)
5 changes: 3 additions & 2 deletions common/djangoapps/third_party_auth/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,11 @@ def get_disconnect_url(provider_id, association_id):
ValueError: if no provider is enabled with the given ID.
"""
backend_name = _get_enabled_provider(provider_id).backend_name
# Use custom JSON disconnect endpoint to avoid CORS issues
if association_id:
return _get_url('social:disconnect_individual', backend_name, url_params={'association_id': association_id})
return _get_url('custom_disconnect_json_individual', backend_name, url_params={'association_id': association_id})
else:
return _get_url('social:disconnect', backend_name)
return _get_url('custom_disconnect_json', backend_name)


def get_login_url(provider_id, auth_entry, redirect_url=None):
Expand Down
4 changes: 4 additions & 0 deletions common/djangoapps/third_party_auth/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
post_to_custom_auth_form,
saml_metadata_view
)
from .custom_disconnect_view import disconnect_json_view

urlpatterns = [
path('auth/inactive', inactive_user_view, name="third_party_inactive_redirect"),
path('auth/custom_auth_entry', post_to_custom_auth_form, name='tpa_post_to_custom_auth_form'),
re_path(r'^auth/saml/metadata.xml', saml_metadata_view),
re_path(r'^auth/login/(?P<backend>lti)/$', lti_login_and_complete_view),
path('auth/idp_redirect/<slug:provider_slug>', IdPRedirectView.as_view(), name="idp_redirect"),
# Custom JSON disconnect endpoint to avoid CORS issues
re_path(r'^auth/disconnect_json/(?P<backend>[^/]+)/$', disconnect_json_view, name='custom_disconnect_json'),
re_path(r'^auth/disconnect_json/(?P<backend>[^/]+)/(?P<association_id>\d+)/$', disconnect_json_view, name='custom_disconnect_json_individual'),
path('auth/', include('social_django.urls', namespace='social')),
path('auth/saml/v0/', include('common.djangoapps.third_party_auth.samlproviderconfig.urls')),
path('auth/saml/v0/', include('common.djangoapps.third_party_auth.samlproviderdata.urls')),
Expand Down