Subsonic: user management endpoints (getUser, getUsers, createUser, updateUser, deleteUser, changePassword) with full permission model mapping
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package subsonic
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCreateUserAndGetUser(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "admin", "adminpassword1", true)
|
||||
|
||||
q := baseParams("admin", "adminpassword1")
|
||||
q.Set("username", "newbie")
|
||||
q.Set("password", "newbiepass123")
|
||||
q.Set("email", "newbie@example.com")
|
||||
q.Set("streamRole", "true")
|
||||
q.Set("downloadRole", "false")
|
||||
resp := getPath(t, srv.URL, "/rest/createUser.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("createUser failed: %+v", x)
|
||||
}
|
||||
|
||||
q2 := baseParams("admin", "adminpassword1")
|
||||
q2.Set("username", "newbie")
|
||||
q2.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getUser.view", q2)
|
||||
j := decodeJSON(t, resp)
|
||||
u := j["user"].(map[string]any)
|
||||
if u["email"] != "newbie@example.com" {
|
||||
t.Fatalf("email = %v", u["email"])
|
||||
}
|
||||
if u["streamRole"] != true {
|
||||
t.Fatalf("streamRole = %v, want true", u["streamRole"])
|
||||
}
|
||||
if u["downloadRole"] != false {
|
||||
t.Fatalf("downloadRole = %v, want false", u["downloadRole"])
|
||||
}
|
||||
if u["adminRole"] != false {
|
||||
t.Fatalf("adminRole = %v, want false", u["adminRole"])
|
||||
}
|
||||
|
||||
// The new user can log in with the password we set.
|
||||
loginQ := baseParams("newbie", "newbiepass123")
|
||||
resp = getPath(t, srv.URL, "/rest/ping.view", loginQ)
|
||||
x = decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("new user login failed: %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserForbiddenForOthers(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
createTestUser(t, s, "bob", "password123", false)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("username", "bob")
|
||||
resp := getPath(t, srv.URL, "/rest/getUser.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
|
||||
t.Fatalf("expected error 50, got %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsersRequiresAdmin(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
|
||||
resp := getPath(t, srv.URL, "/rest/getUsers.view", baseParams("alice", "password123"))
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
|
||||
t.Fatalf("expected error 50, got %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserPreservesUnspecifiedRoles(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "admin", "adminpassword1", true)
|
||||
|
||||
q := baseParams("admin", "adminpassword1")
|
||||
q.Set("username", "bob")
|
||||
q.Set("password", "bobpassword12")
|
||||
q.Set("streamRole", "true")
|
||||
q.Set("downloadRole", "true")
|
||||
getPath(t, srv.URL, "/rest/createUser.view", q)
|
||||
|
||||
q2 := baseParams("admin", "adminpassword1")
|
||||
q2.Set("username", "bob")
|
||||
q2.Set("email", "bob@example.com")
|
||||
// Deliberately omit streamRole/downloadRole to verify they survive.
|
||||
resp := getPath(t, srv.URL, "/rest/updateUser.view", q2)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("updateUser failed: %+v", x)
|
||||
}
|
||||
|
||||
q3 := baseParams("admin", "adminpassword1")
|
||||
q3.Set("username", "bob")
|
||||
q3.Set("f", "json")
|
||||
resp = getPath(t, srv.URL, "/rest/getUser.view", q3)
|
||||
j := decodeJSON(t, resp)
|
||||
u := j["user"].(map[string]any)
|
||||
if u["streamRole"] != true || u["downloadRole"] != true {
|
||||
t.Fatalf("roles should be preserved across update, got %v", u)
|
||||
}
|
||||
if u["email"] != "bob@example.com" {
|
||||
t.Fatalf("email = %v, want updated", u["email"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangePasswordSelf(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("username", "alice")
|
||||
q.Set("password", "newpassword456")
|
||||
resp := getPath(t, srv.URL, "/rest/changePassword.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("changePassword failed: %+v", x)
|
||||
}
|
||||
|
||||
resp = getPath(t, srv.URL, "/rest/ping.view", baseParams("alice", "newpassword456"))
|
||||
x = decodeXML(t, resp)
|
||||
if x.Status != "ok" {
|
||||
t.Fatalf("login with new password failed: %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChangePasswordOtherUserForbidden(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "alice", "password123", false)
|
||||
createTestUser(t, s, "bob", "password123", false)
|
||||
|
||||
q := baseParams("alice", "password123")
|
||||
q.Set("username", "bob")
|
||||
q.Set("password", "hacked12345")
|
||||
resp := getPath(t, srv.URL, "/rest/changePassword.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" || x.Error == nil || x.Error.Code != ErrUnauthorized {
|
||||
t.Fatalf("expected error 50, got %+v", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteUserCannotDeleteSelf(t *testing.T) {
|
||||
s, srv := newTestRecorderServer(t)
|
||||
createTestUser(t, s, "admin", "adminpassword1", true)
|
||||
|
||||
q := baseParams("admin", "adminpassword1")
|
||||
q.Set("username", "admin")
|
||||
resp := getPath(t, srv.URL, "/rest/deleteUser.view", q)
|
||||
x := decodeXML(t, resp)
|
||||
if x.Status != "failed" {
|
||||
t.Fatalf("expected failure deleting own account, got %+v", x)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user