Skip to content

Commit

Permalink
update gdpr to delete draftregistrations and draftnode
Browse files Browse the repository at this point in the history
  • Loading branch information
John Tordoff committed Oct 9, 2023
1 parent 943dc72 commit 7501822
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 6 deletions.
57 changes: 52 additions & 5 deletions osf/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,12 @@ def gdpr_delete(self):
https://openscience.atlassian.net/wiki/spaces/PRODUC/pages/482803755/GDPR-Related+protocols
"""
from osf.models import Preprint, AbstractNode
from osf.models import (
Preprint,
AbstractNode,
DraftRegistration,
DraftNode
)

user_nodes = self.nodes.exclude(is_deleted=True)
# Validates the user isn't trying to delete things they deliberately made public.
Expand All @@ -1924,7 +1929,7 @@ def gdpr_delete(self):
)
shared_nodes = user_nodes.exclude(id__in=personal_nodes.values_list('id'))

for node in shared_nodes.exclude(type__in=['osf.quickfilesnode', 'osf.draftnode']):
for node in shared_nodes:
alternate_admins = OSFUser.objects.filter(groups__name=node.format_group(ADMIN)).filter(is_active=True).exclude(id=self.id)
if not alternate_admins:
raise UserStateError(
Expand All @@ -1937,21 +1942,63 @@ def gdpr_delete(self):
'have an external account for {} attached to Node {}, '
'which has other contributors.'.format(addon.short_name, node._id))

# Validates that the user isn't trying to delete things DraftRegistration they are the only admin on.
personal_draft_registrations = (
DraftRegistration.objects.annotate(contrib_count=Count('_contributors'))
.filter(contrib_count__lte=1)
.filter(_contributors=self)
.filter(deleted__isnull=True)
)
shared_draft_registrations = self.draft_registrations.exclude(id__in=personal_draft_registrations.values_list('id'))
for node in shared_draft_registrations:
alternate_admins = OSFUser.objects.filter(
groups__name=node.format_group(ADMIN)
).filter(
is_active=True
).exclude(
id=self.id
)
if not alternate_admins:
raise UserStateError(
f'You cannot delete DraftRegistration {node._id} because it would be a node with contributors, but with no '
f'admin.'
)
for addon in node.get_addons():
if addon.short_name not in ('osfstorage', 'wiki') and \
addon.user_settings and \
addon.user_settings.owner.id == self.id:
raise UserStateError(
f'You cannot delete this user because they have an external account for {addon.short_name}'
f' attached to DraftRegistration {node._id}, which has other contributors.'
)

for group in self.osf_groups:
if not group.managers.exclude(id=self.id).filter(is_registered=True).exists() and group.members.exclude(id=self.id).exists():
raise UserStateError('You cannot delete this user because they are the only registered manager of OSFGroup {} that contains other members.'.format(group._id))

for node in shared_nodes.all():
logger.info('Removing {self._id} as a contributor to node (pk:{node_id})...'.format(self=self, node_id=node.pk))
logger.info(f'Removing {self._id} as a contributor to node (pk:{node.pk})...')
node.remove_contributor(self, auth=Auth(self), log=False)

for draft_registration in shared_draft_registrations.all():
logger.info(f'Removing {self._id} as a contributor to node (pk:{node.pk})...')
draft_registration.remove_contributor(self, auth=Auth(self), log=False)

# This is doesn't to remove identifying info, but ensures other users can't see the deleted user's profile etc.
self.deactivate_account()

# delete all personal nodes (one contributor), bookmarks, quickfiles etc.
for node in personal_nodes.all():
logger.info('Soft-deleting node (pk: {node_id})...'.format(node_id=node.pk))
node.remove_node(auth=Auth(self))
if isinstance(node, DraftNode):
logger.info(f'Hard-deleting draftnode (pk: {node.pk})...')
node.delete()
else:
logger.info(f'Soft-deleting node (pk: {node.pk})...')
node.remove_node(auth=Auth(self))

for draft_registration in personal_draft_registrations.all():
logger.info(f'Hard-deleting draft registrations (pk: {draft_registration.pk})...')
draft_registration.delete()

for group in self.osf_groups:
if len(group.managers) == 1 and group.managers[0] == self:
Expand Down
42 changes: 41 additions & 1 deletion osf_tests/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
NotableDomain,
PreprintContributor,
DraftRegistrationContributor,
DraftRegistration,
DraftNode,
UserSessionMap,
)
from osf.models.institution_affiliation import get_user_by_institution_identity
Expand Down Expand Up @@ -66,7 +68,8 @@
UnregUserFactory,
UserFactory,
RegistrationFactory,
PreprintFactory
PreprintFactory,
DraftNodeFactory
)
from tests.base import OsfTestCase
from tests.utils import run_celery_tasks
Expand Down Expand Up @@ -2387,6 +2390,12 @@ def registration(self, user):
registration.save()
return registration

@pytest.fixture()
def registration_with_draft_node(self, user, registration):
registration.branched_from = DraftNodeFactory(creator=user)
registration.save()
return registration

@pytest.fixture()
def project(self, user):
project = ProjectFactory(creator=user)
Expand Down Expand Up @@ -2433,11 +2442,42 @@ def test_can_gdpr_delete_personal_nodes(self, user):
user.gdpr_delete()
assert user.nodes.exclude(is_deleted=True).count() == 0

def test_can_gdpr_delete_personal_registrations(self, user, registration_with_draft_node):
assert DraftRegistration.objects.all().count() == 1
assert DraftNode.objects.all().count() == 1

with pytest.raises(UserStateError) as exc_info:
user.gdpr_delete()

assert exc_info.value.args[0] == 'You cannot delete this user because they have one or more registrations.'
assert DraftRegistration.objects.all().count() == 1
assert DraftNode.objects.all().count() == 1

registration_with_draft_node.remove_node(Auth(user))
assert DraftRegistration.objects.all().count() == 1
assert DraftNode.objects.all().count() == 1
user.gdpr_delete()

assert user.nodes.exclude(is_deleted=True).count() == 0
assert DraftRegistration.objects.all().count() == 0
assert DraftNode.objects.all().count() == 0

def test_can_gdpr_delete_shared_nodes_with_multiple_admins(self, user, project_with_two_admins):

user.gdpr_delete()
assert user.nodes.all().count() == 0

def test_can_gdpr_delete_shared_draft_registration_with_multiple_admins(self, user, registration):
other_admin = AuthUserFactory()
draft_registrations = user.draft_registrations.get()
draft_registrations.add_contributor(other_admin, permissions='admin')
assert draft_registrations.contributors.all().count() == 2
registration.delete_registration_tree(save=True)

user.gdpr_delete()
assert draft_registrations.contributors.get() == other_admin
assert user.nodes.filter(deleted__isnull=True).count() == 0

def test_cant_gdpr_delete_registrations(self, user, registration):

with pytest.raises(UserStateError) as exc_info:
Expand Down

0 comments on commit 7501822

Please sign in to comment.