diff --git a/changelog.d/20149.bugfix b/changelog.d/20149.bugfix new file mode 100644 index 0000000000..2440618acf --- /dev/null +++ b/changelog.d/20149.bugfix @@ -0,0 +1 @@ +Fix the `/profile` endpoint, when querying custom fields, returning a 500 error instead of 404 when the profile does not exist at all. \ No newline at end of file diff --git a/synapse/storage/databases/main/profile.py b/synapse/storage/databases/main/profile.py index d5e35fa7ca..fe48636ffb 100644 --- a/synapse/storage/databases/main/profile.py +++ b/synapse/storage/databases/main/profile.py @@ -291,11 +291,14 @@ class ProfileWorkerStore(SQLBaseStore): (field_path, field_name, user_id.localpart), ) + row = txn.fetchone() + if row is None: + # The user has no profile at all. + raise StoreError(404, "No row found") + # Test exists first since value being None is used for both # missing and a null JSON value. - exists, value = cast( - tuple[bool, JsonValue | dict[str, JsonValue]], txn.fetchone() - ) + exists, value = cast(tuple[bool, JsonValue | dict[str, JsonValue]], row) if not exists: raise StoreError(404, "No row found") return value @@ -310,9 +313,14 @@ class ProfileWorkerStore(SQLBaseStore): (field_path, field_path, user_id.localpart), ) + row = txn.fetchone() + if row is None: + # The user has no profile at all. + raise StoreError(404, "No row found") + # If value_type is None, then the value did not exist. value_type, value = cast( - tuple[str | None, JsonValue | dict[str, JsonValue]], txn.fetchone() + tuple[str | None, JsonValue | dict[str, JsonValue]], row ) if not value_type: raise StoreError(404, "No row found") diff --git a/tests/storage/test_profile.py b/tests/storage/test_profile.py index b65af28962..0fc40794c7 100644 --- a/tests/storage/test_profile.py +++ b/tests/storage/test_profile.py @@ -19,9 +19,12 @@ # # +from http import HTTPStatus + from twisted.internet.testing import MemoryReactor from synapse.api.constants import ProfileFields +from synapse.api.errors import StoreError from synapse.server import HomeServer from synapse.storage.database import LoggingTransaction from synapse.storage.engines import PostgresEngine @@ -95,6 +98,20 @@ class ProfileStoreTestCase(unittest.HomeserverTestCase): self.get_success(self.store.get_profile_avatar_url(self.u_frank)) ) + def test_get_profile_field_without_profile(self) -> None: + """ + Getting a custom profile field for a user that has no row in the + `profiles` table at all should raise a 404. + + Regression test (we previously would trigger an unhandled exception). + Can happen for users whose profile was erased upon deactivation. + """ + f = self.get_failure( + self.store.get_profile_field(self.u_frank, "org.example.field"), + StoreError, + ) + self.assertEqual(f.value.code, HTTPStatus.NOT_FOUND) + def test_profiles_bg_migration(self) -> None: """ Test background job that copies entries from column user_id to full_user_id, adding