def test_get_potentially_retired_user_username_match(retirement_user): """ Check that we can pass in an un-retired username and get the user-to-be-retired back. """ hashed_username = get_retired_username_by_username(retirement_user.username) assert get_potentially_retired_user_by_username_and_hash(retirement_user.username, hashed_username) == retirement_user
def test_can_retire_user_from_credit_request(self): test_parameters = {'hi': 'there'} CreditRequest.objects.create( username=self.user.username, course=self.credit_course, provider=self.provider, parameters=test_parameters, ) credit_request_before_retire = CreditRequest.objects.filter( username=self.user.username )[0] self.assertEqual(credit_request_before_retire.parameters, test_parameters) user_was_retired = CreditRequest.retire_user( original_username=self.user.username, retired_username=get_retired_username_by_username(self.user.username) ) credit_request_before_retire.refresh_from_db() credit_requests_after_retire = CreditRequest.objects.filter( username=self.user.username ) self.assertTrue(user_was_retired) self.assertEqual(credit_request_before_retire.parameters, {}) self.assertFalse(credit_requests_after_retire.exists())
def test_get_retired_username(retirement_user): """ Basic testing of getting retired usernames. The hasher is opaque to us, we just care that it's succeeding and using our format. """ hashed_username = get_retired_username_by_username(retirement_user.username) check_username_against_fmt(hashed_username)
def test_get_retired_username(retirement_user): """ Basic testing of getting retired usernames. The hasher is opaque to us, we just care that it's succeeding and using our format. """ hashed_username = get_retired_username_by_username(retirement_user.username) check_username_against_fmt(hashed_username)
def test_get_potentially_retired_user_username_match(retirement_user): """ Check that we can pass in an un-retired username and get the user-to-be-retired back. """ hashed_username = get_retired_username_by_username(retirement_user.username) assert get_potentially_retired_user_by_username_and_hash(retirement_user.username, hashed_username) == retirement_user
def create_retirement(cls, user): """ Creates a UserRetirementStatus for the given user, in the correct initial state. Will fail if the user already has a UserRetirementStatus row or if states are not yet populated. """ try: pending = RetirementState.objects.all().order_by('state_execution_order')[0] except IndexError: raise RetirementStateError('Default state does not exist! Populate retirement states to retire users.') if cls.objects.filter(user=user).exists(): raise RetirementStateError('User {} already has a retirement status row!'.format(user)) retired_username = get_retired_username_by_username(user.username) retired_email = get_retired_email_by_email(user.email) UserRetirementRequest.create_retirement_request(user) return cls.objects.create( user=user, original_username=user.username, original_email=user.email, original_name=user.profile.name, retired_username=retired_username, retired_email=retired_email, current_state=pending, last_state=pending, responses='Created in state {} by create_retirement'.format(pending) )
def test_can_retire_user_from_credit_request(self): test_parameters = {'hi': 'there'} CreditRequest.objects.create( username=self.user.username, course=self.credit_course, provider=self.provider, parameters=test_parameters, ) credit_request_before_retire = CreditRequest.objects.filter( username=self.user.username)[0] self.assertEqual(credit_request_before_retire.parameters, test_parameters) user_was_retired = CreditRequest.retire_user( original_username=self.user.username, retired_username=get_retired_username_by_username( self.user.username)) credit_request_before_retire.refresh_from_db() credit_requests_after_retire = CreditRequest.objects.filter( username=self.user.username) self.assertTrue(user_was_retired) self.assertEqual(credit_request_before_retire.parameters, {}) self.assertFalse(credit_requests_after_retire.exists())
def setUp(self): super(CreditRequirementStatusTests, self).setUp() self.course_key = CourseKey.from_string("edX/DemoX/Demo_Course") self.old_username = "******" self.retired_username = get_retired_username_by_username( self.old_username) self.credit_course = add_credit_course(self.course_key)
def post(self, request): """ POST /api/user/v1/accounts/retire/ { 'username': '******' } Retires the user with the given username. This includes retiring this username, the associates email address, and any other PII associated with this user. """ username = request.data['username'] if is_username_retired(username): return Response(status=status.HTTP_404_NOT_FOUND) try: retirement_status = UserRetirementStatus.get_retirement_for_retirement_action(username) user = retirement_status.user retired_username = retirement_status.retired_username or get_retired_username_by_username(username) retired_email = retirement_status.retired_email or get_retired_email_by_email(user.email) original_email = retirement_status.original_email # Retire core user/profile information self.clear_pii_from_userprofile(user) self.delete_users_profile_images(user) self.delete_users_country_cache(user) # Retire data from Enterprise models self.retire_users_data_sharing_consent(username, retired_username) self.retire_sapsf_data_transmission(user) self.retire_user_from_pending_enterprise_customer_user(user, retired_email) self.retire_entitlement_support_detail(user) # Retire misc. models that may contain PII of this user SoftwareSecurePhotoVerification.retire_user(user.id) PendingEmailChange.delete_by_user_value(user, field='user') UserOrgTag.delete_by_user_value(user, field='user') # Retire any objects linked to the user via their original email CourseEnrollmentAllowed.delete_by_user_value(original_email, field='email') UnregisteredLearnerCohortAssignments.delete_by_user_value(original_email, field='email') # TODO: Password Reset links - https://openedx.atlassian.net/browse/PLAT-2104 # TODO: Delete OAuth2 records - https://openedx.atlassian.net/browse/EDUCATOR-2703 user.first_name = '' user.last_name = '' user.is_active = False user.username = retired_username user.save() except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def post(self, request): """ POST /api/user/v1/accounts/retire/ { 'username': '******' } Retires the user with the given username. This includes retiring this username, the associates email address, and any other PII associated with this user. """ username = request.data['username'] if is_username_retired(username): return Response(status=status.HTTP_404_NOT_FOUND) try: retirement_status = UserRetirementStatus.get_retirement_for_retirement_action(username) user = retirement_status.user retired_username = retirement_status.retired_username or get_retired_username_by_username(username) retired_email = retirement_status.retired_email or get_retired_email_by_email(user.email) original_email = retirement_status.original_email # Retire core user/profile information self.clear_pii_from_userprofile(user) self.delete_users_profile_images(user) self.delete_users_country_cache(user) # Retire data from Enterprise models self.retire_users_data_sharing_consent(username, retired_username) self.retire_sapsf_data_transmission(user) self.retire_user_from_pending_enterprise_customer_user(user, retired_email) self.retire_entitlement_support_detail(user) # Retire misc. models that may contain PII of this user SoftwareSecurePhotoVerification.retire_user(user.id) PendingEmailChange.delete_by_user_value(user, field='user') UserOrgTag.delete_by_user_value(user, field='user') # Retire any objects linked to the user via their original email CourseEnrollmentAllowed.delete_by_user_value(original_email, field='email') UnregisteredLearnerCohortAssignments.delete_by_user_value(original_email, field='email') # TODO: Password Reset links - https://openedx.atlassian.net/browse/PLAT-2104 # TODO: Delete OAuth2 records - https://openedx.atlassian.net/browse/EDUCATOR-2703 user.first_name = '' user.last_name = '' user.is_active = False user.username = retired_username user.save() except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def test_get_potentially_retired_user_username_match(): """ Check that we can pass in an un-retired username and get the user-to-be-retired back. """ user = UserFactory() hashed_username = get_retired_username_by_username(user.username) assert get_potentially_retired_user_by_username_and_hash(user.username, hashed_username) == user
def test_get_retired_username_status_exists(retirement_user, retirement_status): # pylint: disable=redefined-outer-name """ Checks that a retired username is gotten from a UserRetirementStatus object when one already exists for a user. """ hashed_username = get_retired_username_by_username(retirement_user.username) check_username_against_fmt(hashed_username) assert retirement_status.retired_username == hashed_username
def test_get_retired_username_status_exists(retirement_user, retirement_status): # pylint: disable=redefined-outer-name """ Checks that a retired username is gotten from a UserRetirementStatus object when one already exists for a user. """ hashed_username = get_retired_username_by_username(retirement_user.username) check_username_against_fmt(hashed_username) assert retirement_status.retired_username == hashed_username
def post(self, request): """ POST /api/user/v1/accounts/retire/ { 'username': '******' } Retires the user with the given username. This includes retiring this username, the associated email address, and any other PII associated with this user. """ username = request.data['username'] try: retirement_status = UserRetirementStatus.get_retirement_for_retirement_action(username) user = retirement_status.user retired_username = retirement_status.retired_username or get_retired_username_by_username(username) retired_email = retirement_status.retired_email or get_retired_email_by_email(user.email) original_email = retirement_status.original_email # Retire core user/profile information self.clear_pii_from_userprofile(user) self.delete_users_profile_images(user) self.delete_users_country_cache(user) # Retire data from Enterprise models self.retire_users_data_sharing_consent(username, retired_username) self.retire_sapsf_data_transmission(user) self.retire_degreed_data_transmission(user) self.retire_user_from_pending_enterprise_customer_user(user, retired_email) self.retire_entitlement_support_detail(user) # Retire misc. models that may contain PII of this user PendingEmailChange.delete_by_user_value(user, field='user') UserOrgTag.delete_by_user_value(user, field='user') # Retire any objects linked to the user via their original email CourseEnrollmentAllowed.delete_by_user_value(original_email, field='email') UnregisteredLearnerCohortAssignments.delete_by_user_value(original_email, field='email') # This signal allows code in higher points of LMS to retire the user as necessary USER_RETIRE_LMS_CRITICAL.send(sender=self.__class__, user=user) user.first_name = '' user.last_name = '' user.is_active = False user.username = retired_username user.save() except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def test_get_potentially_retired_user_does_not_exist(): """ Check that the call to get a user with a non-existent username and hashed username bubbles up User.DoesNotExist """ fake_username = "******" hashed_username = get_retired_username_by_username(fake_username) with pytest.raises(User.DoesNotExist): get_potentially_retired_user_by_username_and_hash(fake_username, hashed_username)
def test_get_potentially_retired_user_does_not_exist(): """ Check that the call to get a user with a non-existent username and hashed username bubbles up User.DoesNotExist """ fake_username = "******" hashed_username = get_retired_username_by_username(fake_username) with pytest.raises(User.DoesNotExist): get_potentially_retired_user_by_username_and_hash(fake_username, hashed_username)
def test_is_username_retired_is_retired(retirement_user): """ Check functionality of is_username_retired when username is retired """ original_username = retirement_user.username retired_username = get_retired_username_by_username(retirement_user.username) # Fake username retirement. retirement_user.username = retired_username retirement_user.save() assert is_username_retired(original_username)
def test_is_username_retired_is_retired(retirement_user): """ Check functionality of is_username_retired when username is retired """ original_username = retirement_user.username retired_username = get_retired_username_by_username(retirement_user.username) # Fake username retirement. retirement_user.username = retired_username retirement_user.save() assert is_username_retired(original_username)
def _fake_logged_out_user(user, retire_username=False): """ Simulate the initial logout retirement endpoint. """ # By default, do not change the username to the retired hash version because # that is not what happens upon actual retirement requests immediately after # logout. if retire_username: user.username = get_retired_username_by_username(user.username) user.email = get_retired_email_by_email(user.email) user.set_unusable_password() user.save()
def _fake_logged_out_user(user, retire_username=False): """ Simulate the initial logout retirement endpoint. """ # By default, do not change the username to the retired hash version because # that is not what happens upon actual retirement requests immediately after # logout. if retire_username: user.username = get_retired_username_by_username(user.username) user.email = get_retired_email_by_email(user.email) user.set_unusable_password() user.save()
def test_is_username_retired_is_retired(): """ Check functionality of is_username_retired when username is retired """ user = UserFactory() original_username = user.username retired_username = get_retired_username_by_username(user.username) # Fake username retirement. user.username = retired_username user.save() assert is_username_retired(original_username)
def test_is_username_retired_is_retired(): """ Check functionality of is_username_retired when username is retired """ user = UserFactory() original_username = user.username retired_username = get_retired_username_by_username(user.username) # Fake username retirement. user.username = retired_username user.save() assert is_username_retired(original_username)
def post(self, request): """ POST /api/user/v1/accounts/retire/ { 'username': '******' } Retires the user with the given username. This includes retiring this username, the associates email address, and any other PII associated with this user. """ username = request.data['username'] if is_username_retired(username): return Response(status=status.HTTP_404_NOT_FOUND) try: retirement_status = UserRetirementStatus.get_retirement_for_retirement_action( username) user = retirement_status.user retired_username = retirement_status.retired_username or get_retired_username_by_username( username) retired_email = retirement_status.retired_email or get_retired_email_by_email( user.email) self.clear_pii_from_userprofile(user) self.delete_users_profile_images(user) self.delete_users_country_cache(user) self.retire_users_data_sharing_consent(username, retired_username) self.retire_sapsf_data_transmission(user) self.retire_user_from_pending_enterprise_customer_user( user, retired_email) self.retire_entitlement_support_detail(user) # TODO: Password Reset links - https://openedx.atlassian.net/browse/PLAT-2104 # TODO: Delete OAuth2 records - https://openedx.atlassian.net/browse/EDUCATOR-2703 user.first_name = '' user.last_name = '' user.is_active = False user.username = retired_username user.save() except UserRetirementStatus.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except RetirementStateError as exc: return Response(text_type(exc), status=status.HTTP_400_BAD_REQUEST) except Exception as exc: # pylint: disable=broad-except return Response(text_type(exc), status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response(status=status.HTTP_204_NO_CONTENT)
def test_get_potentially_retired_user_hashed_match(retirement_user): """ Check that we can pass in a hashed username and get the user-to-be-retired back. """ orig_username = retirement_user.username hashed_username = get_retired_username_by_username(orig_username) # Fake username retirement. retirement_user.username = hashed_username retirement_user.save() # Check to find the user by original username should fail, # 2nd check by hashed username should succeed. assert get_potentially_retired_user_by_username_and_hash(orig_username, hashed_username) == retirement_user
def test_retired_username(self): """ Ensure that a retired username cannot be registered again. """ user = UserFactory() orig_username = user.username # Fake retirement of the username. user.username = get_retired_username_by_username(orig_username) user.save() # Attempt to create another account with the same username that's been retired. self.url_params['username'] = orig_username response = self.client.post(self.url, self.url_params) self._validate_exiting_username_response(orig_username, response, self.INVALID_ERR_MSG[0], self.INVALID_ERR_MSG[1])
def test_retired_username(self): """ Ensure that a retired username cannot be registered again. """ user = UserFactory() orig_username = user.username # Fake retirement of the username. user.username = get_retired_username_by_username(orig_username) user.save() # Attempt to create another account with the same username that's been retired. self.url_params['username'] = orig_username response = self.client.post(self.url, self.url_params) self._validate_exiting_username_response(orig_username, response)
def test_get_potentially_retired_user_hashed_match(retirement_user): """ Check that we can pass in a hashed username and get the user-to-be-retired back. """ orig_username = retirement_user.username hashed_username = get_retired_username_by_username(orig_username) # Fake username retirement. retirement_user.username = hashed_username retirement_user.save() # Check to find the user by original username should fail, # 2nd check by hashed username should succeed. assert get_potentially_retired_user_by_username_and_hash(orig_username, hashed_username) == retirement_user
def _assert_retirementstatus_is_user(retirement, user): """ Helper function to compare a newly created UserRetirementStatus object to expected values for the given user. """ pending = RetirementState.objects.all().order_by('state_execution_order')[0] retired_username = get_retired_username_by_username(user.username) retired_email = get_retired_email_by_email(user.email) assert retirement.user == user assert retirement.original_username == user.username assert retirement.original_email == user.email assert retirement.original_name == user.profile.name assert retirement.retired_username == retired_username assert retirement.retired_email == retired_email assert retirement.current_state == pending assert retirement.last_state == pending assert pending.state_name in retirement.responses
def _assert_retirementstatus_is_user(retirement, user): """ Helper function to compare a newly created UserRetirementStatus object to expected values for the given user. """ pending = RetirementState.objects.all().order_by( 'state_execution_order')[0] retired_username = get_retired_username_by_username(user.username) retired_email = get_retired_email_by_email(user.email) assert retirement.user == user assert retirement.original_username == user.username assert retirement.original_email == user.email assert retirement.original_name == user.profile.name assert retirement.retired_username == retired_username assert retirement.retired_email == retired_email assert retirement.current_state == pending assert retirement.last_state == pending assert pending.state_name in retirement.responses
def retire_user(cls, username_to_retire): """ Retire a user by anonymizing Args: username_to_retire(str): Username of the user """ requirement_statuses = cls.objects.filter(username=username_to_retire) retirement_username = get_retired_username_by_username( username_to_retire) if requirement_statuses.exists(): requirement_statuses.update(username=retirement_username, reason={}) return True else: log.info( u'Can not retire requirement statuses for user "%s" because the user could not be found', username_to_retire) return False
def retire_user(cls, username_to_retire): """ Retire a user by anonymizing Args: username_to_retire(str): Username of the user """ requirement_statuses = cls.objects.filter(username=username_to_retire) retirement_username = get_retired_username_by_username(username_to_retire) if requirement_statuses.exists(): requirement_statuses.update( username=retirement_username, reason={} ) return True else: log.info( u'Can not retire requirement statuses for user "%s" because the user could not be found', username_to_retire ) return False
def _create_retirement(self, state, create_datetime=None): """ Helper method to create a RetirementStatus with useful defaults """ if create_datetime is None: create_datetime = datetime.datetime.now(pytz.UTC) - datetime.timedelta(days=8) user = UserFactory() return UserRetirementStatus.objects.create( user=user, original_username=user.username, original_email=user.email, original_name=user.profile.name, retired_username=get_retired_username_by_username(user.username), retired_email=get_retired_email_by_email(user.email), current_state=state, last_state=state, responses="", created=create_datetime, modified=create_datetime )
def test_cannot_retire_nonexistent_user(self): test_parameters = {'hi': 'there'} CreditRequest.objects.create( username=self.user.username, course=self.credit_course, provider=self.provider, parameters=test_parameters, ) another_user = UserFactory.create() credit_request_before_retire = CreditRequest.objects.filter( username=self.user.username)[0] was_retired = CreditRequest.retire_user( original_username=another_user.username, retired_username=get_retired_username_by_username( another_user.username)) credit_request_before_retire.refresh_from_db() self.assertFalse(was_retired) self.assertEqual(credit_request_before_retire.parameters, test_parameters)
def test_cannot_retire_nonexistent_user(self): test_parameters = {'hi': 'there'} CreditRequest.objects.create( username=self.user.username, course=self.credit_course, provider=self.provider, parameters=test_parameters, ) another_user = UserFactory.create() credit_request_before_retire = CreditRequest.objects.filter( username=self.user.username )[0] was_retired = CreditRequest.retire_user( original_username=another_user.username, retired_username=get_retired_username_by_username(another_user.username) ) credit_request_before_retire.refresh_from_db() self.assertFalse(was_retired) self.assertEqual(credit_request_before_retire.parameters, test_parameters)
def fake_retirement(user): """ Makes an attempt to put user for the given user into a "COMPLETED" retirement state by faking important parts of retirement. Use to test idempotency for retirement API calls. Since there are many configurable retirement steps this is only a "best guess" and may need additional changes added to more accurately reflect post-retirement state. """ # Deactivate / logout and hash username & email UserSocialAuth.objects.filter(user_id=user.id).delete() user.first_name = '' user.last_name = '' user.is_active = False user.username = get_retired_username_by_username(user.username) user.email = get_retired_email_by_email(user.email) user.set_unusable_password() user.save() # Clear profile AccountRetirementView.clear_pii_from_userprofile(user) # Unenroll from all courses api.unenroll_user_from_all_courses(user.username)
def setUp(self): super(CreditRequirementStatusTests, self).setUp() self.course_key = CourseKey.from_string("edX/DemoX/Demo_Course") self.old_username = "******" self.retired_username = get_retired_username_by_username(self.old_username) self.credit_course = add_credit_course(self.course_key)
def _fake_logged_out_user(user): # Simulate the initial logout retirement endpoint. user.username = get_retired_username_by_username(user.username) user.email = get_retired_email_by_email(user.email) user.set_unusable_password() user.save()
def build_post(self, user): retired_username = get_retired_username_by_username(user.username) return {'retired_username': retired_username}