diff --git a/api/base/utils.py b/api/base/utils.py index 9e5f6e1a445..31bc35fd8d4 100644 --- a/api/base/utils.py +++ b/api/base/utils.py @@ -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: diff --git a/api/users/views.py b/api/users/views.py index de413053796..7628dd0ffbd 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -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: diff --git a/api_tests/users/views/test_user_detail.py b/api_tests/users/views/test_user_detail.py index f5a4f0acf6e..188a3593ee1 100644 --- a/api_tests/users/views/test_user_detail.py +++ b/api_tests/users/views/test_user_detail.py @@ -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() @@ -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() @@ -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() @@ -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: diff --git a/osf/models/user.py b/osf/models/user.py index 6fcfbb6e159..ae0b234e227 100644 --- a/osf/models/user.py +++ b/osf/models/user.py @@ -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