fix(subsonic): getUser answers an admin's lookup with the requested user (#6292)
An admin asking for another username got their own name and roles back. The handler now looks the user up by exact username and answers with that user's payload; an unknown username is ErrDataNotFound. A non-admin asking about someone else is still ErrNotAuthorized. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+30
-11
@@ -1,9 +1,13 @@
|
||||
package subsonic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
)
|
||||
|
||||
@@ -38,26 +42,41 @@ type User struct {
|
||||
VideoConversionRole bool `json:"videoConversionRole" xml:"videoConversionRole,attr"`
|
||||
}
|
||||
|
||||
// handleGetUser returns the authenticated user when no username is provided
|
||||
// or when the requested username matches; admins may look up any user. Per
|
||||
// spec a non-admin asking about another user gets ErrNotAuthorized.
|
||||
func handleGetUser(w http.ResponseWriter, r *http.Request) {
|
||||
// userHandlers serves getUser. lookup finds a user by exact username; it is
|
||||
// a field so tests can answer without a database.
|
||||
type userHandlers struct {
|
||||
lookup func(ctx context.Context, username string) (dbq.User, error)
|
||||
}
|
||||
|
||||
// getUser returns the authenticated user when no username is provided or
|
||||
// when the requested username matches; admins may look up any user, and an
|
||||
// unknown username is ErrDataNotFound. Per spec a non-admin asking about
|
||||
// another user gets ErrNotAuthorized.
|
||||
func (h *userHandlers) getUser(w http.ResponseWriter, r *http.Request) {
|
||||
caller, ok := UserFromContext(r.Context())
|
||||
if !ok {
|
||||
WriteFail(w, r, ErrNotAuthorized, "Not authenticated")
|
||||
return
|
||||
}
|
||||
requested := r.URL.Query().Get("username")
|
||||
if requested != "" && requested != caller.Username && !caller.IsAdmin {
|
||||
if requested == "" || requested == caller.Username {
|
||||
Write(w, r, UserResponse{Envelope: NewEnvelope("ok"), User: userPayload(caller)})
|
||||
return
|
||||
}
|
||||
if !caller.IsAdmin {
|
||||
WriteFail(w, r, ErrNotAuthorized, "Not authorized to view other users")
|
||||
return
|
||||
}
|
||||
// No per-user lookup: an admin asking for another username is answered
|
||||
// with the caller's own roles.
|
||||
Write(w, r, UserResponse{
|
||||
Envelope: NewEnvelope("ok"),
|
||||
User: userPayload(caller),
|
||||
})
|
||||
target, err := h.lookup(r.Context(), requested)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
WriteFail(w, r, ErrDataNotFound, "User not found")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
WriteFail(w, r, ErrGeneric, "Failed to look up user")
|
||||
return
|
||||
}
|
||||
Write(w, r, UserResponse{Envelope: NewEnvelope("ok"), User: userPayload(target)})
|
||||
}
|
||||
|
||||
func userPayload(u dbq.User) User {
|
||||
|
||||
Reference in New Issue
Block a user