fix(subsonic): add username parameter validation for GetUser endpoint

Fixes #4794

Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
Deluan
2025-12-10 18:30:26 -05:00
parent a081569ed4
commit 6ed6524752
2 changed files with 67 additions and 4 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils/req"
"github.com/navidrome/navidrome/utils/slice" "github.com/navidrome/navidrome/utils/slice"
) )
@@ -35,7 +36,13 @@ func (api *Router) GetUser(r *http.Request) (*responses.Subsonic, error) {
if !ok { if !ok {
return nil, newError(responses.ErrorGeneric, "Internal error") return nil, newError(responses.ErrorGeneric, "Internal error")
} }
username, err := req.Params(r).String("username")
if err != nil {
return nil, err
}
if username != loggedUser.UserName {
return nil, newError(responses.ErrorAuthorizationFail)
}
response := newResponse() response := newResponse()
user := buildUserResponse(loggedUser) user := buildUserResponse(loggedUser)
response.User = &user response.User = &user

View File

@@ -1,7 +1,7 @@
package subsonic package subsonic
import ( import (
"context" "errors"
"net/http/httptest" "net/http/httptest"
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
@@ -43,8 +43,8 @@ var _ = Describe("Users", func() {
} }
// Create request with user in context // Create request with user in context
req := httptest.NewRequest("GET", "/rest/getUser", nil) req := httptest.NewRequest("GET", "/rest/getUser?username=testuser", nil)
ctx := request.WithUser(context.Background(), testUser) ctx := request.WithUser(GinkgoT().Context(), testUser)
req = req.WithContext(ctx) req = req.WithContext(ctx)
userResponse, err1 := router.GetUser(req) userResponse, err1 := router.GetUser(req)
@@ -116,4 +116,60 @@ var _ = Describe("Users", func() {
Expect(response.Folder).To(ContainElements(int32(1), int32(2), int32(5))) Expect(response.Folder).To(ContainElements(int32(1), int32(2), int32(5)))
}) })
}) })
Describe("GetUser authorization", func() {
It("should allow user to request their own information", func() {
req := httptest.NewRequest("GET", "/rest/getUser?username=testuser", nil)
ctx := request.WithUser(GinkgoT().Context(), testUser)
req = req.WithContext(ctx)
response, err := router.GetUser(req)
Expect(err).ToNot(HaveOccurred())
Expect(response).ToNot(BeNil())
Expect(response.User).ToNot(BeNil())
Expect(response.User.Username).To(Equal("testuser"))
})
It("should deny user from requesting another user's information", func() {
req := httptest.NewRequest("GET", "/rest/getUser?username=anotheruser", nil)
ctx := request.WithUser(GinkgoT().Context(), testUser)
req = req.WithContext(ctx)
response, err := router.GetUser(req)
Expect(err).To(HaveOccurred())
Expect(response).To(BeNil())
var subErr subError
ok := errors.As(err, &subErr)
Expect(ok).To(BeTrue())
Expect(subErr.code).To(Equal(responses.ErrorAuthorizationFail))
})
It("should return error when username parameter is missing", func() {
req := httptest.NewRequest("GET", "/rest/getUser", nil)
ctx := request.WithUser(GinkgoT().Context(), testUser)
req = req.WithContext(ctx)
response, err := router.GetUser(req)
Expect(err).To(MatchError("missing parameter: 'username'"))
Expect(response).To(BeNil())
})
It("should return error when user context is missing", func() {
req := httptest.NewRequest("GET", "/rest/getUser?username=testuser", nil)
response, err := router.GetUser(req)
Expect(err).To(HaveOccurred())
Expect(response).To(BeNil())
var subErr subError
ok := errors.As(err, &subErr)
Expect(ok).To(BeTrue())
Expect(subErr.code).To(Equal(responses.ErrorGeneric))
})
})
}) })