diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-07-28 23:13:10 +0200 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-07-28 23:13:10 +0200 |
| commit | a94588497c521a89aa4a78eba44e305d06d91aa8 (patch) | |
| tree | ff06c113f1812bb273c1958c061282fc917abe57 | |
| parent | 9a258c089dcbc68b12fe63479542fc7158ec7a10 (diff) | |
Fix Folder access filtering
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs | 42 | ||||
| -rw-r--r-- | Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs | 39 |
2 files changed, 73 insertions, 8 deletions
diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs index a4de9feb05..9d16f7976a 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.QueryBuilding.cs @@ -396,6 +396,17 @@ public sealed partial class BaseItemRepository } /// <summary> + /// Checks whether the user restricts access to items by parental rating or tags. + /// </summary> + /// <param name="filter">The query filter.</param> + /// <returns><c>true</c> if the query carries parental restrictions.</returns> + private static bool RequiresParentalRestrictions(InternalItemsQuery filter) + => filter.IncludeInheritedTags.Length > 0 + || filter.ExcludeInheritedTags.Length > 0 + || filter.MaxParentalRating is not null + || filter.BlockUnratedItems.Length > 0; + + /// <summary> /// Applies user access filtering to a query. /// Includes TopParentIds, parental rating, and tag filtering. /// </summary> @@ -412,6 +423,30 @@ public sealed partial class BaseItemRepository baseQuery = baseQuery.Where(e => topParentIds.Contains(e.TopParentId!.Value)); } + baseQuery = ApplyParentalRestrictions(context, baseQuery, filter); + + // Exclude alternate versions (have PrimaryVersionId set) and owned non-extra items. + // Extras (trailers, etc.) have OwnerId set but also have ExtraType set — keep those. + if (!filter.IncludeOwnedItems) + { + baseQuery = baseQuery.Where(e => e.PrimaryVersionId == null && (e.OwnerId == null || e.ExtraType != null)); + } + + return baseQuery; + } + + /// <summary> + /// Applies the user's parental rating and tag restrictions to a query. + /// </summary> + /// <param name="context">The database context.</param> + /// <param name="baseQuery">The query to filter.</param> + /// <param name="filter">The query filter.</param> + /// <returns>The filtered query.</returns> + private IQueryable<BaseItemEntity> ApplyParentalRestrictions( + JellyfinDbContext context, + IQueryable<BaseItemEntity> baseQuery, + InternalItemsQuery filter) + { // Apply parental rating filtering if (filter.MaxParentalRating is not null) { @@ -462,13 +497,6 @@ public sealed partial class BaseItemRepository || e.Type == personTypeName); } - // Exclude alternate versions (have PrimaryVersionId set) and owned non-extra items. - // Extras (trailers, etc.) have OwnerId set but also have ExtraType set — keep those. - if (!filter.IncludeOwnedItems) - { - baseQuery = baseQuery.Where(e => e.PrimaryVersionId == null && (e.OwnerId == null || e.ExtraType != null)); - } - return baseQuery; } diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs index 1ff8d8f863..5ff6e6da49 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.Querying.cs @@ -167,7 +167,10 @@ public sealed partial class BaseItemRepository .Where(album => albumIdsWithMatchingTrack.Contains(album.Id)); } - var orderedAlbums = topAlbumsQuery + // The album is what gets returned, and neither branch above reads it through the + // user's filters, so its own parental restrictions have to be applied here: a + // matching track does not make an album the user may not see visible. + var orderedAlbums = ApplyParentalRestrictions(context, topAlbumsQuery, filter) .OrderByDescending(album => album.DateCreated) .ThenByDescending(album => album.Id); @@ -420,6 +423,40 @@ public sealed partial class BaseItemRepository seriesResults.Add((seasonId, seriesId, maxDate, mostRecentEpisodeId)); } + // Step 5b: A container is what gets returned, so it has to pass the user's access + // filters on its own - a matching episode does not make a Season or Series the user + // may not see visible. Containers that don't pass are replaced by their episode. + if (RequiresParentalRestrictions(filter) && entitiesToFetch.Count > 0) + { + var allowedContainerIds = ApplyParentalRestrictions( + context, + context.BaseItems.AsNoTracking().Where(e => entitiesToFetch.Contains(e.Id)), + filter) + .Select(e => e.Id) + .ToHashSet(); + + for (var i = 0; i < seriesResults.Count; i++) + { + var (seasonId, seriesId, maxDate, mostRecentEpisodeId) = seriesResults[i]; + if (seasonId.HasValue && !allowedContainerIds.Contains(seasonId.Value)) + { + seasonId = null; + } + + if (seriesId.HasValue && !allowedContainerIds.Contains(seriesId.Value)) + { + seriesId = null; + } + + if (seasonId is null && seriesId is null) + { + entitiesToFetch.Add(mostRecentEpisodeId); + } + + seriesResults[i] = (seasonId, seriesId, maxDate, mostRecentEpisodeId); + } + } + // Step 6: Fetch the Season/Series entities we decided to return var entities = entitiesToFetch.Count > 0 ? ApplyNavigations( |
