A listing asked for a user that is not there answers with nothing #64

Closed
opened 2026-08-04 12:54:39 +02:00 by Claude · 1 comment
Collaborator

A listing asked on behalf of a user that is not there answers with nothing.

RequestHelpers.GetUserId (RequestHelpers.cs:67) does not check that the user
exists. It falls back to the caller when none is named, forbids a non-admin
naming somebody else, and otherwise returns the id as given — so a valid guid
naming no row is passed straight through, and the routes that take it go on to
answer as though no user narrowed them.

Measured against a production library, with a guid no user carries:

request upstream ours
/Persons?userId=<unknown> 200, 32576 200, 0
/Search/Hints?userId=<unknown>&searchTerm=blue 200, 5 200, 0
/Genres?userId=<unknown> 200, 46 200, 0
/Items?userId=<unknown> 404 404

/Items agrees because ItemsController looks the user up for itself and
refuses. The by-name and search routes do not, and this server narrows every one
of them to a user that has no libraries — which selects nothing.

Found replaying the committed scenarios against a library that is not the
reference fixture, so the user id they name does not exist here. That is what
made it visible, but the behaviour is not about fixtures: any client holding a
stale user id sees an empty library rather than the answer upstream gives.

A listing asked on behalf of a user that is not there answers with nothing. `RequestHelpers.GetUserId` (`RequestHelpers.cs:67`) does not check that the user exists. It falls back to the caller when none is named, forbids a non-admin naming somebody else, and otherwise returns the id as given — so a valid guid naming no row is passed straight through, and the routes that take it go on to answer as though no user narrowed them. Measured against a production library, with a guid no user carries: | request | upstream | ours | |---|---|---| | `/Persons?userId=<unknown>` | 200, 32576 | 200, **0** | | `/Search/Hints?userId=<unknown>&searchTerm=blue` | 200, 5 | 200, **0** | | `/Genres?userId=<unknown>` | 200, 46 | 200, **0** | | `/Items?userId=<unknown>` | 404 | 404 | `/Items` agrees because `ItemsController` looks the user up for itself and refuses. The by-name and search routes do not, and this server narrows every one of them to a user that has no libraries — which selects nothing. Found replaying the committed scenarios against a library that is not the reference fixture, so the user id they name does not exist here. That is what made it visible, but the behaviour is not about fixtures: any client holding a stale user id sees an empty library rather than the answer upstream gives.
Author
Collaborator

Fixed. Upstream's rule is that an id it cannot resolve narrows nothing,
where this server narrowed to a user that has no libraries — which selects
nothing.

LibraryManager.AddUserToQuery (LibraryManager.cs:1994) is called only when
query.User is not null, and only it sets TopParentIds. So a null user means
no library filter at all. The routes divide three ways, all read from the C#:

route upstream result for an unknown id
/Items looks the user up and throws 404 — already agreed, now guarded by a test
/Items/{id}/Collections if (user is null) return Unauthorized() before the item lookup 401
/Persons, /Genres, /MusicGenres, /Studios, /Artists, /Years, /Search/Hints, /Items/Filters2 null user reaches the query 200, unnarrowed

Fixed outright in the bundle scenario: /Items/{id}/Collections now 401,
/Items/Filters2 all three lists, /Persons count 0 → 32576, /Search/Hints
0 → 24 hints.

A bonus that was not part of this issue: /Items/Filters2 was returning four
empty lists for every user, not only unknown ones. For a real user it now
agrees with upstream exactly — Genres 20, AudioLanguages 16, SubtitleLanguages
51, Tags 0.

Ten tests, of which three are deliberate guards that pass either way: /Items
still 404s, an unnamed id still reads the caller, and a restricted account still
sees only its own library. Removing narrowing for a real user is the regression
this could most easily have caused.

The scenario count rose to 23 because two pre-existing defects were invisible
while these routes answered emptily — filed as #67 and #68.

Fixed. Upstream's rule is that an id it cannot resolve narrows **nothing**, where this server narrowed to a user that has no libraries — which selects nothing. `LibraryManager.AddUserToQuery` (`LibraryManager.cs:1994`) is called only when `query.User is not null`, and only it sets `TopParentIds`. So a null user means no library filter at all. The routes divide three ways, all read from the C#: | route | upstream | result for an unknown id | |---|---|---| | `/Items` | looks the user up and throws | 404 — already agreed, now guarded by a test | | `/Items/{id}/Collections` | `if (user is null) return Unauthorized()` before the item lookup | **401** | | `/Persons`, `/Genres`, `/MusicGenres`, `/Studios`, `/Artists`, `/Years`, `/Search/Hints`, `/Items/Filters2` | null user reaches the query | **200, unnarrowed** | Fixed outright in the `bundle` scenario: `/Items/{id}/Collections` now 401, `/Items/Filters2` all three lists, `/Persons` count 0 → 32576, `/Search/Hints` 0 → 24 hints. **A bonus that was not part of this issue:** `/Items/Filters2` was returning four empty lists for *every* user, not only unknown ones. For a real user it now agrees with upstream exactly — Genres 20, AudioLanguages 16, SubtitleLanguages 51, Tags 0. Ten tests, of which three are deliberate guards that pass either way: `/Items` still 404s, an unnamed id still reads the caller, and a restricted account still sees only its own library. Removing narrowing for a real user is the regression this could most easily have caused. The scenario count rose to 23 because two pre-existing defects were invisible while these routes answered emptily — filed as #67 and #68.
Sign in to join this conversation.
No description provided.