def test_user_tries_deleting_his_profile_but_it_fails_partially( user_gql_client, service_1, service_2, mocker ): """Test an edge case where dry runs passes for all connected services, but the proper service connection delete fails for a single connected service. All other connected services should still get deleted. """ def mock_delete_gdpr_data(self, api_token, dry_run=False): if self.service.name == service_2.name and not dry_run: raise requests.HTTPError("Such big fail! :(") mocker.patch.object( ServiceConnection, "delete_gdpr_data", autospec=True, side_effect=mock_delete_gdpr_data, ) mocker.patch.object( TunnistamoTokenExchange, "fetch_api_tokens", return_value=GDPR_API_TOKENS ) profile = ProfileFactory(user=user_gql_client.user) ServiceConnectionFactory(profile=profile, service=service_1) ServiceConnectionFactory(profile=profile, service=service_2) executed = user_gql_client.execute(DELETE_MY_PROFILE_MUTATION) expected_data = {"deleteMyProfile": None} assert ServiceConnection.objects.count() == 1 assert ServiceConnection.objects.first().service == service_2 assert dict(executed["data"]) == expected_data assert_match_error_code(executed, CONNECTED_SERVICE_DELETION_FAILED_ERROR)
def test_user_deletion_from_keycloak( user_gql_client, mocker, kc_delete_user_response_code, keycloak_setup ): user = user_gql_client.user profile = ProfileFactory(user=user) def kc_delete_user_response(*args, **kwargs): response = requests.Response() response.status_code = kc_delete_user_response_code response.raise_for_status() mocked_keycloak_delete_user = mocker.patch.object( KeycloakAdminClient, "delete_user", side_effect=kc_delete_user_response ) executed = user_gql_client.execute(DELETE_MY_PROFILE_MUTATION) if kc_delete_user_response_code in [204, 404]: assert executed["data"] == {"deleteMyProfile": {"clientMutationId": None}} assert "errors" not in executed else: assert Profile.objects.filter(pk=profile.pk).exists() assert executed["data"]["deleteMyProfile"] is None assert_match_error_code(executed, "CONNECTED_SERVICE_DELETION_FAILED_ERROR") mocked_keycloak_delete_user.assert_called_once_with(user.uuid)
def test_anon_user_can_not_claim_claimable_profile(anon_user_gql_client): profile = ProfileWithPrimaryEmailFactory( user=None, first_name="John", last_name="Doe" ) claim_token = ClaimTokenFactory(profile=profile) t = Template( """ mutation { claimProfile( input: { token: "${claimToken}" } ) { profile { id } } } """ ) query = t.substitute(claimToken=claim_token.token) executed = anon_user_gql_client.execute(query) assert_match_error_code(executed, "PERMISSION_DENIED_ERROR")
def test_normal_user_can_query_his_own_profile(user_gql_client, service, with_service, with_serviceconnection): profile = ProfileFactory(user=user_gql_client.user) if with_serviceconnection: ServiceConnectionFactory(profile=profile, service=service) query = """ { myProfile { firstName lastName } } """ expected_data = { "myProfile": { "firstName": profile.first_name, "lastName": profile.last_name } } executed = user_gql_client.execute( query, service=service if with_service else None) if with_service and with_serviceconnection: assert executed["data"] == expected_data elif not with_service: assert_match_error_code(executed, "SERVICE_NOT_IDENTIFIED_ERROR") assert executed["data"]["myProfile"] is None else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["myProfile"] is None
def test_user_can_delete_his_profile( user_gql_client, profile_service, service_1, requests_mock, mocker, with_serviceconnection, ): """Deletion is allowed when GDPR URL is set, and service returns a successful status.""" profile = ProfileFactory(user=user_gql_client.user) ServiceConnectionFactory(profile=profile, service=profile_service) if with_serviceconnection: requests_mock.delete( f"{service_1.gdpr_url}{profile.pk}", json={}, status_code=204 ) ServiceConnectionFactory(profile=profile, service=service_1) mocker.patch.object( TunnistamoTokenExchange, "fetch_api_tokens", return_value=GDPR_API_TOKENS ) executed = user_gql_client.execute(DELETE_MY_PROFILE_MUTATION, service=service_1) if with_serviceconnection: expected_data = {"deleteMyProfile": {"clientMutationId": None}} assert executed["data"] == expected_data with pytest.raises(Profile.DoesNotExist): profile.refresh_from_db() with pytest.raises(User.DoesNotExist): user_gql_client.user.refresh_from_db() else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["deleteMyProfile"] is None assert Profile.objects.filter(pk=profile.pk).exists()
def test_giving_non_existing_service_client_id_results_in_object_does_not_exist_error( user_gql_client, ): input_data = service_input_data(uuid.uuid1(), "not_existing") executed = execute_mutation(input_data, user_gql_client) assert_match_error_code(executed, "OBJECT_DOES_NOT_EXIST_ERROR")
def test_staff_user_can_resolve_address_entity( user_gql_client, group, service, with_serviceconnection ): address, variables = _create_address_and_variables(with_serviceconnection, service) user = user_gql_client.user user.groups.add(group) assign_perm("can_view_profiles", group, service) expected_data = { "_entities": [ { "id": address._global_id, "address": address.address, "postalCode": address.postal_code, } ] } executed = user_gql_client.execute( ENTITY_QUERY, variables=variables, service=service, ) if with_serviceconnection: assert executed["data"] == expected_data else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["_entities"] is None
def test_giving_too_long_name_field_causes_a_validation_error( self, field_name, user_gql_client): profile_input = {field_name: "x" * 256} executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "VALIDATION_ERROR")
def test_owner_can_resolve_address_entity( user_gql_client, service, with_service, with_serviceconnection ): address, variables = _create_address_and_variables( with_serviceconnection, service, user=user_gql_client.user ) expected_data = { "_entities": [ { "id": address._global_id, "address": address.address, "postalCode": address.postal_code, } ] } executed = user_gql_client.execute( ENTITY_QUERY, variables=variables, service=service if with_service else None, ) if with_service and with_serviceconnection: assert executed["data"] == expected_data elif not with_service: assert_match_error_code(executed, "SERVICE_NOT_IDENTIFIED_ERROR") assert executed["data"]["_entities"] is None else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["_entities"] is None
def test_normal_user_can_create_temporary_read_access_token_for_profile( self, user_gql_client, service, with_serviceconnection): profile = ProfileFactory(user=user_gql_client.user) if with_serviceconnection: ServiceConnectionFactory(profile=profile, service=service) executed = user_gql_client.execute(self.query, service=service) if with_serviceconnection: token_data = executed["data"][ "createMyProfileTemporaryReadAccessToken"][ "temporaryReadAccessToken"] # Check that an UUID can be parsed from the token uuid.UUID(token_data["token"]) actual_expiration_time = datetime.fromisoformat( token_data["expiresAt"]) expected_expiration_time = timezone.now() + timedelta(days=2) assert_almost_equal(actual_expiration_time, expected_expiration_time, timedelta(seconds=1)) else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"][ "createMyProfileTemporaryReadAccessToken"] is None
def test_staff_user_needs_required_permission_to_access_verified_personal_information( has_needed_permission, amr_claim_value, settings, user_gql_client, profile_with_verified_personal_information, group, service, ): settings.VERIFIED_PERSONAL_INFORMATION_ACCESS_AMR_LIST = [ "authmethod1", "authmethod2", ] ServiceConnectionFactory( profile=profile_with_verified_personal_information, service=service) user = user_gql_client.user user.groups.add(group) assign_perm("can_view_profiles", group, service) if has_needed_permission: assign_perm("can_view_verified_personal_information", group, service) t = Template(""" { profile(id: "${id}") { verifiedPersonalInformation { firstName } } } """) query = t.substitute(id=relay.Node.to_global_id( ProfileNode._meta.name, profile_with_verified_personal_information.id)) token_payload = {"loa": "substantial", "amr": amr_claim_value} executed = user_gql_client.execute(query, auth_token_payload=token_payload, service=service) if (has_needed_permission and amr_claim_value in settings.VERIFIED_PERSONAL_INFORMATION_ACCESS_AMR_LIST): assert "errors" not in executed assert executed["data"] == { "profile": { "verifiedPersonalInformation": { "firstName": profile_with_verified_personal_information. verified_personal_information.first_name } } } else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"] == { "profile": { "verifiedPersonalInformation": None } }
def test_adding_address_with_invalid_country_code_value_causes_a_validation_error( self, country_code, user_gql_client, address_data): address_data["country_code"] = country_code new_address = to_graphql_object(address_data) profile_input = {"addAddresses": [new_address]} executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "VALIDATION_ERROR")
def test_adding_address_with_too_long_field_causes_a_validation_error( self, field_name, user_gql_client, address_data): address_data[field_name] = "x" * 130 new_address = to_graphql_object(address_data) profile_input = {"addAddresses": [new_address]} executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "VALIDATION_ERROR")
def test_deny_invalid_primary_email_address(test_email, user_gql_client): user_id = uuid.uuid1() input_data = primary_email_input_data(user_id, test_email) executed = execute_mutation(input_data, user_gql_client) assert_match_error_code(executed, "VALIDATION_ERROR") assert executed["data"]["prof"] is None
def test_user_gets_error_when_deleting_non_existent_profile(user_gql_client): profile = ProfileFactory(user=user_gql_client.user) profile.delete() executed = user_gql_client.execute(DELETE_MY_PROFILE_MUTATION) expected_data = {"deleteMyProfile": None} assert dict(executed["data"]) == expected_data assert_match_error_code(executed, PROFILE_DOES_NOT_EXIST_ERROR)
def test_anonymous_user_can_not_resolve_profile_entity( anon_user_gql_client, service, with_service, with_serviceconnection ): profile, variables = _create_profile_and_variables(with_serviceconnection, service) executed = anon_user_gql_client.execute( ENTITY_QUERY, variables=variables, service=service if with_service else None, ) assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["_entities"] is None
def test_giving_invalid_ssn_causes_a_validation_error( self, user_gql_client): profile_input = { "sensitivedata": { "ssn": "101010X1234" }, } executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "VALIDATION_ERROR")
def test_too_low_level_of_assurance_denies_access(self, loa, user_gql_client): profile = ProfileFactory(user=user_gql_client.user) VerifiedPersonalInformationFactory(profile=profile) executed = self._execute_query(user_gql_client, loa) assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["myProfile"][ "verifiedPersonalInformation"] is None
def test_can_not_delete_primary_email(user_gql_client): profile = ProfileWithPrimaryEmailFactory(user=user_gql_client.user) email = profile.emails.first() email_deletes = [to_global_id(type="EmailNode", id=email.id)] executed = user_gql_client.execute( EMAILS_MUTATION, variables={"profileInput": { "removeEmails": email_deletes }}) assert_match_error_code(executed, "PROFILE_MUST_HAVE_PRIMARY_EMAIL")
def test_using_non_existing_token_produces_an_object_does_not_exist_error( user_gql_client, ): non_existing_token = "e5d47102-a29b-441d-adbc-c6e4e762ffe1" variables = { "token": non_existing_token, } executed = user_gql_client.execute(CLAIM_PROFILE_MUTATION, variables=variables) assert_match_error_code(executed, "OBJECT_DOES_NOT_EXIST_ERROR")
def test_api_tokens_missing(user_gql_client, service_1, query_or_delete, mocker): """Missing API token for a service connection that has the query/delete scope set, should be an error.""" mocker.patch.object(TunnistamoTokenExchange, "fetch_api_tokens", return_value={}) profile = ProfileFactory(user=user_gql_client.user) ServiceConnectionFactory(profile=profile, service=service_1) if query_or_delete == "query": executed = user_gql_client.execute(DOWNLOAD_MY_PROFILE_MUTATION) else: executed = user_gql_client.execute(DELETE_MY_PROFILE_MUTATION) assert_match_error_code(executed, MISSING_GDPR_API_TOKEN_ERROR)
def test_adding_invalid_email_address_causes_an_invalid_email_format_error( self, invalid_email, email_data, user_gql_client): profile_input = { "addEmails": [{ "email": invalid_email, "emailType": email_data["email_type"] }], } executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "INVALID_EMAIL_FORMAT")
def test_adding_phone_with_empty_phone_number_causes_a_validation_error( self, user_gql_client, phone_data): profile_input = { "addPhones": [{ "phone": "", "phoneType": phone_data["phone_type"] }], } executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "VALIDATION_ERROR")
def test_can_not_delete_primary_email(user_gql_client): profile = ProfileFactory(user=None) email = EmailFactory(profile=profile, primary=True) claim_token = ClaimTokenFactory(profile=profile) email_deletes = [to_global_id(type="EmailNode", id=email.id)] executed = user_gql_client.execute( CLAIM_PROFILE_MUTATION, variables={ "token": str(claim_token.token), "profileInput": {"removeEmails": email_deletes}, }, ) assert_match_error_code(executed, "PROFILE_MUST_HAVE_PRIMARY_EMAIL")
def test_can_not_change_primary_email_to_non_primary(user_gql_client): profile = ProfileWithPrimaryEmailFactory(user=user_gql_client.user) email = profile.emails.first() email_updates = [{ "id": to_global_id(type="EmailNode", id=email.id), "primary": False }] executed = user_gql_client.execute( EMAILS_MUTATION, variables={"profileInput": { "updateEmails": email_updates }}) assert_match_error_code(executed, "PROFILE_MUST_HAVE_PRIMARY_EMAIL")
def test_non_owner_user_can_not_resolve_address_entity( user_gql_client, service, with_service, with_serviceconnection ): address, variables = _create_address_and_variables(with_serviceconnection, service) executed = user_gql_client.execute( ENTITY_QUERY, variables=variables, service=service if with_service else None, ) if not with_service: assert_match_error_code(executed, "SERVICE_NOT_IDENTIFIED_ERROR") assert executed["data"]["_entities"] is None else: assert_match_error_code(executed, "PERMISSION_DENIED_ERROR") assert executed["data"]["_entities"] is None
def test_not_specifying_requesters_service_results_in_permission_denied_error( user_gql_client, ): query = """ { profiles { edges { node { firstName } } } } """ executed = user_gql_client.execute(query) assert_match_error_code(executed, "PERMISSION_DENIED_ERROR")
def test_updating_to_invalid_email_address_causes_an_invalid_email_format_error( self, invalid_email, user_gql_client): profile = self._get_profile(user_gql_client.user) email = EmailFactory(profile=profile, primary=False) profile_input = { "updateEmails": [{ "id": to_global_id(type="EmailNode", id=email.id), "email": invalid_email, }], } executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "INVALID_EMAIL_FORMAT")
def test_updating_phone_number_with_empty_phone_number_causes_a_validation_error( self, user_gql_client, empty_string_value): profile = self._get_profile(user_gql_client.user) phone = PhoneFactory(profile=profile) profile_input = { "updatePhones": [{ "id": to_global_id(type="PhoneNode", id=phone.id), "phone": empty_string_value, }], } executed = self._execute_query(user_gql_client, profile_input) assert_match_error_code(executed, "VALIDATION_ERROR")
def test_can_not_change_primary_email_to_non_primary(user_gql_client): profile = ProfileFactory(user=None) email = EmailFactory(profile=profile, primary=True) claim_token = ClaimTokenFactory(profile=profile) variables = { "token": str(claim_token.token), "profileInput": { "updateEmails": [ {"id": to_global_id(type="EmailNode", id=email.id), "primary": False} ], }, } executed = user_gql_client.execute(CLAIM_PROFILE_MUTATION, variables=variables) assert_match_error_code(executed, "PROFILE_MUST_HAVE_PRIMARY_EMAIL")