mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
feat: enforce NowPlaying.AdminOnly on the getNowPlaying endpoint
When NowPlaying.AdminOnly is enabled, non-admin users now receive an empty list from the Subsonic getNowPlaying endpoint, mirroring how Jukebox.AdminOnly is enforced server-side. Previously this restriction was only applied in the web UI and could be bypassed by calling the endpoint directly. The endpoint otherwise keeps the standard Subsonic behavior of returning what all users are currently playing, without filtering by the requesting user's library access. Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
6af2b1d777
commit
4298cec37d
@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core/scrobbler"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
@ -203,14 +204,20 @@ func (api *Router) GetStarred2(r *http.Request) (*responses.Subsonic, error) {
|
||||
|
||||
func (api *Router) GetNowPlaying(r *http.Request) (*responses.Subsonic, error) {
|
||||
ctx := r.Context()
|
||||
response := newResponse()
|
||||
response.NowPlaying = &responses.NowPlaying{}
|
||||
|
||||
// When restricted to admins, non-admin users get an empty list
|
||||
if conf.Server.NowPlaying.AdminOnly && !getUser(ctx).IsAdmin {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
npInfo, err := api.scrobbler.GetNowPlaying(ctx)
|
||||
if err != nil {
|
||||
log.Error(r, "Error retrieving now playing list", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := newResponse()
|
||||
response.NowPlaying = &responses.NowPlaying{}
|
||||
var i int32
|
||||
response.NowPlaying.Entry = slice.Map(npInfo, func(np scrobbler.PlaybackSession) responses.NowPlayingEntry {
|
||||
i++
|
||||
|
||||
@ -4,8 +4,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http/httptest"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/core/auth"
|
||||
"github.com/navidrome/navidrome/core/scrobbler"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
@ -539,4 +543,94 @@ var _ = Describe("Album Lists", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GetNowPlaying", func() {
|
||||
var mockPlayTracker *fakePlayTracker
|
||||
var user model.User
|
||||
|
||||
BeforeEach(func() {
|
||||
mockPlayTracker = &fakePlayTracker{}
|
||||
user = model.User{
|
||||
ID: "test-user",
|
||||
Libraries: []model.Library{
|
||||
{ID: 1, Name: "Library 1"},
|
||||
{ID: 2, Name: "Library 2"},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("should return what all users are playing, regardless of the requesting user's libraries", func() {
|
||||
// The Subsonic getNowPlaying contract returns activity from all users;
|
||||
// it does not filter by the requesting user's library access.
|
||||
mockPlayTracker.NowPlayingData = []scrobbler.PlaybackSession{
|
||||
{
|
||||
MediaFile: model.MediaFile{ID: "1", Title: "Track 1", LibraryID: 1},
|
||||
Start: time.Now(),
|
||||
Username: "user1",
|
||||
PlayerId: "player1",
|
||||
PlayerName: "Player 1",
|
||||
},
|
||||
{
|
||||
MediaFile: model.MediaFile{ID: "2", Title: "Track 2", LibraryID: 3}, // Library the requesting user can't access
|
||||
Start: time.Now(),
|
||||
Username: "user2",
|
||||
PlayerId: "player2",
|
||||
PlayerName: "Player 2",
|
||||
},
|
||||
}
|
||||
router := New(ds, nil, nil, nil, nil, nil, nil, nil, nil, mockPlayTracker, nil, nil, nil, nil, nil, nil)
|
||||
ctx := request.WithUser(context.Background(), user)
|
||||
r := newGetRequest()
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
resp, err := router.GetNowPlaying(r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.NowPlaying.Entry).To(HaveLen(2))
|
||||
Expect(resp.NowPlaying.Entry[0].Title).To(Equal("Track 1"))
|
||||
Expect(resp.NowPlaying.Entry[1].Title).To(Equal("Track 2"))
|
||||
})
|
||||
|
||||
Context("when NowPlaying.AdminOnly is enabled", func() {
|
||||
BeforeEach(func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.NowPlaying.AdminOnly = true
|
||||
mockPlayTracker.NowPlayingData = []scrobbler.PlaybackSession{
|
||||
{
|
||||
MediaFile: model.MediaFile{ID: "1", Title: "Track 1", LibraryID: 1},
|
||||
Start: time.Now(),
|
||||
Username: "user1",
|
||||
PlayerId: "player1",
|
||||
PlayerName: "Player 1",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
It("should return an empty list to non-admin users", func() {
|
||||
router := New(ds, nil, nil, nil, nil, nil, nil, nil, nil, mockPlayTracker, nil, nil, nil, nil, nil, nil)
|
||||
ctx := request.WithUser(context.Background(), user) // user is not admin
|
||||
r := newGetRequest()
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
resp, err := router.GetNowPlaying(r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.NowPlaying.Entry).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("should return entries to admin users", func() {
|
||||
router := New(ds, nil, nil, nil, nil, nil, nil, nil, nil, mockPlayTracker, nil, nil, nil, nil, nil, nil)
|
||||
admin := user
|
||||
admin.IsAdmin = true
|
||||
ctx := request.WithUser(context.Background(), admin)
|
||||
r := newGetRequest()
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
resp, err := router.GetNowPlaying(r)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(resp.NowPlaying.Entry).To(HaveLen(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -190,11 +190,12 @@ var _ = Describe("MediaAnnotationController", func() {
|
||||
type fakePlayTracker struct {
|
||||
Submissions []scrobbler.Submission
|
||||
ReportedPlayback []scrobbler.ReportPlaybackParams
|
||||
NowPlayingData []scrobbler.PlaybackSession
|
||||
Error error
|
||||
}
|
||||
|
||||
func (f *fakePlayTracker) GetNowPlaying(_ context.Context) ([]scrobbler.PlaybackSession, error) {
|
||||
return nil, f.Error
|
||||
return f.NowPlayingData, f.Error
|
||||
}
|
||||
|
||||
func (f *fakePlayTracker) Submit(_ context.Context, submissions []scrobbler.Submission) error {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user