Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions api/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ def get_object_or_error(model_or_qs, query_or_pk=None, request=None, display_nam
# users who are unconfirmed or unregistered, but not users who have been
# disabled.
if model_cls is OSFUser and obj.is_disabled:
if getattr(obj, 'gdpr_deleted', False):
raise NotFound
raise UserGone(user=obj)
if check_deleted and (model_cls is not OSFUser and not getattr(obj, 'is_active', True) or getattr(obj, 'is_deleted', False) or getattr(obj, 'deleted', False)):
if display_name is None:
Expand Down
2 changes: 2 additions & 0 deletions api/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def get_user(self, check_permissions=True):
contrib_id, contrib = list(self.request.parents[Contributor].items())[0]
user = contrib.user
if user.is_disabled:
if getattr(user, 'gdpr_deleted', False):
raise NotFound
raise UserGone(user=user)
# Make sure that the contributor ID is correct
if user._id == key:
Expand Down
21 changes: 18 additions & 3 deletions api_tests/users/views/test_user_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -1185,7 +1185,7 @@ def user_two(self):
def test_requesting_as_deactivated_user_returns_400_response(
self, app, user_one):
url = f'/{API_BASE}users/{user_one._id}/'
res = app.get(url, auth=user_one.auth, expect_errors=True)
res = app.get(url, auth=user_one.auth, expect_errors=False)
assert res.status_code == 200
user_one.is_disabled = True
user_one.save()
Expand All @@ -1196,7 +1196,7 @@ def test_requesting_as_deactivated_user_returns_400_response(
def test_unconfirmed_users_return_entire_user_object(
self, app, user_one, user_two):
url = f'/{API_BASE}users/{user_one._id}/'
res = app.get(url, auth=user_two.auth, expect_errors=True)
res = app.get(url, auth=user_two.auth, expect_errors=False)
assert res.status_code == 200
user_one.is_registered = False
user_one.save()
Expand All @@ -1209,7 +1209,7 @@ def test_unconfirmed_users_return_entire_user_object(
def test_requesting_deactivated_user_returns_410_response_and_meta_info(
self, app, user_one, user_two):
url = f'/{API_BASE}users/{user_one._id}/'
res = app.get(url, auth=user_two.auth, expect_errors=True)
res = app.get(url, auth=user_two.auth, expect_errors=False)
assert res.status_code == 200
user_one.is_disabled = True
user_one.save()
Expand All @@ -1223,6 +1223,21 @@ def test_requesting_deactivated_user_returns_410_response_and_meta_info(
res.json['errors'][0]['meta']['profile_image']).netloc == 'secure.gravatar.com'
assert res.json['errors'][0]['detail'] == 'The requested user is no longer available.'

def test_gdpr_deleted_user_returns_404_and_no_meta_info(
self, app, user_one, user_two):
url = f'/{API_BASE}users/{user_one._id}/'
res = app.get(url, auth=user_two.auth, expect_errors=False)
assert res.status_code == 200

user_one.gdpr_delete()
user_one.save()

res = app.get(url, auth=user_two.auth, expect_errors=True)
assert res.status_code == 404
if res.json:
assert 'errors' in res.json
assert 'meta' not in res.json['errors'][0]


@pytest.mark.django_db
class UserProfileMixin:
Expand Down
8 changes: 8 additions & 0 deletions osf/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,14 @@ def _merge_user_draft_registrations(self, user):
draft_reg.remove_permission(user, user_perms)
draft_reg.save()

@property
def gdpr_deleted(self):
if not self.is_disabled:
return False
if self.fullname != 'Deleted user':
return False
return not self.emails.exists()

def deactivate_account(self):
"""
Disables user account, making is_disabled true, while also unsubscribing user
Expand Down
Loading