From 9a258c089dcbc68b12fe63479542fc7158ec7a10 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 28 Jul 2026 20:41:21 +0200 Subject: Restrict people, genres, studios and artists to names backed by an item the user can access --- .../Item/BaseItemRepository.TranslateQuery.cs | 62 +++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) (limited to 'Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs') diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs index 47f8a40b9c..8b0b7f37f8 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs @@ -1050,14 +1050,29 @@ public sealed partial class BaseItemRepository { var includedItemByNameTypes = GetItemByNameTypesInQuery(filter); var enableItemsByName = (filter.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0; - if (enableItemsByName && includedItemByNameTypes.Count > 0) + + // A by-name item belongs to no library, so it has no TopParentId to test and the filter + // below would drop it. Items-by-name queries exempt the whole group; a query that names a + // by-name type explicitly gets the same exemption, since it is asking for those items. + var exemptedItemByNameTypes = enableItemsByName + ? includedItemByNameTypes + : _itemByNameKinds.Where(filter.IncludeItemTypes.Contains).Select(e => _itemTypeLookup.BaseItemKindNames[e]!).ToList(); + + if (exemptedItemByNameTypes.Count > 0) { - baseQuery = baseQuery.Where(e => includedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value)); + baseQuery = baseQuery.Where(e => exemptedItemByNameTypes.Contains(e.Type) || queryTopParentIds.Any(w => w == e.TopParentId!.Value)); } else { baseQuery = baseQuery.WhereOneOrMany(queryTopParentIds, e => e.TopParentId!.Value); } + + // That exemption is what lets a by-name item from a library the user cannot open show up + // in search. Decide those on the items behind the name instead. + if (filter.UserHasContentRestrictions && exemptedItemByNameTypes.Count > 0) + { + baseQuery = ApplyItemByNameAccessFiltering(baseQuery, context, filter, exemptedItemByNameTypes, queryTopParentIds); + } } if (filter.AncestorIds.Length > 0) @@ -1272,4 +1287,47 @@ public sealed partial class BaseItemRepository return baseQuery; } + + /// + /// Keeps a by-name row only when at least one item behind its name is reachable for the user. + /// + private IQueryable ApplyItemByNameAccessFiltering( + IQueryable baseQuery, + JellyfinDbContext context, + InternalItemsQuery filter, + IReadOnlyList itemByNameTypes, + Guid[] topParentIds) + { + // IncludeOwnedItems: a credit on an alternate version of a reachable movie still counts. + var accessibleItems = ApplyAccessFiltering( + context, + context.BaseItems.AsNoTracking(), + new InternalItemsQuery(filter.User) { TopParentIds = topParentIds, IncludeOwnedItems = true }); + + // Each predicate is written outside-in - name row, then link table, then item - and with nested + // Any() rather than a Contains over the accessible ids, which would materialise all of them + // before the first row. That keeps every step an index seek. + var personType = _itemTypeLookup.BaseItemKindNames[BaseItemKind.Person]; + if (itemByNameTypes.Contains(personType)) + { + baseQuery = baseQuery.Where(e => e.Type != personType + || context.Peoples.Any(p => p.Name == e.Name + && context.PeopleBaseItemMap.Any(m => m.PeopleId == p.Id && accessibleItems.Any(i => i.Id == m.ItemId)))); + } + + foreach (var (kind, valueTypes) in _itemByNameValueTypes) + { + var typeName = _itemTypeLookup.BaseItemKindNames[kind]; + if (!itemByNameTypes.Contains(typeName)) + { + continue; + } + + baseQuery = baseQuery.Where(e => e.Type != typeName + || context.ItemValues.Any(v => valueTypes.Contains(v.Type) && v.CleanValue == e.CleanName + && context.ItemValuesMap.Any(m => m.ItemValueId == v.ItemValueId && accessibleItems.Any(i => i.Id == m.ItemId)))); + } + + return baseQuery; + } } -- cgit v1.2.3