功能:扩展云管资源运维、账号授权及监控管理
This commit is contained in:
1 parent
eef485306f
commit
6134d49c16
87 files changed
+29498
-126
No files matched your search
@@ -0,0 +1,195 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"vdi-cloud/platform/internal/store"
|
||||
)
|
||||
|
||||
const testAccountPassword = "Account-test-2026"
|
||||
|
||||
func createTestAccount(t *testing.T, f *fixture, token, username, role string) string {
|
||||
t.Helper()
|
||||
status, out := f.request("POST", "/administrators", token, map[string]any{"username": username, "displayName": "Account test", "role": role, "password": testAccountPassword}, "")
|
||||
expect(t, 201, status, out)
|
||||
for _, field := range []string{"password", "passwordHash", "password_hash"} {
|
||||
if _, ok := out[field]; ok {
|
||||
t.Fatal("credential leaked", field)
|
||||
}
|
||||
}
|
||||
return out["id"].(string)
|
||||
}
|
||||
func accountLogin(t *testing.T, f *fixture, username, password string) string {
|
||||
t.Helper()
|
||||
status, out := f.request("POST", "/auth/login", "", map[string]any{"username": username, "password": password}, "")
|
||||
expect(t, 200, status, out)
|
||||
return out["accessToken"].(string)
|
||||
}
|
||||
|
||||
func TestAdministratorLifecycleAndSessionRevocation(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
admin := f.login("admin")
|
||||
id := createTestAccount(t, f, admin, "maintainer", "operator")
|
||||
token := accountLogin(t, f, "maintainer", testAccountPassword)
|
||||
status, out := f.request("GET", "/administrators", token, nil, "")
|
||||
expect(t, 403, status, out)
|
||||
status, out = f.request("PUT", "/administrators/"+id, admin, map[string]any{"displayName": "Read-only", "role": "viewer", "enabled": true}, "")
|
||||
expect(t, 200, status, out)
|
||||
status, out = f.request("GET", "/auth/me", token, nil, "")
|
||||
expect(t, 401, status, out)
|
||||
token = accountLogin(t, f, "maintainer", testAccountPassword)
|
||||
status, out = f.request("POST", "/desktops/desk_demo_kylin/actions", token, map[string]any{"action": "start"}, "viewer-no-power")
|
||||
expect(t, 403, status, out)
|
||||
status, out = f.request("POST", "/administrators/"+id+"/password", admin, map[string]any{"password": "Reset-test-2026"}, "")
|
||||
expect(t, 204, status, out)
|
||||
status, out = f.request("GET", "/auth/me", token, nil, "")
|
||||
expect(t, 401, status, out)
|
||||
status, out = f.request("POST", "/auth/login", "", map[string]any{"username": "maintainer", "password": testAccountPassword}, "")
|
||||
expect(t, 401, status, out)
|
||||
token = accountLogin(t, f, "maintainer", "Reset-test-2026")
|
||||
status, out = f.request("PUT", "/administrators/"+id, admin, map[string]any{"displayName": "Disabled", "role": "viewer", "enabled": false}, "")
|
||||
expect(t, 200, status, out)
|
||||
status, out = f.request("GET", "/auth/me", token, nil, "")
|
||||
expect(t, 401, status, out)
|
||||
status, out = f.request("POST", "/auth/login", "", map[string]any{"username": "maintainer", "password": "Reset-test-2026"}, "")
|
||||
expect(t, 401, status, out)
|
||||
if err := store.Bootstrap(context.Background(), f.db, "demo", "", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var enabled bool
|
||||
if err := f.db.Get(&enabled, "SELECT enabled FROM administrators WHERE id=$1", id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if enabled {
|
||||
t.Fatal("restart re-enabled disabled account")
|
||||
}
|
||||
status, out = f.request("PUT", "/administrators/"+id, admin, map[string]any{"displayName": "Restored", "role": "operator", "enabled": true}, "")
|
||||
expect(t, 200, status, out)
|
||||
_ = accountLogin(t, f, "maintainer", "Reset-test-2026")
|
||||
var audit []string
|
||||
if err := f.db.Select(&audit, "SELECT action||':'||target FROM audit_logs WHERE target='maintainer' AND action LIKE '%administrator%'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(audit) != 5 {
|
||||
t.Fatal("account changes not audited", audit)
|
||||
}
|
||||
status, out = f.request("GET", "/administrators", admin, nil, "")
|
||||
expect(t, 200, status, out)
|
||||
encoded, _ := json.Marshal(out)
|
||||
if strings.Contains(string(encoded), "$2") || strings.Contains(string(encoded), testAccountPassword) {
|
||||
t.Fatal("credential material in account listing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwnPasswordRequiresCurrentPasswordAndInvalidatesAllSessions(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
token := f.login("viewer")
|
||||
other := f.login("viewer")
|
||||
status, out := f.request("POST", "/auth/password", token, map[string]any{"currentPassword": "wrong-password", "newPassword": "Updated-test-2026"}, "")
|
||||
expect(t, 400, status, out)
|
||||
status, out = f.request("GET", "/auth/me", token, nil, "")
|
||||
expect(t, 200, status, out)
|
||||
status, out = f.request("POST", "/auth/password", token, map[string]any{"currentPassword": "Demo@2026", "newPassword": "Updated-test-2026"}, "")
|
||||
expect(t, 204, status, out)
|
||||
for _, old := range []string{token, other} {
|
||||
status, out = f.request("GET", "/auth/me", old, nil, "")
|
||||
expect(t, 401, status, out)
|
||||
}
|
||||
status, out = f.request("POST", "/auth/login", "", map[string]any{"username": "viewer", "password": "Demo@2026"}, "")
|
||||
expect(t, 401, status, out)
|
||||
_ = accountLogin(t, f, "viewer", "Updated-test-2026")
|
||||
var audit string
|
||||
if err := f.db.Get(&audit, "SELECT action FROM audit_logs WHERE action='change_own_password'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdministratorValidationRBACAndLastOwner(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
admin := f.login("admin")
|
||||
var adminID string
|
||||
if err := f.db.Get(&adminID, "SELECT id FROM administrators WHERE username='admin'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, role := range []string{"operator", "viewer"} {
|
||||
token := f.login(role)
|
||||
for _, r := range []struct{ method, path string }{{"GET", "/administrators"}, {"POST", "/administrators"}, {"PUT", "/administrators/" + adminID}, {"POST", "/administrators/" + adminID + "/password"}} {
|
||||
status, out := f.request(r.method, r.path, token, map[string]any{}, "")
|
||||
expect(t, 403, status, out)
|
||||
}
|
||||
}
|
||||
for _, password := range []string{"short", strings.Repeat("a", 73), strings.Repeat("密", 25)} {
|
||||
status, out := f.request("POST", "/administrators", admin, map[string]any{"username": "invalid-pass", "displayName": "Invalid", "role": "viewer", "password": password}, "")
|
||||
expect(t, 400, status, out)
|
||||
}
|
||||
body := map[string]any{"username": "test-user", "displayName": "Valid", "role": "super_admin", "password": testAccountPassword}
|
||||
status, out := f.request("POST", "/administrators", admin, body, "")
|
||||
expect(t, 201, status, out)
|
||||
status, out = f.request("POST", "/administrators", admin, body, "")
|
||||
expect(t, 409, status, out)
|
||||
status, out = f.request("PUT", "/administrators/"+adminID, admin, map[string]any{"displayName": "Self", "role": "viewer", "enabled": true}, "")
|
||||
expect(t, 409, status, out)
|
||||
status, out = f.request("POST", "/administrators/"+adminID+"/password", admin, map[string]any{"password": testAccountPassword}, "")
|
||||
expect(t, 409, status, out)
|
||||
status, out = f.request("PUT", "/administrators/"+adminID, admin, map[string]any{"displayName": "Self name", "role": "super_admin", "enabled": true}, "")
|
||||
expect(t, 200, status, out)
|
||||
status, out = f.request("GET", "/auth/me", admin, nil, "")
|
||||
expect(t, 200, status, out)
|
||||
if out["displayName"] != "Self name" {
|
||||
t.Fatal(out)
|
||||
}
|
||||
// The final owner's role and enabled state remain protected independently of UI restrictions.
|
||||
if _, err := f.db.Exec("UPDATE administrators SET enabled=false WHERE username='test-user'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
status, out = f.request("PUT", "/administrators/"+adminID, admin, map[string]any{"displayName": "Self", "role": "super_admin", "enabled": false}, "")
|
||||
expect(t, 409, status, out)
|
||||
if out["code"] != "last_super_admin" {
|
||||
t.Fatal(out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentAdministratorDisableKeepsOneOwner(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
admin := f.login("admin")
|
||||
otherID := createTestAccount(t, f, admin, "second-owner", "super_admin")
|
||||
other := accountLogin(t, f, "second-owner", testAccountPassword)
|
||||
var adminID string
|
||||
if err := f.db.Get(&adminID, "SELECT id FROM administrators WHERE username='admin'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
start := make(chan struct{})
|
||||
results := make(chan int, 2)
|
||||
var group sync.WaitGroup
|
||||
for _, r := range []struct{ token, target string }{{admin, otherID}, {other, adminID}} {
|
||||
group.Add(1)
|
||||
go func(token, target string) {
|
||||
defer group.Done()
|
||||
<-start
|
||||
status, _ := f.request("PUT", "/administrators/"+target, token, map[string]any{"displayName": "Concurrent", "role": "super_admin", "enabled": false}, "")
|
||||
results <- status
|
||||
}(r.token, r.target)
|
||||
}
|
||||
close(start)
|
||||
group.Wait()
|
||||
close(results)
|
||||
success := 0
|
||||
for status := range results {
|
||||
if status == 200 {
|
||||
success++
|
||||
} else if status != 401 && status != 403 {
|
||||
t.Fatalf("unexpected losing request status %d", status)
|
||||
}
|
||||
}
|
||||
var count int
|
||||
if err := f.db.Get(&count, "SELECT count(*) FROM administrators WHERE enabled AND role='super_admin'"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if success != 1 || count != 1 {
|
||||
t.Fatal("concurrent writes lost owner", success, count)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user