From 95efbe21a59ca0a1b317f03a4a630e3beaf54cf5 Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Thu, 20 Aug 2026 13:15:17 +0200 Subject: [PATCH 1/2] Fix 502 on GDPR delete for users with guid-less legacy nodes --- osf/models/mixins.py | 5 +++++ osf_tests/test_user.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/osf/models/mixins.py b/osf/models/mixins.py index 1fe75420fa2..d39bd1a19d2 100644 --- a/osf/models/mixins.py +++ b/osf/models/mixins.py @@ -536,6 +536,11 @@ def get_addons(self, service_type: str | None = None, in_request_context: bool = lambda x: x is not None, (self.get_addon(addon) for addon in self.OSF_HOSTED_ADDONS) ) + if not self._id: + # Resources without a guid (e.g. legacy nodes without guids) can't be + # associated with addons in GravyValet, which needs a guid to build + # the resource's semantic IRI. + return osf_addons return itertools.chain(osf_addons, self._get_addons_from_gv(requesting_user_id=user_id, service_type=service_type, auth=auth)) return [_f for _f in [ diff --git a/osf_tests/test_user.py b/osf_tests/test_user.py index 27a447d6681..b1957809d00 100644 --- a/osf_tests/test_user.py +++ b/osf_tests/test_user.py @@ -13,6 +13,7 @@ import itsdangerous import pytest from importlib import import_module +from waffle.testutils import override_flag from framework.auth.exceptions import ExpiredTokenError, InvalidTokenError, ChangePasswordError from framework.auth.signals import user_account_merged @@ -42,6 +43,7 @@ from osf.utils.names import impute_names_model from osf.utils import permissions from osf.exceptions import ValidationError, BlockedEmailError, UserStateError, InstitutionAffiliationStateError +from osf.features import ENABLE_GV from .utils import capture_signals from .factories import ( @@ -2326,6 +2328,16 @@ def test_cant_gdpr_delete_shared_node_if_only_admin(self, user, project_user_is_ assert exc_info.value.args[0] == 'You cannot delete Node {} because it would' \ ' be a Node with contributors, but with no admin.'.format(project_user_is_only_admin._id) + def test_can_gdpr_delete_shared_node_without_guid(self, user, project_with_two_admins): + project_with_two_admins.guids.all().delete() + node = AbstractNode.objects.get(pk=project_with_two_admins.pk) + assert node._id is None + + with override_flag(ENABLE_GV, active=True): + user.gdpr_delete() + + assert user.nodes.all().count() == 0 + def test_cant_gdpr_delete_with_addon_credentials(self, user, project_with_two_admins_and_addon_credentials): with pytest.raises(UserStateError) as exc_info: From 2b1df1101fbc78b8536666aed0fe24a413323267 Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Thu, 20 Aug 2026 19:10:16 +0200 Subject: [PATCH 2/2] Fix 502 on GDPR delete for users with guid-less legacy nodes --- admin/users/views.py | 3 ++- admin_tests/users/test_views.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/admin/users/views.py b/admin/users/views.py index 894efddfc58..94d93f5ba87 100644 --- a/admin/users/views.py +++ b/admin/users/views.py @@ -213,12 +213,13 @@ class UserGDPRDeleteView(UserMixin, View): permission_required = 'osf.change_osfuser' def post(self, request, *args, **kwargs): + user = self.get_object() try: - user = self.get_object() user.gdpr_delete() user.save() except UserStateError as e: messages.warning(request, str(e)) + return redirect(self.get_success_url()) messages.success(request, f'User {user._id} was successfully GDPR deleted') diff --git a/admin_tests/users/test_views.py b/admin_tests/users/test_views.py index a6ea9f07638..6c86d9ed923 100644 --- a/admin_tests/users/test_views.py +++ b/admin_tests/users/test_views.py @@ -19,6 +19,7 @@ UserFactory, AuthUserFactory, ProjectFactory, + PreprintFactory, UnconfirmedUserFactory ) from admin_tests.utilities import setup_view, setup_log_view, setup_form_view @@ -157,6 +158,20 @@ def test_user_with_deleted_node_is_deleted(self): self.user.reload() assert self.user.deleted + def test_gdpr_delete_blocked_by_resource_without_alternate_admin_does_not_report_success(self): + patch_messages(self.request) + + preprint = PreprintFactory(creator=self.user) + other_contrib = UserFactory() + preprint.add_contributor(other_contrib, auth=Auth(self.user), save=True) + + count = AdminLogEntry.objects.count() + self.view().post(self.request) + + self.user.reload() + assert not self.user.deleted + assert AdminLogEntry.objects.count() == count + class TestDisableUser(AdminTestCase): def setUp(self):