Fix Folder access filtering

This commit is contained in:
Shadowghost
2026-07-28 23:13:10 +02:00
parent 9a258c089d
commit a94588497c
2 changed files with 73 additions and 8 deletions
@@ -395,6 +395,17 @@ public sealed partial class BaseItemRepository
return ApplyAccessFiltering(context, baseQuery, filter);
}
/// <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.
@@ -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;
}
@@ -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(