mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
Artwork state and its bytes outlive a deleted entity until the next prune (@daily), and the serving path consulted only item_artwork, so a Subsonic id or a signed public token kept serving a removed entity's image in the meantime. Master's readers loaded the entity first, so this was a regression. The check goes in serveHash, the one path that can hand back a found row's bytes: absent rows are already unavailable, and the provisional and disc paths load their entity to resolve at all. Doing it there instead of per-handler also settles who owns the invariant. Subsonic had worked around it with artworkAccessible, whose comment described the service "bypassing the library and private-playlist filters"; that workaround is now deleted, since the service resolves through the request-scoped repositories and enforces the filters itself. Because those repositories are ctx-scoped, each caller says what it wants by what it passes: Subsonic hands over the request context and so gets visibility as well as existence, while the public image route elevates like the Jellyfin one already did -- a token is the authorization there, and a visibility check would hide a shared private playlist, the very case shares exist for. Two supporting fixes. albumRepository.Exists and mediaFileRepository .Exists used the plain exists() helper, which applies no library filter, so they reported rows in libraries the caller cannot see; they now count through applyLibraryFilter as CountAll and artistRepository.Exists already do. Neither had a production caller. RadioRepository gained the Exists it lacked. Tests follow the layers: the service refuses a found row whose entity is gone, the repositories hide rows the caller may not see, and the handlers only assert the context they hand over. Jellyfin needed no change -- resolveArtworkID probes the entity tables, so a deleted item yields an empty artwork id -- but that protection was incidental and untested, so it is pinned now.
41 lines
1.1 KiB
Go
41 lines
1.1 KiB
Go
package model
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/navidrome/navidrome/consts"
|
|
)
|
|
|
|
type Radio struct {
|
|
ItemImage `structs:"-"`
|
|
|
|
ID string `structs:"id" json:"id"`
|
|
StreamUrl string `structs:"stream_url" json:"streamUrl"`
|
|
Name string `structs:"name" json:"name"`
|
|
HomePageUrl string `structs:"home_page_url" json:"homePageUrl"`
|
|
UploadedImage string `structs:"uploaded_image" json:"uploadedImage,omitempty"`
|
|
CreatedAt time.Time `structs:"created_at" json:"createdAt"`
|
|
UpdatedAt time.Time `structs:"updated_at" json:"updatedAt"`
|
|
}
|
|
|
|
func (r Radio) CoverArtID() ArtworkID {
|
|
return artworkIDFromRadio(r)
|
|
}
|
|
|
|
func (r Radio) UploadedImagePath() string {
|
|
return UploadedImagePath(consts.EntityRadio, r.UploadedImage)
|
|
}
|
|
|
|
type Radios []Radio
|
|
|
|
type RadioRepository interface {
|
|
ResourceRepository
|
|
CountAll(options ...QueryOptions) (int64, error)
|
|
Delete(id string) error
|
|
Exists(id string) (bool, error)
|
|
Get(id string) (*Radio, error)
|
|
GetAll(options ...QueryOptions) (Radios, error)
|
|
GetAllIDs(options ...QueryOptions) ([]string, error)
|
|
Put(u *Radio, colsToUpdate ...string) error
|
|
}
|