feat(database): add library filtering to album, folder, and media file queries

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan 2025-06-06 16:46:38 -04:00
commit 5c01eb06ae
5 changed files with 39 additions and 3 deletions

View file

@ -217,7 +217,8 @@ func (r *albumRepository) UpdateExternalInfo(al *model.Album) error {
func (r *albumRepository) selectAlbum(options ...model.QueryOptions) SelectBuilder {
sql := r.newSelect(options...).Columns("album.*")
return r.withAnnotation(sql, "album.id")
sql = r.withAnnotation(sql, "album.id")
return r.applyLibraryFilter(sql)
}
func (r *albumRepository) Get(id string) (*model.Album, error) {

View file

@ -61,8 +61,9 @@ func newFolderRepository(ctx context.Context, db dbx.Builder) model.FolderReposi
}
func (r folderRepository) selectFolder(options ...model.QueryOptions) SelectBuilder {
return r.newSelect(options...).Columns("folder.*", "library.path as library_path").
sql := r.newSelect(options...).Columns("folder.*", "library.path as library_path").
Join("library on library.id = folder.library_id")
return r.applyLibraryFilter(sql)
}
func (r folderRepository) Get(id string) (*model.Folder, error) {

View file

@ -137,7 +137,8 @@ func (r *mediaFileRepository) selectMediaFile(options ...model.QueryOptions) Sel
sql := r.newSelect(options...).Columns("media_file.*", "library.path as library_path").
LeftJoin("library on media_file.library_id = library.id")
sql = r.withAnnotation(sql, "media_file.id")
return r.withBookmark(sql, "media_file.id")
sql = r.withBookmark(sql, "media_file.id")
return r.applyLibraryFilter(sql)
}
func (r *mediaFileRepository) Get(id string) (*model.MediaFile, error) {

View file

@ -199,6 +199,29 @@ func (r sqlRepository) applyFilters(sq SelectBuilder, options ...model.QueryOpti
return sq
}
// applyLibraryFilter adds library filtering to queries for tables that have a library_id column
// This ensures users only see content from libraries they have access to
func (r sqlRepository) applyLibraryFilter(sq SelectBuilder) SelectBuilder {
user := loggedUser(r.ctx)
// Admin users see all content
if user.IsAdmin {
return sq
}
// Get user's accessible library IDs
userID := userId(r.ctx)
if userID == invalidUserId {
// No user context - return empty result set
return sq.Where(Eq{"1": "0"})
}
// Use subquery to filter by user's library access
// This approach doesn't require DataStore in context
return sq.Where(Expr(r.tableName+".library_id IN ("+
"SELECT ul.library_id FROM user_library ul WHERE ul.user_id = ?)", userID))
}
func (r sqlRepository) seedKey() string {
// Seed keys must be all lowercase, or else SQLite3 will encode it, making it not match the seed
// used in the query. Hashing the user ID and converting it to a hex string will do the trick

View file

@ -71,6 +71,16 @@ var _ = Describe("Scanner", Ordered, func() {
}
ds.MockedMediaFile = mfRepo
// Create the admin user in the database to match the context
adminUser := model.User{
ID: "123",
UserName: "admin",
Name: "Admin User",
IsAdmin: true,
NewPassword: "password",
}
Expect(ds.User(ctx).Put(&adminUser)).To(Succeed())
s = scanner.New(ctx, ds, artwork.NoopCacheWarmer(), events.NoopBroker(),
core.NewPlaylists(ds), metrics.NewNoopInstance())