mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-08 02:17:25 +02:00
Merge commit from fork
This commit is contained in:
parent
2d09ebc676
commit
4168377b65
2 changed files with 64 additions and 3 deletions
|
|
@ -162,13 +162,26 @@ func NewArtistRepository(ctx context.Context, db dbx.Builder) model.ArtistReposi
|
|||
|
||||
func roleFilter(_ string, role any) Sqlizer {
|
||||
if role, ok := role.(string); ok {
|
||||
if _, ok := model.AllRoles[role]; ok {
|
||||
return Expr("JSON_EXTRACT(library_artist.stats, '$." + role + ".m') IS NOT NULL")
|
||||
if safe, ok := sanitizeArtistStatsRole(role); ok && safe != "total" {
|
||||
return Expr("JSON_EXTRACT(library_artist.stats, '$." + safe + ".m') IS NOT NULL")
|
||||
}
|
||||
}
|
||||
return Eq{"1": 2}
|
||||
}
|
||||
|
||||
// sanitizeArtistStatsRole allowlists values interpolated into JSON paths for artist
|
||||
// stats (filter and sort). "total" is the aggregate key stored by the scanner.
|
||||
// Unknown values must not reach SQL string concatenation.
|
||||
func sanitizeArtistStatsRole(role string) (string, bool) {
|
||||
if role == "" || role == "total" {
|
||||
return "total", true
|
||||
}
|
||||
if _, ok := model.AllRoles[role]; ok {
|
||||
return role, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// artistLibraryIdFilter filters artists based on library access through the library_artist table
|
||||
func artistLibraryIdFilter(_ string, value any) Sqlizer {
|
||||
return Eq{"library_artist.library_id": value}
|
||||
|
|
@ -716,7 +729,9 @@ func (r *artistRepository) ReadAll(options ...rest.QueryOptions) (any, error) {
|
|||
role := "total"
|
||||
if len(options) > 0 {
|
||||
if v, ok := options[0].Filters["role"].(string); ok {
|
||||
role = v
|
||||
if safe, ok := sanitizeArtistStatsRole(v); ok {
|
||||
role = safe
|
||||
}
|
||||
}
|
||||
}
|
||||
r.sortMappings["song_count"] = "sum(stats->>'" + role + "'->>'m')"
|
||||
|
|
|
|||
|
|
@ -112,6 +112,52 @@ var _ = Describe("ArtistRepository", func() {
|
|||
})
|
||||
})
|
||||
|
||||
Describe("sanitizeArtistStatsRole", func() {
|
||||
It("allowlists total and registered roles", func() {
|
||||
role, ok := sanitizeArtistStatsRole("total")
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(role).To(Equal("total"))
|
||||
role, ok = sanitizeArtistStatsRole("albumartist")
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(role).To(Equal("albumartist"))
|
||||
})
|
||||
|
||||
It("rejects SQL injection payloads used in sort mappings", func() {
|
||||
payload := "total'||(SELECT password FROM user LIMIT 1)||'"
|
||||
role, ok := sanitizeArtistStatsRole(payload)
|
||||
Expect(ok).To(BeFalse())
|
||||
Expect(role).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ReadAll role sort SQL injection", func() {
|
||||
It("does not interpolate attacker-controlled role into ORDER BY", func() {
|
||||
ctx := request.WithUser(GinkgoT().Context(), adminUser)
|
||||
repo := NewArtistRepository(ctx, GetDBXBuilder()).(*artistRepository)
|
||||
payload := "total') OR 1=1--"
|
||||
_, err := repo.ReadAll(rest.QueryOptions{
|
||||
Sort: "songCount",
|
||||
Order: "ASC",
|
||||
Filters: map[string]any{"role": payload},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(repo.sortMappings["song_count"]).To(Equal("sum(stats->>'total'->>'m')"))
|
||||
Expect(repo.sortMappings["song_count"]).ToNot(ContainSubstring(payload))
|
||||
})
|
||||
|
||||
It("keeps valid role sort paths", func() {
|
||||
ctx := request.WithUser(GinkgoT().Context(), adminUser)
|
||||
repo := NewArtistRepository(ctx, GetDBXBuilder()).(*artistRepository)
|
||||
_, err := repo.ReadAll(rest.QueryOptions{
|
||||
Sort: "songCount",
|
||||
Order: "DESC",
|
||||
Filters: map[string]any{"role": "composer"},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(repo.sortMappings["song_count"]).To(Equal("sum(stats->>'composer'->>'m')"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("searchScope", func() {
|
||||
// Resolves the library IDs a search must be restricted to (nil = fast-path / no filter),
|
||||
// the way Search() does, for a repo whose context carries the given user.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue