diff --git a/common/djangoapps/third_party_auth/api/tests/test_views.py b/common/djangoapps/third_party_auth/api/tests/test_views.py index 670caf04c7..e598493208 100644 --- a/common/djangoapps/third_party_auth/api/tests/test_views.py +++ b/common/djangoapps/third_party_auth/api/tests/test_views.py @@ -7,6 +7,7 @@ from unittest.mock import patch import ddt from django.conf import settings +from django.contrib.auth import get_user_model from django.http import QueryDict from django.test.utils import override_settings from django.urls import reverse @@ -214,15 +215,43 @@ class UserViewV2APITests(UserViewsMixin, TpaAPITestCase): Test the Third Party Auth User REST API """ - def make_url(self, identifier): + def setUp(self): # pylint: disable=arguments-differ + """ Create users for use in the tests """ + super().setUp() + admin_user = get_user_model().objects.get(username=ADMIN_USERNAME) + self.auth_token = f"JWT {generate_jwt(admin_user, is_restricted=False, scopes=None, filters=None)}" + + def make_url(self, params): """ Return the view URL, with the identifier provided """ return '?'.join([ reverse('third_party_auth_users_api_v2'), - urllib.parse.urlencode(identifier) + urllib.parse.urlencode(params) ]) + @ddt.data( + ({}, 400, ["Must provide one of ['email', 'username']"]), + ({'username': ALICE_USERNAME}, 400, ["Must provide uid"]), + ( + {'username': 'invalid-user', 'uid': f'{ALICE_USERNAME}@gmail.com'}, + 404, + {f"Either user invalid-user or social auth record {ALICE_USERNAME}@gmail.com does not exist."} + ), + ( + {'username': ALICE_USERNAME, 'uid': 'invalid-uid'}, + 404, + {f"Either user {ALICE_USERNAME} or social auth record invalid-uid does not exist."} + ), + ({'username': ALICE_USERNAME, 'uid': f'{ALICE_USERNAME}@gmail.com'}, 204, None), + ) + @ddt.unpack + def test_delete_social_auth_record(self, identifier, expect_code, expect_data): + url = self.make_url(identifier) + response = self.client.delete(url, HTTP_AUTHORIZATION=self.auth_token) + assert response.status_code == expect_code + assert (response.data == expect_data) + @override_settings(EDX_API_KEY=VALID_API_KEY) @ddt.ddt diff --git a/common/djangoapps/third_party_auth/api/views.py b/common/djangoapps/third_party_auth/api/views.py index c1127f8e33..c2b8b0dd6f 100644 --- a/common/djangoapps/third_party_auth/api/views.py +++ b/common/djangoapps/third_party_auth/api/views.py @@ -65,6 +65,7 @@ class BaseUserView(APIView): identifier_kinds = ['email', 'username'] authentication_classes = ( + JwtAuthentication, # Users may want to view/edit the providers used for authentication before they've # activated their account, so we allow inactive users. BearerAuthenticationAllowInactiveUser, @@ -249,6 +250,42 @@ class UserViewV2(BaseUserView): identifier = self.get_identifier_for_requested_user(request) return self.do_get(request, identifier) + def delete(self, request): + """ + Delete given social auth record for a user. + + Args: + request (Request): The HTTP DELETE request + + Request Parameters: + email/username: Must provide one of 'email' or 'username'. If both are provided, + the username will be ignored. + uid: UID of the social auth record to delete + + Return: + JSON serialized list of the providers linked to this user after the delete operation. + + """ + identifier = self.get_identifier_for_requested_user(request) + uid = request.query_params.get("uid") + if not uid: + raise exceptions.ValidationError("Must provide uid") + + is_unprivileged = self.is_unprivileged_query(request, identifier) + + if is_unprivileged: + return Response(status=status.HTTP_403_FORBIDDEN) + + try: + UserSocialAuth.objects.get(**{"user__" + identifier.kind: identifier.value}, uid=uid).delete() + except UserSocialAuth.DoesNotExist: + return Response( + data={f"Either user {identifier.value} or social auth record {uid} does not exist."}, + status=status.HTTP_404_NOT_FOUND + ) + + return Response(status=status.HTTP_204_NO_CONTENT) + def get_identifier_for_requested_user(self, request): """ Return an identifier namedtuple for the requested user. diff --git a/docs/lms-openapi.yaml b/docs/lms-openapi.yaml index 0f0ac22253..d4a94b3d08 100644 --- a/docs/lms-openapi.yaml +++ b/docs/lms-openapi.yaml @@ -8890,10 +8890,35 @@ paths: parameters: [] responses: '200': - description: '' + description: 'JSON serialized list of the providers linked to this user.' tags: - third_party_auth - parameters: [] + delete: + operationId: third_party_auth_v0_users_delete + summary: Delete given social auth record for a user. + description: Allows deleting the given social auth record if it exists for a specified user. + parameters: + - name: uid + in: query + description: UID of the social auth record to delete. + required: true + type: string + responses: + '204': + description: 'No content returned if delete operation successful.' + tags: + - third_party_auth + parameters: + - name: username + in: query + description: Username of user. One of 'email' or 'username'. If both are provided, the username will be ignored. + required: false + type: string + - name: email + in: query + description: Email of user. One of 'email' or 'username'. If both are provided, the username will be ignored. + required: false + type: string /third_party_auth/v0/users/{username}: get: operationId: third_party_auth_v0_users_read